MYKeychain.m
author snej@snej-mbp.mtv.corp.google.com
Wed Apr 08 16:30:52 2009 -0700 (2009-04-08)
changeset 3 1dfe820d7ebe
parent 2 8982b8fada63
child 4 f4709533c816
permissions -rw-r--r--
* Replaced MYKeyPair with MYPrivateKey.
* Changed config files.
snej@0
     1
//
snej@0
     2
//  MYKeychain.m
snej@0
     3
//  MYCrypto
snej@0
     4
//
snej@0
     5
//  Created by Jens Alfke on 3/23/09.
snej@0
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@0
     7
//
snej@0
     8
snej@0
     9
#import "MYKeychain.h"
snej@0
    10
#import "MYCrypto_Private.h"
snej@0
    11
#import "MYDigest.h"
snej@0
    12
snej@2
    13
#if !MYCRYPTO_USE_IPHONE_API
snej@0
    14
snej@0
    15
snej@0
    16
@interface MYKeyEnumerator : NSEnumerator
snej@0
    17
{
snej@0
    18
    MYKeychain *_keychain;
snej@0
    19
    SecKeychainSearchRef _search;
snej@0
    20
    SecItemClass _itemClass;
snej@0
    21
}
snej@0
    22
snej@0
    23
- (id) initWithKeychain: (MYKeychain*)keychain
snej@0
    24
              itemClass: (SecItemClass)itemClass
snej@0
    25
             attributes: (SecKeychainAttribute[])attributes 
snej@0
    26
                  count: (unsigned)count;
snej@0
    27
@end
snej@0
    28
snej@0
    29
snej@0
    30
snej@0
    31
@implementation MYKeychain
snej@0
    32
snej@0
    33
snej@0
    34
- (id) initWithKeychainRef: (SecKeychainRef)keychainRef
snej@0
    35
{
snej@0
    36
    self = [super init];
snej@0
    37
    if (self != nil) {
snej@0
    38
        if (keychainRef) {
snej@0
    39
            CFRetain(keychainRef);
snej@0
    40
            _keychain = keychainRef;
snej@0
    41
        }
snej@0
    42
    }
snej@0
    43
    return self;
snej@0
    44
}
snej@0
    45
snej@0
    46
+ (MYKeychain*) _readableKeychainWithRef: (SecKeychainRef)keychainRef fromPath: (NSString*)path {
snej@0
    47
    if (!keychainRef)
snej@0
    48
        return nil;
snej@0
    49
    SecKeychainStatus status;
snej@0
    50
    BOOL ok = check(SecKeychainGetStatus(keychainRef, &status), @"SecKeychainGetStatus");
snej@0
    51
    if (ok && !(status & kSecReadPermStatus)) {
snej@0
    52
        Warn(@"Can't open keychain at %@ : not readable (status=%i)", path,status);
snej@0
    53
        ok = NO;
snej@0
    54
    }
snej@0
    55
    MYKeychain *keychain = nil;
snej@0
    56
    if (ok)
snej@0
    57
        keychain = [[[self alloc] initWithKeychainRef: keychainRef] autorelease];
snej@0
    58
    CFRelease(keychainRef);
snej@0
    59
    return keychain;
snej@0
    60
}
snej@0
    61
snej@0
    62
+ (MYKeychain*) openKeychainAtPath: (NSString*)path
snej@0
    63
{
snej@0
    64
    Assert(path);
snej@0
    65
    SecKeychainRef keychainRef = NULL;
snej@0
    66
    if (!check(SecKeychainOpen(path.fileSystemRepresentation, &keychainRef), @"SecKeychainOpen"))
snej@0
    67
        return nil;
snej@0
    68
    return [self _readableKeychainWithRef: keychainRef fromPath: path];
snej@0
    69
}
snej@0
    70
snej@0
    71
+ (MYKeychain*) createKeychainAtPath: (NSString*)path
snej@0
    72
                        withPassword: (NSString*)password
snej@0
    73
{
snej@0
    74
    Assert(path);
snej@0
    75
    const char *passwordStr = [password UTF8String];
snej@0
    76
    SecKeychainRef keychainRef = NULL;
snej@0
    77
    if (!check(SecKeychainCreate(path.fileSystemRepresentation,
snej@0
    78
                                 passwordStr ?strlen(passwordStr) :0,
snej@0
    79
                                 passwordStr, 
snej@0
    80
                                 (password==nil), 
snej@0
    81
                                 NULL, 
snej@0
    82
                                 &keychainRef),
snej@0
    83
               @"SecKeychainCreate"))
snej@0
    84
        return nil;
snej@0
    85
    return [self _readableKeychainWithRef: keychainRef fromPath: path];
snej@0
    86
}
snej@0
    87
