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