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