snej@0
    88
- (BOOL) deleteKeychainFile {
snej@0
    89
    Assert(_keychain);
snej@0
    90
    return check(SecKeychainDelete(_keychain), @"SecKeychainDelete");
snej@0
    91
}
snej@0
    92
snej@0
    93
snej@0
    94
- (void) dealloc
snej@0
    95
{
snej@0
    96
    if (_keychain) CFRelease(_keychain);
snej@0
    97
    [super dealloc];
snej@0
    98
}
snej@0
    99
snej@2
   100
- (void) finalize
snej@2
   101
{
snej@2
   102
    if (_keychain) CFRelease(_keychain);
snej@2
   103
    [super finalize];
snej@2
   104
}
snej@2
   105
snej@0
   106
snej@0
   107
+ (MYKeychain*) allKeychains
snej@0
   108
{
snej@0
   109
    static MYKeychain *sAllKeychains;
snej@0
   110
    @synchronized(self) {
snej@0
   111
        if (!sAllKeychains)
snej@0
   112
            sAllKeychains = [[self alloc] initWithKeychainRef: nil];
snej@0
   113
    }
snej@0
   114
    return sAllKeychains;
snej@0
   115
}
snej@0
   116
snej@0
   117
snej@0
   118
+ (MYKeychain*) defaultKeychain
snej@0
   119
{
snej@0
   120
    static MYKeychain *sDefaultKeychain;
snej@0
   121
    @synchronized(self) {
snej@0
   122
        if (!sDefaultKeychain) {
snej@0
   123
            SecKeychainRef kc = NULL;
snej@0
   124
            OSStatus err = SecKeychainCopyDomainDefault(kSecPreferencesDomainUser,&kc);
snej@0
   125
#if TARGET_OS_IPHONE
snej@0
   126
            // In the simulator, an app is run in a sandbox that has no keychain by default.
snej@0
   127
            // As a convenience, create one if necessary:
snej@0
   128
            if (err == errSecNoDefaultKeychain) {
snej@0
   129
                Log(@"No default keychain in simulator; creating one...");
snej@0
   130
                NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
snej@0
   131
                                                                      NSUserDomainMask, YES) objectAtIndex: 0];
snej@0
   132
                path = [path stringByAppendingPathComponent: @"MYCrypto.keychain"];
snej@0
   133
                sDefaultKeychain = [[self createKeychainAtPath: path withPassword: nil] retain];
snej@0
   134
                Assert(sDefaultKeychain, @"Couldn't create default keychain");
snej@0
   135
                SecKeychainSetDomainDefault(kSecPreferencesDomainUser, sDefaultKeychain.keychainRef);
snej@0
   136
                Log(@"...created %@", sDefaultKeychain);
snej@0
   137
                return sDefaultKeychain;
snej@0
   138
            }
snej@0
   139
#endif
snej@0
   140
            if (!check(err, @"SecKeychainCopyDefault"))
snej@0
   141
                kc = NULL;
snej@0
   142
snej@0
   143
            Assert(kc, @"No default keychain");
snej@0
   144
            sDefaultKeychain = [[self alloc] initWithKeychainRef: kc];
snej@0
   145
            CFRelease(kc);
snej@0
   146
        }
snej@0
   147
    }
snej@0
   148
    return sDefaultKeychain;
snej@0
   149
}
snej@0
   150
snej@0
   151
snej@0
   152
- (id) copyWithZone: (NSZone*)zone {
snej@0
   153
    // It's not necessary to make copies of Keychain objects. This makes it more efficient
snej@0
   154
    // to use instances as NSDictionary keys or store them in NSSets.
snej@0
   155
    return [self retain];
snej@0
   156
}
snej@0
   157
snej@0
   158
- (BOOL) isEqual: (id)obj {
snej@0
   159
    return (obj == self) || 
snej@0
   160
            ([obj isKindOfClass: [MYKeychain class]] && CFEqual(_keychain, [obj keychainRef]));
snej@0
   161
}
snej@0
   162
