MYCertificate now checks validity of self-signed certs loaded from the keychain (because the Security framework doesn't validate self-signed certs.)
5 // Created by Jens Alfke on 3/23/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
10 #import "MYCrypto_Private.h"
12 #import "MYIdentity.h"
15 #if !MYCRYPTO_USE_IPHONE_API
18 @interface MYKeyEnumerator : NSEnumerator
21 MYKeychain *_keychain;
22 SecKeychainSearchRef _search;
23 SecItemClass _itemClass;
26 - (id) initWithKeychain: (MYKeychain*)keychain
27 itemClass: (SecItemClass)itemClass
28 attributes: (SecKeychainAttribute[])attributes
29 count: (unsigned)count;
33 @interface MYIdentityEnumerator : NSEnumerator
36 SecIdentitySearchRef _searchRef;
39 - (id) initWithKeychain: (MYKeychain*)keychain keyUsage: (CSSM_KEYUSE)keyUsage;
45 @implementation MYKeychain
48 - (id) initWithKeychainRef: (SecKeychainRef)keychainRef
53 CFRetain(keychainRef);
54 _keychain = keychainRef;
60 + (MYKeychain*) _readableKeychainWithRef: (SecKeychainRef)keychainRef fromPath: (NSString*)path {
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);
69 MYKeychain *keychain = nil;
71 keychain = [[[self alloc] initWithKeychainRef: keychainRef] autorelease];
72 CFRelease(keychainRef);
76 + (MYKeychain*) openKeychainAtPath: (NSString*)path
79 SecKeychainRef keychainRef = NULL;
80 if (!check(SecKeychainOpen(path.fileSystemRepresentation, &keychainRef), @"SecKeychainOpen"))
82 return [self _readableKeychainWithRef: keychainRef fromPath: path];
85 + (MYKeychain*) createKeychainAtPath: (NSString*)path
86 withPassword: (NSString*)password
89 const char *passwordStr = [password UTF8String];
90 SecKeychainRef keychainRef = NULL;
91 if (!check(SecKeychainCreate(path.fileSystemRepresentation,
92 passwordStr ?strlen(passwordStr) :0,
97 @"SecKeychainCreate"))
99 return [self _readableKeychainWithRef: keychainRef fromPath: path];
102 - (BOOL) deleteKeychainFile {
104 return check(SecKeychainDelete(_keychain), @"SecKeychainDelete");
110 if (_keychain) CFRelease(_keychain);
116 if (_keychain) CFRelease(_keychain);
121 + (MYKeychain*) allKeychains
123 static MYKeychain *sAllKeychains;
124 @synchronized(self) {
126 sAllKeychains = [[self alloc] initWithKeychainRef: nil];
128 return sAllKeychains;
132 + (MYKeychain*) defaultKeychain
134 static MYKeychain *sDefaultKeychain;
135 @synchronized(self) {
136 if (!sDefaultKeychain) {
137 SecKeychainRef kc = NULL;
138 OSStatus err = SecKeychainCopyDomainDefault(kSecPreferencesDomainUser,&kc);
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;
154 if (!check(err, @"SecKeychainCopyDefault"))
157 Assert(kc, @"No default keychain");
158 sDefaultKeychain = [[self alloc] initWithKeychainRef: kc];
162 return sDefaultKeychain;
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];
172 - (BOOL) isEqual: (id)obj {
173 return (obj == self) ||
174 ([obj isKindOfClass: [MYKeychain class]] && CFEqual(_keychain, [obj keychainRef]));
178 - (SecKeychainRef) keychainRef {
183 - (SecKeychainRef) keychainRefOrDefault {
187 return [[[self class] defaultKeychain] keychainRef];
194 char pathBuf[PATH_MAX];
195 UInt32 pathLen = sizeof(pathBuf);
196 if (!check(SecKeychainGetPath(_keychain, &pathLen, pathBuf), @"SecKeychainGetPath"))
198 return [[NSFileManager defaultManager] stringWithFileSystemRepresentation: pathBuf length: pathLen];
201 - (NSString*) description {
203 return $sprintf(@"%@[%p, %@]", [self class], _keychain, self.path);
205 return $sprintf(@"%@[all]", [self class]);
209 + (void) setUserInteractionAllowed: (BOOL)allowed {
210 SecKeychainSetUserInteractionAllowed(allowed);
215 #pragma mark SEARCHING:
218 - (MYKeychainItem*) itemOfClass: (SecItemClass)itemClass
219 withDigest: (MYSHA1Digest*)pubKeyDigest
221 SecKeychainAttribute attr = {.tag= (itemClass==kSecCertificateItemClass ?kSecPublicKeyHashItemAttr :kSecKeyLabel),
222 .length= pubKeyDigest.length,
223 .data= (void*) pubKeyDigest.bytes};
224 MYKeyEnumerator *e = [[MYKeyEnumerator alloc] initWithKeychain: self
226 attributes: &attr count: 1];
227 MYKeychainItem *item = e.nextObject;
232 - (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
233 return (MYPublicKey*) [self itemOfClass: kSecPublicKeyItemClass withDigest: pubKeyDigest];
236 - (NSEnumerator*) enumeratePublicKeys {
237 return [[[MYKeyEnumerator alloc] initWithKeychain: self
238 itemClass: kSecPublicKeyItemClass
239 attributes: NULL count: 0] autorelease];
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];
251 - (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
252 return (MYPrivateKey*) [self itemOfClass: kSecPrivateKeyItemClass withDigest: pubKeyDigest];
255 - (NSEnumerator*) enumeratePrivateKeys {
256 return [[[MYKeyEnumerator alloc] initWithKeychain: self
257 itemClass: kSecPrivateKeyItemClass
258 attributes: NULL count: 0] autorelease];
261 - (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest {
262 return (MYCertificate*) [self itemOfClass: kSecCertificateItemClass withDigest: pubKeyDigest];
265 - (NSEnumerator*) enumerateCertificates {
266 return [[[MYKeyEnumerator alloc] initWithKeychain: self
267 itemClass: kSecCertificateItemClass
268 attributes: NULL count: 0] autorelease];
271 - (NSEnumerator*) enumerateIdentities {
272 return [self enumerateIdentitiesWithKeyUsage: 0];
275 - (NSEnumerator*) enumerateIdentitiesWithKeyUsage: (CSSM_KEYUSE)keyUsage {
276 return [[[MYIdentityEnumerator alloc] initWithKeychain: self keyUsage: keyUsage] autorelease];
279 - (NSEnumerator*) enumerateSymmetricKeys {
280 return [[[MYKeyEnumerator alloc] initWithKeychain: self
281 itemClass: kSecSymmetricKeyItemClass
282 attributes: NULL count: 0] autorelease];
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];
299 - (MYPublicKey*) importPublicKey: (NSData*)keyData {
300 return [[[MYPublicKey alloc] _initWithKeyData: keyData
301 forKeychain: self.keychainRefOrDefault]
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]
317 - (MYPrivateKey*) importPublicKey: (NSData*)pubKeyData
318 privateKey: (NSData*)privKeyData
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."];
326 - (MYCertificate*) importCertificate: (NSData*)data
327 type: (CSSM_CERT_TYPE) type
328 encoding: (CSSM_CERT_ENCODING) encoding;
330 MYCertificate *cert = [[[MYCertificate alloc] initWithCertificateData: data
335 if (!check(SecCertificateAddToKeychain(cert.certificateRef, self.keychainRefOrDefault),
336 @"SecCertificateAddToKeychain"))
342 - (MYCertificate*) importCertificate: (NSData*)data {
343 return [self importCertificate: data
344 type: CSSM_CERT_X_509v3
345 encoding: CSSM_CERT_ENCODING_BER];
348 - (BOOL) addCertificate: (MYCertificate*)certificate {
350 return check(SecCertificateAddToKeychain(certificate.certificateRef, self.keychainRefOrDefault),
351 @"SecCertificateAddToKeychain");
355 - (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
356 algorithm: (CCAlgorithm)algorithm
358 return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits
359 algorithm: algorithm inKeychain: self];
362 - (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize {
363 return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self];
367 - (CSSM_CSP_HANDLE) CSPHandle {
368 CSSM_CSP_HANDLE cspHandle = 0;
369 Assert(check(SecKeychainGetCSPHandle(self.keychainRefOrDefault, &cspHandle), @"SecKeychainGetCSPHandle"));
379 @implementation MYKeyEnumerator
381 - (id) initWithKeychain: (MYKeychain*)keychain
382 itemClass: (SecItemClass)itemClass
383 attributes: (SecKeychainAttribute[])attributes
384 count: (unsigned)count {
387 _keychain = [keychain retain];
388 _itemClass = itemClass;
389 SecKeychainAttributeList list = {.count=count, .attr=attributes};
390 if (!check(SecKeychainSearchCreateFromAttributes(keychain.keychainRef,
394 @"SecKeychainSearchCreateFromAttributes")) {
405 if (_search) CFRelease(_search);
412 if (_search) CFRelease(_search);
420 MYPublicKey *key = nil;
422 SecKeychainItemRef found = NULL;
423 OSStatus err = SecKeychainSearchCopyNext(_search, &found);
425 if (err != errSecItemNotFound)
426 check(err,@"SecKeychainSearchCopyNext");
432 switch (_itemClass) {
433 case kSecPrivateKeyItemClass: {
434 key = [[MYPrivateKey alloc] initWithKeyRef: (SecKeyRef)found];
437 case kSecCertificateItemClass:
438 key = [[[MYCertificate alloc] initWithCertificateRef: (SecCertificateRef)found] autorelease];
440 case kSecPublicKeyItemClass:
441 key = [[[MYPublicKey alloc] initWithKeyRef: (SecKeyRef)found] autorelease];
453 @implementation MYIdentityEnumerator
455 - (id) initWithKeychain: (MYKeychain*)keychain keyUsage: (CSSM_KEYUSE)keyUsage {
458 if (!check(SecIdentitySearchCreate(keychain.keychainRef, keyUsage, &_searchRef),
459 @"SecIdentitySearchCreate")) {
468 SecIdentityRef identityRef = NULL;
469 OSStatus err = SecIdentitySearchCopyNext(_searchRef, &identityRef);
470 if (err==errKCItemNotFound || !check(err, @"SecIdentitySearchCopyNext"))
472 return [[[MYIdentity alloc] initWithIdentityRef: identityRef] autorelease];
478 #endif !MYCRYPTO_USE_IPHONE_API
483 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
485 Redistribution and use in source and binary forms, with or without modification, are permitted
486 provided that the following conditions are met:
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
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.