MYKeychain-iPhone.m
author Jens Alfke <jens@mooseyard.com>
Fri Aug 07 11:24:53 2009 -0700 (2009-08-07)
changeset 28 54b373aa65ab
parent 24 6856e071d25a
permissions -rw-r--r--
Fixed iPhone OS build. (issue 3)
     1 //
     2 //  MYKeychain-iPhone.m
     3 //  MYCrypto-iPhone
     4 //
     5 //  Created by Jens Alfke on 3/31/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYCrypto_Private.h"
    10 #import "MYDigest.h"
    11 #import "MYIdentity.h"
    12 
    13 
    14 #if MYCRYPTO_USE_IPHONE_API
    15 
    16 
    17 // from cssmtype.h:
    18 enum {
    19     CSSM_CERT_UNKNOWN =					0x00,
    20     CSSM_CERT_X_509v1 =					0x01,
    21     CSSM_CERT_X_509v2 =					0x02,
    22     CSSM_CERT_X_509v3 =					0x03,
    23 
    24     CSSM_CERT_ENCODING_UNKNOWN =		0x00,
    25     CSSM_CERT_ENCODING_CUSTOM =			0x01,
    26     CSSM_CERT_ENCODING_BER =			0x02,
    27     CSSM_CERT_ENCODING_DER =			0x03,
    28 };
    29 
    30 
    31 @interface MYKeyEnumerator : NSEnumerator
    32 {
    33     CFArrayRef _results;
    34     CFTypeRef _itemClass;
    35     CFIndex _index;
    36     MYKeychainItem *_currentObject;
    37 }
    38 
    39 - (id) initWithQuery: (NSMutableDictionary*)query;
    40 + (id) firstItemWithQuery: (NSMutableDictionary*)query;
    41 @end
    42 
    43 
    44 
    45 @implementation MYKeychain
    46 
    47 
    48 + (MYKeychain*) allKeychains
    49 {
    50     // iPhone only has a single keychain.
    51     return [self defaultKeychain];
    52 }
    53 
    54 + (MYKeychain*) defaultKeychain
    55 {
    56     static MYKeychain *sDefaultKeychain;
    57     @synchronized(self) {
    58         if (!sDefaultKeychain) {
    59             sDefaultKeychain = [[self alloc] init];
    60         }
    61     }
    62     return sDefaultKeychain;
    63 }
    64 
    65 
    66 - (id) copyWithZone: (NSZone*)zone {
    67     // It's not necessary to make copies of Keychain objects. This makes it more efficient
    68     // to use instances as NSDictionary keys or store them in NSSets.
    69     return [self retain];
    70 }
    71 
    72 
    73 
    74 #pragma mark -
    75 #pragma mark SEARCHING:
    76 
    77 
    78 - (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
    79     return [MYKeyEnumerator firstItemWithQuery:
    80                 $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPublic},
    81                        {(id)kSecAttrApplicationLabel, pubKeyDigest.asData})];
    82 }   
    83 
    84 - (NSEnumerator*) enumeratePublicKeys {
    85     NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPublic});
    86     return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
    87 }
    88 
    89 
    90 - (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
    91     return [MYKeyEnumerator firstItemWithQuery:
    92                 $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPrivate},
    93                        {(id)kSecAttrApplicationLabel, pubKeyDigest.asData})];
    94 }
    95 
    96 - (NSEnumerator*) enumeratePrivateKeys {
    97     NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPrivate});
    98     return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
    99 }
   100 
   101 - (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest {
   102     return [MYKeyEnumerator firstItemWithQuery:
   103                 $mdict({(id)kSecClass, (id)kSecClassCertificate},
   104                        {(id)kSecAttrPublicKeyHash, pubKeyDigest.asData})];
   105 }
   106 
   107 - (NSEnumerator*) enumerateCertificates {
   108     NSMutableDictionary *query = $mdict({(id)kSecClass, (id)kSecClassCertificate});
   109     return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
   110 }
   111 
   112 - (MYIdentity*) identityWithDigest: (MYSHA1Digest*)pubKeyDigest {
   113     return [MYKeyEnumerator firstItemWithQuery:
   114                 $mdict({(id)kSecClass, (id)kSecClassIdentity},
   115                        {(id)kSecAttrApplicationLabel/*kSecAttrPublicKeyHash*/, pubKeyDigest.asData})];
   116 }
   117 
   118 - (NSEnumerator*) enumerateIdentities {
   119     NSMutableDictionary *query = $mdict({(id)kSecClass, (id)kSecClassIdentity});
   120     return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
   121 }
   122 
   123 - (NSEnumerator*) enumerateSymmetricKeys {
   124     NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric});
   125     return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
   126 }
   127 
   128 - (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias {
   129     NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric},
   130                                         {(id)kSecAttrApplicationTag, alias});
   131     return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
   132 }
   133 
   134 
   135 #pragma mark -
   136 #pragma mark IMPORT:
   137 
   138 
   139 + (CFTypeRef) _addItemWithInfo: (NSMutableDictionary*)info {
   140     // Generally SecItemAdd will fail (return paramErr) if asked to return a regular ref.
   141     // As a workaround ask for a persistent ref instead, then convert that to regular ref.
   142     if (![[info objectForKey: (id)kSecReturnRef] boolValue])
   143         [info setObject: $true forKey: (id)kSecReturnPersistentRef];
   144     
   145     CFDataRef itemPersistentRef;
   146     CFTypeRef item;
   147     OSStatus err = SecItemAdd((CFDictionaryRef)info, (CFTypeRef*)&itemPersistentRef);
   148     if (err==errSecDuplicateItem) {
   149         Log(@"_addItemWithInfo: Keychain claims it's a dup, so look for existing item");
   150         // it's already in the keychain -- get a reference to it:
   151 		[info removeObjectForKey: (id)kSecReturnPersistentRef];
   152 		[info setObject: $true forKey: (id)kSecReturnRef];
   153 		if (check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef *)&item), 
   154                   @"SecItemCopyMatching")) {
   155             if (!item)
   156                 Warn(@"_addItemWithInfo: Couldn't find supposedly-duplicate item, info=%@",info);
   157             Log(@"_addItemWithInfo: SecItemAdd found item; ref=%@", item);//TEMP
   158             return item;
   159         }
   160     } else if (check(err, @"SecItemAdd")) {
   161         // It was added
   162         if ([[info objectForKey: (id)kSecReturnPersistentRef] boolValue]) {
   163             // now get its item ref:
   164             Log(@"SecItemAdd added item; persistenRef=%@", itemPersistentRef);//TEMP
   165             info = $mdict({(id)kSecValuePersistentRef, (id)itemPersistentRef},
   166             {(id)kSecReturnRef, $true});
   167             err = SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef *)&item);
   168             CFRelease(itemPersistentRef);
   169             if (check(err,@"SecItemCopyMatching")) {
   170                 Assert(item!=nil);
   171                 return item;
   172             }
   173         } else {
   174             Log(@"SecItemAdd added item; ref=%@", itemPersistentRef);//TEMP
   175             return (CFTypeRef)itemPersistentRef;
   176         }
   177     }
   178     Log(@"SecItemAdd failed: info = %@", info); // for help in debugging, dump the input dict
   179     return NULL;
   180 }
   181 
   182 
   183 - (MYPublicKey*) importPublicKey: (NSData*)keyData {
   184     return [[[MYPublicKey alloc] _initWithKeyData: keyData 
   185                                       forKeychain: self]
   186             autorelease];
   187 }
   188 
   189 - (MYCertificate*) importCertificate: (NSData*)data
   190 {
   191     Assert(data);
   192     
   193 #if 1
   194     SecCertificateRef cert0 = SecCertificateCreateWithData(NULL, (CFDataRef)data);
   195     if (!cert0)
   196         return nil;
   197     NSMutableDictionary *info = $mdict( {(id)kSecClass, (id)kSecClassCertificate},
   198                                         {(id)kSecValueRef, (id)cert0});
   199 #else
   200     NSMutableDictionary *info = $mdict( {(id)kSecClass, (id)kSecClassCertificate},
   201                                         {(id)kSecAttrCertificateType, $object(CSSM_CERT_X_509v3)},
   202                                         {(id)kSecAttrCertificateEncoding, $object(CSSM_CERT_ENCODING_BER)},
   203                                         {(id)kSecValueData, data} );
   204 #endif
   205     SecCertificateRef cert = (SecCertificateRef) [[self class] _addItemWithInfo: info];
   206     if (!cert)
   207         return nil;
   208     MYCertificate *myCert = [[[MYCertificate alloc] initWithCertificateRef: cert] autorelease];
   209     AssertEqual(data, myCert.certificateData);  //TEMP for debugging
   210     return myCert;
   211 }
   212 
   213 
   214 #pragma mark -
   215 #pragma mark GENERATION:
   216 
   217 
   218 - (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
   219                                      algorithm: (CCAlgorithm)algorithm
   220 {
   221     return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits
   222                                              algorithm: algorithm inKeychain: self];
   223 }
   224 
   225 - (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize {
   226     return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self];
   227 }
   228 
   229 
   230 #pragma mark -
   231 #pragma mark REMOVING:
   232 
   233 
   234 - (BOOL) removeAllCertificates {
   235     NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassCertificate});
   236     return check(SecItemDelete((CFDictionaryRef)query),  @"SecItemDelete");
   237 }
   238 
   239 - (BOOL) removeAllKeys {
   240     NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey});
   241     return check(SecItemDelete((CFDictionaryRef)query), @"SecItemDelete");
   242 }
   243 
   244 
   245 @end
   246 
   247 
   248 
   249 #pragma mark -
   250 @implementation MYKeyEnumerator
   251 
   252 - (id) initWithQuery: (NSMutableDictionary*)query {
   253     self = [super init];
   254     if (self) {
   255         _itemClass = (CFTypeRef)[query objectForKey: (id)kSecAttrKeyClass];
   256         if (_itemClass)
   257             [query setObject: (id)kSecClassKey forKey: (id)kSecClass];
   258         else
   259             _itemClass = (CFTypeRef)[query objectForKey: (id)kSecClass];
   260         Assert(_itemClass);
   261         CFRetain(_itemClass);
   262 
   263         // Ask for all results unless caller specified fewer:
   264         CFTypeRef limit = [query objectForKey: (id)kSecMatchLimit];
   265         if (! limit) {
   266             limit = kSecMatchLimitAll;
   267             [query setObject: (id)limit forKey: (id)kSecMatchLimit];
   268         }
   269         
   270         [query setObject: $true forKey: (id)kSecReturnRef];
   271         
   272         OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)&_results);
   273         if (err && err != errSecItemNotFound) {
   274             check(err,@"SecItemCopyMatching");
   275             [self release];
   276             return nil;
   277         }
   278         //Log(@"Enumerator results = %@", _results);
   279         
   280         if (_results && CFEqual(limit,kSecMatchLimitOne)) {
   281             // If you ask for only one, it gives you the object back instead of an array:
   282             CFArrayRef resultsArray = CFArrayCreate(NULL, (const void**)&_results, 1, 
   283                                                     &kCFTypeArrayCallBacks);
   284             CFRelease(_results);
   285             _results = resultsArray;
   286         }
   287     }
   288     return self;
   289 }
   290 
   291 + (id) firstItemWithQuery: (NSMutableDictionary*)query {
   292     [query setObject: (id)kSecMatchLimitOne forKey: (id)kSecMatchLimit];
   293     MYKeyEnumerator *e = [[self alloc] initWithQuery: query];
   294     MYKeychainItem *item = [e.nextObject retain];
   295     [e release];
   296     return [item autorelease];
   297 }    
   298 
   299 - (void) dealloc
   300 {
   301     [_currentObject release];
   302     CFRelease(_itemClass);
   303     if (_results) CFRelease(_results);
   304     [super dealloc];
   305 }
   306 
   307 
   308 - (BOOL) _verifyPublicKeyRef: (MYKeychainItemRef)itemRef {
   309     // Enumerating the keychain sometimes returns public-key refs that give not-found errors
   310     // when you try to use them for anything. As a workaround, detect these early on before
   311     // even creating a MYPublicKey:
   312     NSDictionary *info = $dict({(id)kSecValueRef, (id)itemRef},
   313                                {(id)kSecReturnAttributes, $true});
   314     CFDictionaryRef attrs = NULL;
   315     OSStatus err = SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs);
   316     if (attrs) CFRelease(attrs);
   317     if (err == errSecItemNotFound) {
   318         Log(@"MYKeyEnumerator: Ignoring bogus(?) key with ref %p", itemRef);
   319         return NO;
   320     } else
   321         return YES;
   322 }        
   323 
   324 - (id) nextObject {
   325     if (!_results)
   326         return nil;
   327     setObj(&_currentObject,nil);
   328     while (_currentObject==nil && _index < CFArrayGetCount(_results)) {
   329         CFTypeRef found = CFArrayGetValueAtIndex(_results, _index++); 
   330         if (_itemClass == kSecAttrKeyClassPrivate) {
   331             _currentObject = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found];
   332         } else if (_itemClass == kSecAttrKeyClassPublic) {
   333             if ([self _verifyPublicKeyRef: found])
   334                 _currentObject = [[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found];
   335         } else if (_itemClass == kSecAttrKeyClassSymmetric) {
   336             _currentObject = [[MYSymmetricKey alloc] initWithKeyRef: (SecKeyRef)found];
   337         } else if (_itemClass == kSecClassCertificate) {
   338             _currentObject = [[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found];
   339         } else if (_itemClass == kSecClassIdentity) {
   340             _currentObject = [[MYIdentity alloc] initWithIdentityRef: (SecIdentityRef)found];
   341         } else  {
   342             Assert(NO,@"Unknown _itemClass: %@",_itemClass);
   343         }
   344     }
   345     return _currentObject;
   346 }
   347 
   348 
   349 @end
   350 
   351 #endif MYCRYPTO_USE_IPHONE_API
   352 
   353 
   354 /*
   355  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   356  
   357  Redistribution and use in source and binary forms, with or without modification, are permitted
   358  provided that the following conditions are met:
   359  
   360  * Redistributions of source code must retain the above copyright notice, this list of conditions
   361  and the following disclaimer.
   362  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   363  and the following disclaimer in the documentation and/or other materials provided with the
   364  distribution.
   365  
   366  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   367  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   368  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   369  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   370  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   371   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   372  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   373  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   374  */