snej@0
   163
snej@0
   164
- (SecKeychainRef) keychainRef {
snej@0
   165
    return _keychain;
snej@0
   166
}
snej@0
   167
snej@0
   168
snej@0
   169
- (SecKeychainRef) keychainRefOrDefault {
snej@0
   170
    if (_keychain)
snej@0
   171
        return _keychain;
snej@0
   172
    else
snej@0
   173
        return [[[self class] defaultKeychain] keychainRef];
snej@0
   174
}
snej@0
   175
    
snej@0
   176
    
snej@0
   177
- (NSString*) path {
snej@0
   178
    if (!_keychain)
snej@0
   179
        return nil;
snej@0
   180
    char pathBuf[PATH_MAX];
snej@0
   181
    UInt32 pathLen = sizeof(pathBuf);
snej@0
   182
    if (!check(SecKeychainGetPath(_keychain, &pathLen, pathBuf), @"SecKeychainGetPath"))
snej@0
   183
        return nil;
snej@0
   184
    return [[NSFileManager defaultManager] stringWithFileSystemRepresentation: pathBuf length: pathLen];
snej@0
   185
}
snej@0
   186
snej@0
   187
- (NSString*) description {
snej@0
   188
    if (_keychain)
snej@0
   189
        return $sprintf(@"%@[%p, %@]", [self class], _keychain, self.path);
snej@0
   190
    else
snej@0
   191
        return $sprintf(@"%@[all]", [self class]);
snej@0
   192
}
snej@0
   193
snej@0
   194
snej@0
   195
#pragma mark -
snej@0
   196
#pragma mark SEARCHING:
snej@0
   197
snej@0
   198
snej@0
   199
- (MYKeychainItem*) itemOfClass: (SecItemClass)itemClass 
snej@0
   200
                     withDigest: (MYSHA1Digest*)pubKeyDigest 
snej@0
   201
{
snej@0
   202
    SecKeychainAttribute attr = {.tag= (itemClass==kSecCertificateItemClass ?kSecPublicKeyHashItemAttr :kSecKeyLabel), 
snej@0
   203
                                 .length= pubKeyDigest.length, 
snej@0
   204
                                 .data= (void*) pubKeyDigest.bytes};
snej@0
   205
    MYKeyEnumerator *e = [[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   206
                                                         itemClass: itemClass
snej@0
   207
                                                        attributes: &attr count: 1];
snej@0
   208
    MYKeychainItem *item = e.nextObject;
snej@0
   209
    [e release];
snej@0
   210
    return item;
snej@0
   211
}
snej@0
   212
snej@0
   213
- (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
snej@0
   214
    return (MYPublicKey*) [self itemOfClass: kSecPublicKeyItemClass withDigest: pubKeyDigest];
snej@0
   215
}   
snej@0
   216
snej@0
   217
- (NSEnumerator*) enumeratePublicKeys {
snej@0
   218
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   219
                                            itemClass: kSecPublicKeyItemClass
snej@0
   220
                                           attributes: NULL count: 0] autorelease];
snej@0
   221
}
snej@0
   222
snej@0
   223
- (NSEnumerator*) publicKeysWithAlias: (NSString*)alias {
snej@0
   224
    NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding];
snej@0
   225
    SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes};
snej@0
   226
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   227
                                            itemClass: kSecPublicKeyItemClass
snej@0
   228
                                           attributes: &attr count: 1] autorelease];
snej@0
   229
}
snej@0
   230
snej@0
   231
snej@3
   232
- (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
snej@3
   233
    return (MYPrivateKey*) [self itemOfClass: kSecPrivateKeyItemClass withDigest: pubKeyDigest];
snej@0
   234
}
snej@0
   235
snej@3
   236
- (NSEnumerator*) enumeratePrivateKeys {
snej@0
   237
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   238
                                            itemClass: kSecPrivateKeyItemClass
snej@0
   239
                                           attributes: NULL count: 0] autorelease];
snej@0
   240
}
snej@0
   241
snej@0
   242
- (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest {
snej@0
   243
    return (MYCertificate*) [self itemOfClass: kSecCertificateItemClass withDigest: pubKeyDigest];
snej@0
   244
}
snej@0
   245
snej@0
   246
- (NSEnumerator*) enumerateCertificates {
snej@0
   247
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   248
                                            itemClass: kSecCertificateItemClass
snej@0
   249
                                           attributes: NULL count: 0] autorelease];
