MYKeychain.m
author Jens Alfke <jens@mooseyard.com>
Sun Jun 07 21:53:56 2009 -0700 (2009-06-07)
changeset 23 39fec79de6e8
parent 4 f4709533c816
child 26 d9c2a06d4e4e
permissions -rw-r--r--
A snapshot taken during the long, agonizing crawl toward getting everything running on iPhone.
     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 + (void) setUserInteractionAllowed: (BOOL)allowed {
   210     SecKeychainSetUserInteractionAllowed(allowed);
   211 }
   212 
   213 
   214 #pragma mark -
   215 #pragma mark SEARCHING:
   216 
   217 
   218 - (MYKeychainItem*) itemOfClass: (SecItemClass)itemClass 
   219                      withDigest: (MYSHA1Digest*)pubKeyDigest 
   220 {
   221     SecKeychainAttribute attr = {.tag= (itemClass==kSecCertificateItemClass ?kSecPublicKeyHashItemAttr :kSecKeyLabel), 
   222                                  .length= pubKeyDigest.length, 
   223                                  .data= (void*) pubKeyDigest.bytes};
   224     MYKeyEnumerator *e = [[MYKeyEnumerator alloc] initWithKeychain: self
   225                                                          itemClass: itemClass
   226                                                         attributes: &attr count: 1];
   227     MYKeychainItem *item = e.nextObject;
   228     [e release];
   229     return item;
   230 }
   231 
   232 - (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
   233     return (MYPublicKey*) [self itemOfClass: kSecPublicKeyItemClass withDigest: pubKeyDigest];
   234 }   
   235 
   236 - (NSEnumerator*) enumeratePublicKeys {
   237     return [[[MYKeyEnumerator alloc] initWithKeychain: self
   238                                             itemClass: kSecPublicKeyItemClass
   239                                            attributes: NULL count: 0] autorelease];
   240 }
   241 
   242 - (NSEnumerator*) publicKeysWithAlias: (NSString*)alias {
   243     NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding];
   244     SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes};
   245     return [[[MYKeyEnumerator alloc] initWithKeychain: self
   246                                             itemClass: kSecPublicKeyItemClass
   247                                            attributes: &attr count: 1] autorelease];
   248 }
   249 
   250 
   251 - (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
   252     return (MYPrivateKey*) [self itemOfClass: kSecPrivateKeyItemClass withDigest: pubKeyDigest];
   253 }
   254 
   255 - (NSEnumerator*) enumeratePrivateKeys {
   256     return [[[MYKeyEnumerator alloc] initWithKeychain: self
   257                                             itemClass: kSecPrivateKeyItemClass
   258                                            attributes: NULL count: 0] autorelease];
   259 }
   260 
   261 - (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest {
   262     return (MYCertificate*) [self itemOfClass: kSecCertificateItemClass withDigest: pubKeyDigest];
   263 }
   264 
   265 - (NSEnumerator*) enumerateCertificates {
   266     return [[[MYKeyEnumerator alloc] initWithKeychain: self
   267                                             itemClass: kSecCertificateItemClass
   268                                            attributes: NULL count: 0] autorelease];
   269 }
   270 
   271 - (NSEnumerator*) enumerateIdentities {
   272     return [[[MYIdentityEnumerator alloc] initWithKeychain: self] autorelease];
   273 }
   274 
   275 - (NSEnumerator*) enumerateSymmetricKeys {
   276     return [[[MYKeyEnumerator alloc] initWithKeychain: self
   277                                             itemClass: kSecSymmetricKeyItemClass
   278                                            attributes: NULL count: 0] autorelease];
   279 }
   280 
   281 - (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias {
   282     NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding];
   283     SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes};
   284     return [[[MYKeyEnumerator alloc] initWithKeychain: self
   285                                             itemClass: kSecSymmetricKeyItemClass
   286                                            attributes: &attr count: 1] autorelease];
   287 }
   288 
   289 
   290 
   291 #pragma mark -
   292 #pragma mark IMPORT:
   293 
   294 
   295 - (MYPublicKey*) importPublicKey: (NSData*)keyData {
   296     return [[[MYPublicKey alloc] _initWithKeyData: keyData 
   297                                       forKeychain: self.keychainRefOrDefault]
   298             autorelease];
   299 }
   300 
   301 - (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData 
   302                     privateKey: (NSData*)privKeyData 
   303                     alertTitle: (NSString*)title
   304                    alertPrompt: (NSString*)prompt {
   305     return [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
   306                                      publicKeyData: pubKeyData
   307                                        forKeychain: self.keychainRefOrDefault
   308                                         alertTitle: (NSString*)title
   309                                        alertPrompt: (NSString*)prompt]
   310             autorelease];
   311 }
   312 
   313 - (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData 
   314                     privateKey: (NSData*)privKeyData 
   315 {
   316     return [self importPublicKey: pubKeyData privateKey: privKeyData
   317                       alertTitle: @"Import Private Key"
   318                      alertPrompt: @"To import your saved private key, please re-enter the "
   319                                    "passphrase you used when you exported it."];
   320 }
   321 
   322 - (MYCertificate*) importCertificate: (NSData*)data
   323                                 type: (CSSM_CERT_TYPE) type
   324                             encoding: (CSSM_CERT_ENCODING) encoding;
   325 {
   326     MYCertificate *cert = [[[MYCertificate alloc] initWithCertificateData: data 
   327                                                                     type: type
   328                                                                 encoding: encoding]
   329                            autorelease];
   330     if (cert) {
   331         if (!check(SecCertificateAddToKeychain(cert.certificateRef, self.keychainRefOrDefault),
   332                    @"SecCertificateAddToKeychain"))
   333             cert = nil;
   334     }
   335     return cert;
   336 }
   337 
   338 - (MYCertificate*) importCertificate: (NSData*)data {
   339     return [self importCertificate: data 
   340                               type: CSSM_CERT_X_509v3 
   341                           encoding: CSSM_CERT_ENCODING_BER];
   342 }
   343 
   344 - (BOOL) addCertificate: (MYCertificate*)certificate {
   345     Assert(certificate);
   346     return check(SecCertificateAddToKeychain(certificate.certificateRef, self.keychainRefOrDefault),
   347                  @"SecCertificateAddToKeychain");
   348 }
   349 
   350 
   351 - (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
   352                                      algorithm: (CCAlgorithm)algorithm
   353 {
   354     return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits
   355                                              algorithm: algorithm inKeychain: self];
   356 }
   357 
   358 - (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize {
   359     return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self];
   360 }
   361 
   362 
   363 - (CSSM_CSP_HANDLE) CSPHandle {
   364     CSSM_CSP_HANDLE cspHandle = 0;
   365     Assert(check(SecKeychainGetCSPHandle(self.keychainRefOrDefault, &cspHandle), @"SecKeychainGetCSPHandle"));
   366     return cspHandle;
   367 }
   368 
   369 
   370 @end
   371 
   372 
   373 
   374 #pragma mark -
   375 @implementation MYKeyEnumerator
   376 
   377 - (id) initWithKeychain: (MYKeychain*)keychain
   378               itemClass: (SecItemClass)itemClass
   379              attributes: (SecKeychainAttribute[])attributes 
   380                   count: (unsigned)count {
   381     self = [super init];
   382     if (self) {
   383         _keychain = [keychain retain];
   384         _itemClass = itemClass;
   385         SecKeychainAttributeList list = {.count=count, .attr=attributes};
   386         if (!check(SecKeychainSearchCreateFromAttributes(keychain.keychainRef,
   387                                                          itemClass,
   388                                                          &list,
   389                                                          &_search),
   390                    @"SecKeychainSearchCreateFromAttributes")) {
   391             [self release];
   392             return nil;
   393         }
   394     }
   395     return self;
   396 }
   397 
   398 - (void) dealloc
   399 {
   400     [_keychain release];
   401     if (_search) CFRelease(_search);
   402     [super dealloc];
   403 }
   404 
   405 - (void) finalize
   406 {
   407     [_keychain release];
   408     if (_search) CFRelease(_search);
   409     [super finalize];
   410 }
   411 
   412 
   413 - (id) nextObject {
   414     if (!_search)
   415         return nil;
   416     MYPublicKey *key = nil;
   417     do{
   418         SecKeychainItemRef found = NULL;
   419         OSStatus err = SecKeychainSearchCopyNext(_search, &found);
   420         if (err || !found) {
   421             if (err != errSecItemNotFound)
   422                 check(err,@"SecKeychainSearchCopyNext");
   423             CFRelease(_search);
   424             _search = NULL;
   425             return nil;
   426         }
   427         
   428         switch (_itemClass) {
   429             case kSecPrivateKeyItemClass: {
   430                 key = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found];
   431                 break;
   432             }
   433             case kSecCertificateItemClass:
   434                 key = [[[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found] autorelease];
   435                 break;
   436             case kSecPublicKeyItemClass:
   437                 key = [[[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found] autorelease];
   438                 break;
   439         }
   440         CFRelease(found);
   441     } while (key==nil);
   442     return key;
   443 }
   444 
   445 @end
   446 
   447 
   448 
   449 @implementation MYIdentityEnumerator
   450 
   451 - (id) initWithKeychain: (MYKeychain*)keychain {
   452     self = [super init];
   453     if (self) {
   454         if (!check(SecIdentitySearchCreate(keychain.keychainRef, 0, &_searchRef),
   455                    @"SecIdentitySearchCreate")) {
   456             [self release];
   457             return nil;
   458         }
   459     }
   460     return self;
   461 }
   462 
   463 - (id) nextObject {
   464     SecIdentityRef identityRef = NULL;
   465     OSStatus err = SecIdentitySearchCopyNext(_searchRef, &identityRef);
   466     if (err==errKCItemNotFound || !check(err, @"SecIdentitySearchCopyNext"))
   467         return nil;
   468     return [[[MYIdentity alloc] initWithIdentityRef: identityRef] autorelease];
   469 }
   470 
   471 @end
   472 
   473 
   474 #endif !MYCRYPTO_USE_IPHONE_API
   475 
   476 
   477 
   478 /*
   479  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   480  
   481  Redistribution and use in source and binary forms, with or without modification, are permitted
   482  provided that the following conditions are met:
   483  
   484  * Redistributions of source code must retain the above copyright notice, this list of conditions
   485  and the following disclaimer.
   486  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   487  and the following disclaimer in the documentation and/or other materials provided with the
   488  distribution.
   489  
   490  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   491  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   492  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   493  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   494  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   495   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   496  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   497  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   498  */