snej@0: // snej@0: // MYKeychain-iPhone.m snej@0: // MYCrypto-iPhone snej@0: // snej@0: // Created by Jens Alfke on 3/31/09. snej@0: // Copyright 2009 Jens Alfke. All rights reserved. snej@0: // snej@0: snej@0: #import "MYCrypto_Private.h" snej@0: #import "MYDigest.h" snej@5: #import "MYIdentity.h" snej@5: snej@0: snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: snej@0: jens@26: // from cssmtype.h: jens@26: enum { jens@26: CSSM_CERT_UNKNOWN = 0x00, jens@26: CSSM_CERT_X_509v1 = 0x01, jens@26: CSSM_CERT_X_509v2 = 0x02, jens@26: CSSM_CERT_X_509v3 = 0x03, jens@26: jens@26: CSSM_CERT_ENCODING_UNKNOWN = 0x00, jens@26: CSSM_CERT_ENCODING_CUSTOM = 0x01, jens@26: CSSM_CERT_ENCODING_BER = 0x02, jens@26: CSSM_CERT_ENCODING_DER = 0x03, jens@26: }; jens@26: jens@26: snej@0: @interface MYKeyEnumerator : NSEnumerator snej@0: { snej@0: CFArrayRef _results; snej@0: CFTypeRef _itemClass; snej@0: CFIndex _index; jens@23: MYKeychainItem *_currentObject; snej@0: } snej@0: snej@0: - (id) initWithQuery: (NSMutableDictionary*)query; snej@0: + (id) firstItemWithQuery: (NSMutableDictionary*)query; snej@0: @end snej@0: snej@0: snej@0: snej@0: @implementation MYKeychain snej@0: snej@0: snej@0: + (MYKeychain*) allKeychains snej@0: { snej@0: // iPhone only has a single keychain. snej@0: return [self defaultKeychain]; snej@0: } snej@0: snej@0: + (MYKeychain*) defaultKeychain snej@0: { snej@0: static MYKeychain *sDefaultKeychain; snej@0: @synchronized(self) { snej@0: if (!sDefaultKeychain) { snej@0: sDefaultKeychain = [[self alloc] init]; snej@0: } snej@0: } snej@0: return sDefaultKeychain; snej@0: } snej@0: snej@0: snej@0: - (id) copyWithZone: (NSZone*)zone { snej@0: // It's not necessary to make copies of Keychain objects. This makes it more efficient snej@0: // to use instances as NSDictionary keys or store them in NSSets. snej@0: return [self retain]; snej@0: } snej@0: snej@0: snej@0: snej@0: #pragma mark - snej@0: #pragma mark SEARCHING: snej@0: snej@0: snej@0: - (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest { snej@0: return [MYKeyEnumerator firstItemWithQuery: jens@23: $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPublic}, jens@23: {(id)kSecAttrApplicationLabel, pubKeyDigest.asData})]; snej@0: } snej@0: snej@0: - (NSEnumerator*) enumeratePublicKeys { jens@23: NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPublic}); snej@0: return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease]; snej@0: } snej@0: snej@0: snej@3: - (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest { snej@0: return [MYKeyEnumerator firstItemWithQuery: jens@23: $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPrivate}, jens@23: {(id)kSecAttrApplicationLabel, pubKeyDigest.asData})]; snej@0: } snej@0: snej@3: - (NSEnumerator*) enumeratePrivateKeys { jens@23: NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPrivate}); snej@0: return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease]; snej@0: } snej@0: snej@0: - (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest { snej@0: return [MYKeyEnumerator firstItemWithQuery: snej@0: $mdict({(id)kSecClass, (id)kSecClassCertificate}, jens@23: {(id)kSecAttrPublicKeyHash, pubKeyDigest.asData})]; snej@0: } snej@0: snej@0: - (NSEnumerator*) enumerateCertificates { jens@23: NSMutableDictionary *query = $mdict({(id)kSecClass, (id)kSecClassCertificate}); snej@0: return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease]; snej@0: } snej@0: jens@21: - (MYIdentity*) identityWithDigest: (MYSHA1Digest*)pubKeyDigest { jens@21: return [MYKeyEnumerator firstItemWithQuery: jens@21: $mdict({(id)kSecClass, (id)kSecClassIdentity}, jens@26: {(id)kSecAttrApplicationLabel/*kSecAttrPublicKeyHash*/, pubKeyDigest.asData})]; jens@21: } jens@21: snej@5: - (NSEnumerator*) enumerateIdentities { jens@23: NSMutableDictionary *query = $mdict({(id)kSecClass, (id)kSecClassIdentity}); snej@5: return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease]; snej@5: } snej@5: snej@0: - (NSEnumerator*) enumerateSymmetricKeys { jens@23: NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric}); snej@0: return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease]; snej@0: } snej@0: snej@0: - (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias { jens@23: NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric}, jens@23: {(id)kSecAttrApplicationTag, alias}); snej@0: return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease]; snej@0: } snej@0: snej@0: snej@0: #pragma mark - snej@0: #pragma mark IMPORT: snej@0: snej@0: jens@26: + (CFTypeRef) _addItemWithInfo: (NSMutableDictionary*)info { jens@26: // Generally SecItemAdd will fail (return paramErr) if asked to return a regular ref. jens@26: // As a workaround ask for a persistent ref instead, then convert that to regular ref. jens@26: if (![[info objectForKey: (id)kSecReturnRef] boolValue]) jens@26: [info setObject: $true forKey: (id)kSecReturnPersistentRef]; jens@26: jens@26: CFDataRef itemPersistentRef; jens@26: CFTypeRef item; jens@26: OSStatus err = SecItemAdd((CFDictionaryRef)info, (CFTypeRef*)&itemPersistentRef); jens@26: if (err==errSecDuplicateItem) { jens@26: Log(@"_addItemWithInfo: Keychain claims it's a dup, so look for existing item"); jens@26: // it's already in the keychain -- get a reference to it: jens@26: [info removeObjectForKey: (id)kSecReturnPersistentRef]; jens@26: [info setObject: $true forKey: (id)kSecReturnRef]; jens@26: if (check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef *)&item), jens@26: @"SecItemCopyMatching")) { jens@26: if (!item) jens@26: Warn(@"_addItemWithInfo: Couldn't find supposedly-duplicate item, info=%@",info); jens@26: Log(@"_addItemWithInfo: SecItemAdd found item; ref=%@", item);//TEMP jens@26: return item; jens@26: } jens@26: } else if (check(err, @"SecItemAdd")) { jens@26: // It was added jens@26: if ([[info objectForKey: (id)kSecReturnPersistentRef] boolValue]) { jens@26: // now get its item ref: jens@26: Log(@"SecItemAdd added item; persistenRef=%@", itemPersistentRef);//TEMP jens@26: info = $mdict({(id)kSecValuePersistentRef, (id)itemPersistentRef}, jens@26: {(id)kSecReturnRef, $true}); jens@26: err = SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef *)&item); jens@26: CFRelease(itemPersistentRef); jens@26: if (check(err,@"SecItemCopyMatching")) { jens@26: Assert(item!=nil); jens@26: return item; jens@26: } jens@26: } else { jens@26: Log(@"SecItemAdd added item; ref=%@", itemPersistentRef);//TEMP jens@26: return (CFTypeRef)itemPersistentRef; jens@26: } jens@26: } jens@26: Log(@"SecItemAdd failed: info = %@", info); // for help in debugging, dump the input dict jens@26: return NULL; jens@26: } jens@26: jens@26: snej@0: - (MYPublicKey*) importPublicKey: (NSData*)keyData { snej@0: return [[[MYPublicKey alloc] _initWithKeyData: keyData snej@0: forKeychain: self] snej@0: autorelease]; snej@0: } snej@0: snej@0: - (MYCertificate*) importCertificate: (NSData*)data snej@0: { snej@0: Assert(data); jens@26: jens@26: #if 1 jens@26: SecCertificateRef cert0 = SecCertificateCreateWithData(NULL, (CFDataRef)data); jens@26: if (!cert0) snej@0: return nil; jens@26: NSMutableDictionary *info = $mdict( {(id)kSecClass, (id)kSecClassCertificate}, jens@26: {(id)kSecValueRef, (id)cert0}); jens@26: #else jens@26: NSMutableDictionary *info = $mdict( {(id)kSecClass, (id)kSecClassCertificate}, jens@26: {(id)kSecAttrCertificateType, $object(CSSM_CERT_X_509v3)}, jens@26: {(id)kSecAttrCertificateEncoding, $object(CSSM_CERT_ENCODING_BER)}, jens@26: {(id)kSecValueData, data} ); jens@26: #endif jens@26: SecCertificateRef cert = (SecCertificateRef) [[self class] _addItemWithInfo: info]; jens@26: if (!cert) jens@26: return nil; jens@26: MYCertificate *myCert = [[[MYCertificate alloc] initWithCertificateRef: cert] autorelease]; jens@26: AssertEqual(data, myCert.certificateData); //TEMP for debugging jens@26: return myCert; snej@0: } snej@0: snej@0: snej@0: #pragma mark - snej@0: #pragma mark GENERATION: snej@0: snej@0: snej@0: - (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits snej@0: algorithm: (CCAlgorithm)algorithm snej@0: { snej@0: return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits snej@0: algorithm: algorithm inKeychain: self]; snej@0: } snej@0: snej@3: - (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize { snej@3: return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self]; snej@0: } snej@0: snej@0: jens@26: #pragma mark - jens@26: #pragma mark REMOVING: jens@26: jens@26: jens@26: - (BOOL) removeAllCertificates { jens@26: NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassCertificate}); jens@26: return check(SecItemDelete((CFDictionaryRef)query), @"SecItemDelete"); jens@26: } jens@26: jens@26: - (BOOL) removeAllKeys { jens@26: NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey}); jens@26: return check(SecItemDelete((CFDictionaryRef)query), @"SecItemDelete"); jens@26: } jens@26: jens@26: snej@0: @end snej@0: snej@0: snej@0: snej@0: #pragma mark - snej@0: @implementation MYKeyEnumerator snej@0: snej@0: - (id) initWithQuery: (NSMutableDictionary*)query { snej@0: self = [super init]; snej@0: if (self) { jens@23: _itemClass = (CFTypeRef)[query objectForKey: (id)kSecAttrKeyClass]; jens@23: if (_itemClass) jens@23: [query setObject: (id)kSecClassKey forKey: (id)kSecClass]; jens@23: else jens@23: _itemClass = (CFTypeRef)[query objectForKey: (id)kSecClass]; jens@23: Assert(_itemClass); jens@23: CFRetain(_itemClass); jens@23: jens@23: // Ask for all results unless caller specified fewer: jens@23: CFTypeRef limit = [query objectForKey: (id)kSecMatchLimit]; jens@23: if (! limit) { jens@23: limit = kSecMatchLimitAll; jens@23: [query setObject: (id)limit forKey: (id)kSecMatchLimit]; jens@23: } jens@23: jens@23: [query setObject: $true forKey: (id)kSecReturnRef]; jens@23: snej@0: OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)&_results); snej@0: if (err && err != errSecItemNotFound) { snej@0: check(err,@"SecItemCopyMatching"); snej@0: [self release]; snej@0: return nil; snej@0: } jens@26: //Log(@"Enumerator results = %@", _results); jens@23: jens@23: if (_results && CFEqual(limit,kSecMatchLimitOne)) { jens@23: // If you ask for only one, it gives you the object back instead of an array: jens@23: CFArrayRef resultsArray = CFArrayCreate(NULL, (const void**)&_results, 1, jens@23: &kCFTypeArrayCallBacks); jens@23: CFRelease(_results); jens@23: _results = resultsArray; jens@23: } snej@0: } snej@0: return self; snej@0: } snej@0: snej@0: + (id) firstItemWithQuery: (NSMutableDictionary*)query { jens@23: [query setObject: (id)kSecMatchLimitOne forKey: (id)kSecMatchLimit]; snej@0: MYKeyEnumerator *e = [[self alloc] initWithQuery: query]; jens@23: MYKeychainItem *item = [e.nextObject retain]; snej@0: [e release]; jens@23: return [item autorelease]; snej@0: } snej@0: snej@0: - (void) dealloc snej@0: { jens@23: [_currentObject release]; jens@23: CFRelease(_itemClass); snej@0: if (_results) CFRelease(_results); snej@0: [super dealloc]; snej@0: } snej@0: snej@0: jens@24: - (BOOL) _verifyPublicKeyRef: (MYKeychainItemRef)itemRef { jens@24: // Enumerating the keychain sometimes returns public-key refs that give not-found errors jens@24: // when you try to use them for anything. As a workaround, detect these early on before jens@24: // even creating a MYPublicKey: jens@24: NSDictionary *info = $dict({(id)kSecValueRef, (id)itemRef}, jens@26: {(id)kSecReturnAttributes, $true}); jens@24: CFDictionaryRef attrs = NULL; jens@24: OSStatus err = SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs); jens@24: if (attrs) CFRelease(attrs); jens@24: if (err == errSecItemNotFound) { jens@24: Log(@"MYKeyEnumerator: Ignoring bogus(?) key with ref %p", itemRef); jens@24: return NO; jens@24: } else jens@24: return YES; jens@24: } jens@24: snej@0: - (id) nextObject { snej@0: if (!_results) snej@0: return nil; jens@23: setObj(&_currentObject,nil); jens@23: while (_currentObject==nil && _index < CFArrayGetCount(_results)) { snej@2: CFTypeRef found = CFArrayGetValueAtIndex(_results, _index++); snej@0: if (_itemClass == kSecAttrKeyClassPrivate) { jens@23: _currentObject = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found]; snej@0: } else if (_itemClass == kSecAttrKeyClassPublic) { jens@24: if ([self _verifyPublicKeyRef: found]) jens@24: _currentObject = [[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found]; snej@0: } else if (_itemClass == kSecAttrKeyClassSymmetric) { jens@23: _currentObject = [[MYSymmetricKey alloc] initWithKeyRef: (SecKeyRef)found]; snej@0: } else if (_itemClass == kSecClassCertificate) { jens@23: _currentObject = [[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found]; snej@5: } else if (_itemClass == kSecClassIdentity) { jens@23: _currentObject = [[MYIdentity alloc] initWithIdentityRef: (SecIdentityRef)found]; jens@23: } else { jens@23: Assert(NO,@"Unknown _itemClass: %@",_itemClass); snej@0: } snej@0: } jens@23: return _currentObject; snej@0: } snej@0: snej@0: snej@0: @end snej@0: snej@2: #endif MYCRYPTO_USE_IPHONE_API snej@0: snej@0: snej@0: /* snej@0: Copyright (c) 2009, Jens Alfke . All rights reserved. snej@0: snej@0: Redistribution and use in source and binary forms, with or without modification, are permitted snej@0: provided that the following conditions are met: snej@0: snej@0: * Redistributions of source code must retain the above copyright notice, this list of conditions snej@0: and the following disclaimer. snej@0: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions snej@0: and the following disclaimer in the documentation and/or other materials provided with the snej@0: distribution. snej@0: snej@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR snej@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND snej@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- snej@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES snej@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR snej@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN snej@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF snej@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. snej@0: */