snej@0
   250
}
snej@0
   251
snej@0
   252
- (NSEnumerator*) enumerateSymmetricKeys {
snej@0
   253
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   254
                                            itemClass: kSecSymmetricKeyItemClass
snej@0
   255
                                           attributes: NULL count: 0] autorelease];
snej@0
   256
}
snej@0
   257
snej@0
   258
- (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias {
snej@0
   259
    NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding];
snej@0
   260
    SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes};
snej@0
   261
    return [[[MYKeyEnumerator alloc] initWithKeychain: self
snej@0
   262
                                            itemClass: kSecSymmetricKeyItemClass
snej@0
   263
                                           attributes: &attr count: 1] autorelease];
snej@0
   264
}
snej@0
   265
snej@0
   266
snej@0
   267
snej@0
   268
#pragma mark -
snej@0
   269
#pragma mark IMPORT:
snej@0
   270
snej@0
   271
snej@0
   272
- (MYPublicKey*) importPublicKey: (NSData*)keyData {
snej@0
   273
    return [[[MYPublicKey alloc] _initWithKeyData: keyData 
snej@0
   274
                                      forKeychain: self.keychainRefOrDefault]
snej@0
   275
            autorelease];
snej@0
   276
}
snej@0
   277
snej@3
   278
- (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData 
snej@0
   279
                    privateKey: (NSData*)privKeyData 
snej@0
   280
                    alertTitle: (NSString*)title
snej@0
   281
                   alertPrompt: (NSString*)prompt {
snej@3
   282
    return [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
snej@3
   283
                                     publicKeyData: pubKeyData
snej@3
   284
                                       forKeychain: self.keychainRefOrDefault
snej@3
   285
                                        alertTitle: (NSString*)title
snej@3
   286
                                       alertPrompt: (NSString*)prompt]
snej@0
   287
            autorelease];
snej@0
   288
}
snej@0
   289
snej@3
   290
