snej@0: // snej@0: // MYKeychain.m snej@0: // MYCrypto snej@0: // snej@0: // Created by Jens Alfke on 3/23/09. snej@0: // Copyright 2009 Jens Alfke. All rights reserved. snej@0: // snej@0: snej@0: #import "MYKeychain.h" snej@0: #import "MYCrypto_Private.h" snej@0: #import "MYDigest.h" snej@4: #import "MYIdentity.h" snej@4: snej@0: snej@2: #if !MYCRYPTO_USE_IPHONE_API snej@0: snej@0: snej@0: @interface MYKeyEnumerator : NSEnumerator snej@0: { snej@4: @private snej@0: MYKeychain *_keychain; snej@0: SecKeychainSearchRef _search; snej@0: SecItemClass _itemClass; snej@0: } snej@0: snej@0: - (id) initWithKeychain: (MYKeychain*)keychain snej@0: itemClass: (SecItemClass)itemClass snej@0: attributes: (SecKeychainAttribute[])attributes snej@0: count: (unsigned)count; snej@0: @end snej@0: snej@0: snej@4: @interface MYIdentityEnumerator : NSEnumerator snej@4: { snej@4: @private snej@4: SecIdentitySearchRef _searchRef; snej@4: } snej@4: jens@26: - (id) initWithKeychain: (MYKeychain*)keychain keyUsage: (CSSM_KEYUSE)keyUsage; snej@4: @end snej@4: snej@4: snej@4: snej@0: snej@0: @implementation MYKeychain snej@0: snej@0: snej@0: - (id) initWithKeychainRef: (SecKeychainRef)keychainRef snej@0: { snej@0: self = [super init]; snej@0: if (self != nil) { snej@0: if (keychainRef) { snej@0: CFRetain(keychainRef); snej@0: _keychain = keychainRef; snej@0: } snej@0: } snej@0: return self; snej@0: } snej@0: snej@0: + (MYKeychain*) _readableKeychainWithRef: (SecKeychainRef)keychainRef fromPath: (NSString*)path { snej@0: if (!keychainRef) snej@0: return nil; snej@0: SecKeychainStatus status; snej@0: BOOL ok = check(SecKeychainGetStatus(keychainRef, &status), @"SecKeychainGetStatus"); snej@0: if (ok && !(status & kSecReadPermStatus)) { snej@0: Warn(@"Can't open keychain at %@ : not readable (status=%i)", path,status); snej@0: ok = NO; snej@0: } snej@0: MYKeychain *keychain = nil; snej@0: if (ok) snej@0: keychain = [[[self alloc] initWithKeychainRef: keychainRef] autorelease]; snej@0: CFRelease(keychainRef); snej@0: return keychain; snej@0: } snej@0: snej@0: + (MYKeychain*) openKeychainAtPath: (NSString*)path snej@0: { snej@0: Assert(path); snej@0: SecKeychainRef keychainRef = NULL; snej@0: if (!check(SecKeychainOpen(path.fileSystemRepresentation, &keychainRef), @"SecKeychainOpen")) snej@0: return nil; snej@0: return [self _readableKeychainWithRef: keychainRef fromPath: path]; snej@0: } snej@0: snej@0: + (MYKeychain*) createKeychainAtPath: (NSString*)path snej@0: withPassword: (NSString*)password snej@0: { snej@0: Assert(path); snej@0: const char *passwordStr = [password UTF8String]; snej@0: SecKeychainRef keychainRef = NULL; snej@0: if (!check(SecKeychainCreate(path.fileSystemRepresentation, snej@0: passwordStr ?strlen(passwordStr) :0, snej@0: passwordStr, snej@0: (password==nil), snej@0: NULL, snej@0: &keychainRef), snej@0: @"SecKeychainCreate")) snej@0: return nil; snej@0: return [self _readableKeychainWithRef: keychainRef fromPath: path]; snej@0: } snej@0: snej@0: - (BOOL) deleteKeychainFile { snej@0: Assert(_keychain); snej@0: return check(SecKeychainDelete(_keychain), @"SecKeychainDelete"); snej@0: } snej@0: snej@0: snej@0: - (void) dealloc snej@0: { snej@0: if (_keychain) CFRelease(_keychain); snej@0: [super dealloc]; snej@0: } snej@0: snej@2: - (void) finalize snej@2: { snej@2: if (_keychain) CFRelease(_keychain); snej@2: [super finalize]; snej@2: } snej@2: snej@0: snej@0: + (MYKeychain*) allKeychains snej@0: { snej@0: static MYKeychain *sAllKeychains; snej@0: @synchronized(self) { snej@0: if (!sAllKeychains) snej@0: sAllKeychains = [[self alloc] initWithKeychainRef: nil]; snej@0: } snej@0: return sAllKeychains; snej@0: } 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: SecKeychainRef kc = NULL; snej@0: OSStatus err = SecKeychainCopyDomainDefault(kSecPreferencesDomainUser,&kc); snej@0: #if TARGET_OS_IPHONE snej@0: // In the simulator, an app is run in a sandbox that has no keychain by default. snej@0: // As a convenience, create one if necessary: snej@0: if (err == errSecNoDefaultKeychain) { snej@0: Log(@"No default keychain in simulator; creating one..."); snej@0: NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, snej@0: NSUserDomainMask, YES) objectAtIndex: 0]; snej@0: path = [path stringByAppendingPathComponent: @"MYCrypto.keychain"]; snej@0: sDefaultKeychain = [[self createKeychainAtPath: path withPassword: nil] retain]; snej@0: Assert(sDefaultKeychain, @"Couldn't create default keychain"); snej@0: SecKeychainSetDomainDefault(kSecPreferencesDomainUser, sDefaultKeychain.keychainRef); snej@0: Log(@"...created %@", sDefaultKeychain); snej@0: return sDefaultKeychain; snej@0: } snej@0: #endif snej@0: if (!check(err, @"SecKeychainCopyDefault")) snej@0: kc = NULL; snej@0: snej@0: Assert(kc, @"No default keychain"); snej@0: sDefaultKeychain = [[self alloc] initWithKeychainRef: kc]; snej@0: CFRelease(kc); 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: - (BOOL) isEqual: (id)obj { snej@0: return (obj == self) || snej@0: ([obj isKindOfClass: [MYKeychain class]] && CFEqual(_keychain, [obj keychainRef])); snej@0: } snej@0: snej@0: snej@0: - (SecKeychainRef) keychainRef { snej@0: return _keychain; snej@0: } snej@0: snej@0: snej@0: - (SecKeychainRef) keychainRefOrDefault { snej@0: if (_keychain) snej@0: return _keychain; snej@0: else snej@0: return [[[self class] defaultKeychain] keychainRef]; snej@0: } snej@0: snej@0: snej@0: - (NSString*) path { snej@0: if (!_keychain) snej@0: return nil; snej@0: char pathBuf[PATH_MAX]; snej@0: UInt32 pathLen = sizeof(pathBuf); snej@0: if (!check(SecKeychainGetPath(_keychain, &pathLen, pathBuf), @"SecKeychainGetPath")) snej@0: return nil; snej@0: return [[NSFileManager defaultManager] stringWithFileSystemRepresentation: pathBuf length: pathLen]; snej@0: } snej@0: snej@0: - (NSString*) description { snej@0: if (_keychain) snej@0: return $sprintf(@"%@[%p, %@]", [self class], _keychain, self.path); snej@0: else snej@0: return $sprintf(@"%@[all]", [self class]); snej@0: } snej@0: snej@0: jens@16: + (void) setUserInteractionAllowed: (BOOL)allowed { jens@16: SecKeychainSetUserInteractionAllowed(allowed); jens@16: } jens@16: jens@16: snej@0: #pragma mark - snej@0: #pragma mark SEARCHING: snej@0: snej@0: snej@0: - (MYKeychainItem*) itemOfClass: (SecItemClass)itemClass snej@0: withDigest: (MYSHA1Digest*)pubKeyDigest snej@0: { snej@0: SecKeychainAttribute attr = {.tag= (itemClass==kSecCertificateItemClass ?kSecPublicKeyHashItemAttr :kSecKeyLabel), snej@0: .length= pubKeyDigest.length, snej@0: .data= (void*) pubKeyDigest.bytes}; snej@0: MYKeyEnumerator *e = [[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: itemClass snej@0: attributes: &attr count: 1]; snej@0: MYKeychainItem *item = e.nextObject; snej@0: [e release]; snej@0: return item; snej@0: } snej@0: snej@0: - (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest { snej@0: return (MYPublicKey*) [self itemOfClass: kSecPublicKeyItemClass withDigest: pubKeyDigest]; snej@0: } snej@0: snej@0: - (NSEnumerator*) enumeratePublicKeys { snej@0: return [[[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: kSecPublicKeyItemClass snej@0: attributes: NULL count: 0] autorelease]; snej@0: } snej@0: snej@0: - (NSEnumerator*) publicKeysWithAlias: (NSString*)alias { snej@0: NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding]; snej@0: SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes}; snej@0: return [[[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: kSecPublicKeyItemClass snej@0: attributes: &attr count: 1] autorelease]; snej@0: } snej@0: snej@0: snej@3: - (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest { snej@3: return (MYPrivateKey*) [self itemOfClass: kSecPrivateKeyItemClass withDigest: pubKeyDigest]; snej@0: } snej@0: snej@3: - (NSEnumerator*) enumeratePrivateKeys { snej@0: return [[[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: kSecPrivateKeyItemClass snej@0: attributes: NULL count: 0] autorelease]; snej@0: } snej@0: snej@0: - (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest { snej@0: return (MYCertificate*) [self itemOfClass: kSecCertificateItemClass withDigest: pubKeyDigest]; snej@0: } snej@0: snej@0: - (NSEnumerator*) enumerateCertificates { snej@0: return [[[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: kSecCertificateItemClass snej@0: attributes: NULL count: 0] autorelease]; snej@0: } snej@0: snej@4: - (NSEnumerator*) enumerateIdentities { jens@26: return [self enumerateIdentitiesWithKeyUsage: 0]; jens@26: } jens@26: jens@26: - (NSEnumerator*) enumerateIdentitiesWithKeyUsage: (CSSM_KEYUSE)keyUsage { jens@26: return [[[MYIdentityEnumerator alloc] initWithKeychain: self keyUsage: keyUsage] autorelease]; snej@4: } snej@4: snej@0: - (NSEnumerator*) enumerateSymmetricKeys { snej@0: return [[[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: kSecSymmetricKeyItemClass snej@0: attributes: NULL count: 0] autorelease]; snej@0: } snej@0: snej@0: - (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias { snej@0: NSData *utf8 = [alias dataUsingEncoding: NSUTF8StringEncoding]; snej@0: SecKeychainAttribute attr = {.tag=kSecKeyAlias, .length=utf8.length, .data=(void*)utf8.bytes}; snej@0: return [[[MYKeyEnumerator alloc] initWithKeychain: self snej@0: itemClass: kSecSymmetricKeyItemClass snej@0: attributes: &attr count: 1] autorelease]; snej@0: } snej@0: snej@0: snej@0: snej@0: #pragma mark - snej@0: #pragma mark IMPORT: snej@0: snej@0: snej@0: - (MYPublicKey*) importPublicKey: (NSData*)keyData { snej@0: return [[[MYPublicKey alloc] _initWithKeyData: keyData snej@0: forKeychain: self.keychainRefOrDefault] snej@0: autorelease]; snej@0: } snej@0: snej@3: - (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData snej@0: privateKey: (NSData*)privKeyData snej@0: alertTitle: (NSString*)title snej@0: alertPrompt: (NSString*)prompt { snej@3: return [[[MYPrivateKey alloc] _initWithKeyData: privKeyData snej@3: publicKeyData: pubKeyData snej@3: forKeychain: self.keychainRefOrDefault snej@3: alertTitle: (NSString*)title snej@3: alertPrompt: (NSString*)prompt] snej@0: autorelease]; snej@0: } snej@0: snej@3: - (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData snej@0: privateKey: (NSData*)privKeyData snej@0: { snej@0: return [self importPublicKey: pubKeyData privateKey: privKeyData snej@0: alertTitle: @"Import Private Key" snej@0: alertPrompt: @"To import your saved private key, please re-enter the " snej@0: "passphrase you used when you exported it."]; snej@0: } snej@0: snej@0: - (MYCertificate*) importCertificate: (NSData*)data snej@0: type: (CSSM_CERT_TYPE) type snej@0: encoding: (CSSM_CERT_ENCODING) encoding; snej@0: { snej@0: MYCertificate *cert = [[[MYCertificate alloc] initWithCertificateData: data snej@0: type: type snej@0: encoding: encoding] snej@0: autorelease]; snej@0: if (cert) { snej@0: if (!check(SecCertificateAddToKeychain(cert.certificateRef, self.keychainRefOrDefault), snej@0: @"SecCertificateAddToKeychain")) snej@0: cert = nil; snej@0: } snej@0: return cert; snej@0: } snej@0: snej@0: - (MYCertificate*) importCertificate: (NSData*)data { snej@0: return [self importCertificate: data snej@0: type: CSSM_CERT_X_509v3 snej@0: encoding: CSSM_CERT_ENCODING_BER]; snej@0: } snej@0: snej@4: - (BOOL) addCertificate: (MYCertificate*)certificate { snej@4: Assert(certificate); snej@4: return check(SecCertificateAddToKeychain(certificate.certificateRef, self.keychainRefOrDefault), snej@4: @"SecCertificateAddToKeychain"); snej@4: } snej@4: 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: snej@0: - (CSSM_CSP_HANDLE) CSPHandle { snej@0: CSSM_CSP_HANDLE cspHandle = 0; snej@0: Assert(check(SecKeychainGetCSPHandle(self.keychainRefOrDefault, &cspHandle), @"SecKeychainGetCSPHandle")); snej@0: return cspHandle; snej@0: } snej@0: snej@0: snej@0: @end snej@0: snej@0: snej@0: snej@0: #pragma mark - snej@0: @implementation MYKeyEnumerator snej@0: snej@0: - (id) initWithKeychain: (MYKeychain*)keychain snej@0: itemClass: (SecItemClass)itemClass snej@0: attributes: (SecKeychainAttribute[])attributes snej@0: count: (unsigned)count { snej@0: self = [super init]; snej@0: if (self) { snej@0: _keychain = [keychain retain]; snej@0: _itemClass = itemClass; snej@0: SecKeychainAttributeList list = {.count=count, .attr=attributes}; snej@0: if (!check(SecKeychainSearchCreateFromAttributes(keychain.keychainRef, snej@0: itemClass, snej@0: &list, snej@0: &_search), snej@0: @"SecKeychainSearchCreateFromAttributes")) { snej@0: [self release]; snej@0: return nil; snej@0: } snej@0: } snej@0: return self; snej@0: } snej@0: snej@0: - (void) dealloc snej@0: { snej@0: [_keychain release]; snej@0: if (_search) CFRelease(_search); snej@0: [super dealloc]; snej@0: } snej@0: snej@2: - (void) finalize snej@2: { snej@2: [_keychain release]; snej@2: if (_search) CFRelease(_search); snej@2: [super finalize]; snej@2: } snej@2: snej@0: snej@0: - (id) nextObject { snej@0: if (!_search) snej@0: return nil; snej@0: MYPublicKey *key = nil; snej@0: do{ snej@0: SecKeychainItemRef found = NULL; snej@0: OSStatus err = SecKeychainSearchCopyNext(_search, &found); snej@0: if (err || !found) { snej@0: if (err != errSecItemNotFound) snej@0: check(err,@"SecKeychainSearchCopyNext"); snej@0: CFRelease(_search); snej@0: _search = NULL; snej@0: return nil; snej@0: } snej@0: snej@0: switch (_itemClass) { snej@0: case kSecPrivateKeyItemClass: { snej@3: key = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found]; snej@0: break; snej@0: } snej@0: case kSecCertificateItemClass: snej@0: key = [[[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found] autorelease]; snej@0: break; snej@0: case kSecPublicKeyItemClass: snej@0: key = [[[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found] autorelease]; snej@0: break; snej@0: } snej@0: CFRelease(found); snej@0: } while (key==nil); snej@0: return key; snej@0: } snej@0: snej@4: @end snej@4: snej@4: snej@4: snej@4: @implementation MYIdentityEnumerator snej@4: jens@26: - (id) initWithKeychain: (MYKeychain*)keychain keyUsage: (CSSM_KEYUSE)keyUsage { snej@4: self = [super init]; snej@4: if (self) { jens@26: if (!check(SecIdentitySearchCreate(keychain.keychainRef, keyUsage, &_searchRef), snej@4: @"SecIdentitySearchCreate")) { snej@4: [self release]; snej@4: return nil; snej@4: } snej@4: } snej@4: return self; snej@4: } snej@4: snej@4: - (id) nextObject { snej@4: SecIdentityRef identityRef = NULL; snej@4: OSStatus err = SecIdentitySearchCopyNext(_searchRef, &identityRef); snej@4: if (err==errKCItemNotFound || !check(err, @"SecIdentitySearchCopyNext")) snej@4: return nil; snej@4: return [[[MYIdentity alloc] initWithIdentityRef: identityRef] autorelease]; snej@4: } snej@0: snej@0: @end snej@0: snej@0: snej@2: #endif !MYCRYPTO_USE_IPHONE_API snej@0: 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: */