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: 
snej@4: - (id) initWithKeychain: (MYKeychain*)keychain;
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: 
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 {
snej@4:     return [[[MYIdentityEnumerator alloc] initWithKeychain: self] 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: 
snej@4: - (id) initWithKeychain: (MYKeychain*)keychain {
snej@4:     self = [super init];
snej@4:     if (self) {
snej@4:         if (!check(SecIdentitySearchCreate(keychain.keychainRef, 0, &_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 <jens@mooseyard.com>. 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:  */