- (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData 
snej@0
   291
                    privateKey: (NSData*)privKeyData 
snej@0
   292
{
snej@0
   293
    return [self importPublicKey: pubKeyData privateKey: privKeyData
snej@0
   294
                      alertTitle: @"Import Private Key"
snej@0
   295
                     alertPrompt: @"To import your saved private key, please re-enter the "
snej@0
   296
                                   "passphrase you used when you exported it."];
snej@0
   297
}
snej@0
   298
snej@0
   299
- (MYCertificate*) importCertificate: (NSData*)data
snej@0
   300
                                type: (CSSM_CERT_TYPE) type
snej@0
   301
                            encoding: (CSSM_CERT_ENCODING) encoding;
snej@0
   302
{
snej@0
   303
    MYCertificate *cert = [[[MYCertificate alloc] initWithCertificateData: data 
snej@0
   304
                                                                    type: type
snej@0
   305
                                                                encoding: encoding]
snej@0
   306
                           autorelease];
snej@0
   307
    if (cert) {
snej@0
   308
        if (!check(SecCertificateAddToKeychain(cert.certificateRef, self.keychainRefOrDefault),
snej@0
   309
                   @"SecCertificateAddToKeychain"))
snej@0
   310
            cert = nil;
snej@0
   311
    }
snej@0
   312
    return cert;
snej@0
   313
}
snej@0
   314
snej@0
   315
- (MYCertificate*) importCertificate: (NSData*)data {
snej@0
   316
    return [self importCertificate: data 
snej@0
   317
                              type: CSSM_CERT_X_509v3 
snej@0
   318
                          encoding: CSSM_CERT_ENCODING_BER];
snej@0
   319
}
snej@0
   320
snej@0
   321
snej@0
   322
- (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
snej@0
   323
                                     algorithm: (CCAlgorithm)algorithm
snej@0
   324
{
snej@0
   325
    return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits
snej@0
   326
                                             algorithm: algorithm inKeychain: self];
snej@0
   327
}
snej@0
   328
snej@3
   329
- (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize {
snej@3
   330
    return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self];
snej@0
   331
}
snej@0
   332
snej@0
   333
snej@0
   334
- (CSSM_CSP_HANDLE) CSPHandle {
snej@0
   335
    CSSM_CSP_HANDLE cspHandle = 0;
snej@0
   336
    Assert(check(SecKeychainGetCSPHandle(self.keychainRefOrDefault, &cspHandle), @"SecKeychainGetCSPHandle"));
snej@0
   337
    return cspHandle;
snej@0
   338
}
snej@0
   339
snej@0
   340
snej@0
   341
@end
snej@0
   342
snej@0
   343
snej@0
   344
snej@0
   345
#pragma mark -
snej@0
   346
@implementation MYKeyEnumerator
snej@0
   347
snej@0
   348
- (id) initWithKeychain: (MYKeychain*)keychain
snej@0
   349
              itemClass: (SecItemClass)itemClass
snej@0
   350
             attributes: (SecKeychainAttribute[])attributes 
snej@0
   351
                  count: (unsigned)count {
snej@0
   352
    self = [super init];
snej@0
   353
    if (self) {
snej@0
   354
        _keychain = [keychain retain];
snej@0
   355
        _itemClass = itemClass;
snej@0
   356
        SecKeychainAttributeList list = {.count=count, .attr=attributes};
snej@0
   357
        if (!check(SecKeychainSearchCreateFromAttributes(keychain.keychainRef,
snej@0
   358
                                                         itemClass,
snej@0
   359
                                                         &list,
snej@0
   360
                                                         &_search),
snej@0
   361
                   @"SecKeychainSearchCreateFromAttributes")) {
snej@0
   362
            [self release];
snej@0
   363
            return nil;
snej@0
   364
        }
snej@0
   365
    }
snej@0
   366
    return self;
snej@0
   367
}
snej@0
   368
snej@0
   369
- (void) dealloc
snej@0
   370
{
snej@0
   371
    [_keychain release];
snej@0
   372
    if (_search) CFRelease(_search);
snej@0
   373
    [super dealloc];
snej@0
   374
}
snej@0
   375
snej@2
   376
- (void) finalize
snej@2
   377
{
snej@2
   378
    [_keychain release];
snej@2
   379
    if (_search) CFRelease(_search);
snej@2
   380
    [super finalize];
snej@2
   381
}
snej@2
   382
snej@0
   383
snej@0
   384
- (id) nextObject {
snej@0
   385
    if (!_search)
snej@0
   386
        return nil;
snej@0
   387
    MYPublicKey *key = nil;
snej@0
   388
    do{
snej@0
   389
        SecKeychainItemRef found = NULL;
snej@0
   390
        OSStatus err = SecKeychainSearchCopyNext(_search, &found);
snej@0
   391
        if (err || !found) {
snej@0
   392
            if (err != errSecItemNotFound)
snej@0
   393
                check(err,@"SecKeychainSearchCopyNext");
snej@0
   394
            CFRelease(_search);
snej@0
   395
            _search = NULL;
snej@0
   396
            return nil;
snej@0
   397
        }
snej@0
   398
        
snej@0
   399
        switch (_itemClass) {
snej@0
   400
            case kSecPrivateKeyItemClass: {
snej@3
   401
                key = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found];
snej@0
   402
                break;
snej@0
   403
            }
snej@0
   404
            case kSecCertificateItemClass:
snej@0
   405
                key = [[[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found] autorelease];
snej@0
   406
                break;
snej@0
   407
            case kSecPublicKeyItemClass:
snej@0
   408
                key = [[[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found] autorelease];
snej@0
   409
                break;
snej@0
   410
        }
snej@0
   411
        CFRelease(found);
snej@0
   412
    } while (key==nil);
snej@0
   413
    return key;
snej@0
   414
}
snej@0
   415
snej@0
   416
snej@0
   417
@end
snej@0
   418
snej@0
   419
snej@2
   420
#endif !MYCRYPTO_USE_IPHONE_API
snej@0
   421
snej@0
   422
snej@0
   423
snej@0
   424
/*
snej@0
   425
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@0
   426
 
snej@0
   427
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@0
   428
 provided that the following conditions are met:
snej@0
   429
 
snej@0
   430
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@0
   431
 and the following disclaimer.
snej@0
   432
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@0
   433
 and the following disclaimer in the documentation and/or other materials provided with the
snej@0
   434
 distribution.
snej@0
   435
 
snej@0
   436
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@0
   437
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@0
   438
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@0
   439
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@0
   440
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@0
   441
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@0
   442
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@0
   443
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@0
   444
 */