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/31/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYCrypto_Private.h"
11 #import "MYIdentity.h"
14 #if MYCRYPTO_USE_IPHONE_API
19 CSSM_CERT_UNKNOWN = 0x00,
20 CSSM_CERT_X_509v1 = 0x01,
21 CSSM_CERT_X_509v2 = 0x02,
22 CSSM_CERT_X_509v3 = 0x03,
24 CSSM_CERT_ENCODING_UNKNOWN = 0x00,
25 CSSM_CERT_ENCODING_CUSTOM = 0x01,
26 CSSM_CERT_ENCODING_BER = 0x02,
27 CSSM_CERT_ENCODING_DER = 0x03,
31 @interface MYKeyEnumerator : NSEnumerator
36 MYKeychainItem *_currentObject;
39 - (id) initWithQuery: (NSMutableDictionary*)query;
40 + (id) firstItemWithQuery: (NSMutableDictionary*)query;
45 @implementation MYKeychain
48 + (MYKeychain*) allKeychains
50 // iPhone only has a single keychain.
51 return [self defaultKeychain];
54 + (MYKeychain*) defaultKeychain
56 static MYKeychain *sDefaultKeychain;
58 if (!sDefaultKeychain) {
59 sDefaultKeychain = [[self alloc] init];
62 return sDefaultKeychain;
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.
75 #pragma mark SEARCHING:
78 - (MYPublicKey*) publicKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
79 return [MYKeyEnumerator firstItemWithQuery:
80 $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPublic},
81 {(id)kSecAttrApplicationLabel, pubKeyDigest.asData})];
84 - (NSEnumerator*) enumeratePublicKeys {
85 NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPublic});
86 return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
90 - (MYPrivateKey*) privateKeyWithDigest: (MYSHA1Digest*)pubKeyDigest {
91 return [MYKeyEnumerator firstItemWithQuery:
92 $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPrivate},
93 {(id)kSecAttrApplicationLabel, pubKeyDigest.asData})];
96 - (NSEnumerator*) enumeratePrivateKeys {
97 NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassPrivate});
98 return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
101 - (MYCertificate*) certificateWithDigest: (MYSHA1Digest*)pubKeyDigest {
102 return [MYKeyEnumerator firstItemWithQuery:
103 $mdict({(id)kSecClass, (id)kSecClassCertificate},
104 {(id)kSecAttrPublicKeyHash, pubKeyDigest.asData})];
107 - (NSEnumerator*) enumerateCertificates {
108 NSMutableDictionary *query = $mdict({(id)kSecClass, (id)kSecClassCertificate});
109 return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
112 - (MYIdentity*) identityWithDigest: (MYSHA1Digest*)pubKeyDigest {
113 return [MYKeyEnumerator firstItemWithQuery:
114 $mdict({(id)kSecClass, (id)kSecClassIdentity},
115 {(id)kSecAttrApplicationLabel/*kSecAttrPublicKeyHash*/, pubKeyDigest.asData})];
118 - (NSEnumerator*) enumerateIdentities {
119 NSMutableDictionary *query = $mdict({(id)kSecClass, (id)kSecClassIdentity});
120 return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
123 - (NSEnumerator*) enumerateSymmetricKeys {
124 NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric});
125 return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
128 - (NSEnumerator*) symmetricKeysWithAlias: (NSString*)alias {
129 NSMutableDictionary *query = $mdict({(id)kSecAttrKeyClass, (id)kSecAttrKeyClassSymmetric},
130 {(id)kSecAttrApplicationTag, alias});
131 return [[[MYKeyEnumerator alloc] initWithQuery: query] autorelease];
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];
145 CFDataRef itemPersistentRef;
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")) {
156 Warn(@"_addItemWithInfo: Couldn't find supposedly-duplicate item, info=%@",info);
157 Log(@"_addItemWithInfo: SecItemAdd found item; ref=%@", item);//TEMP
160 } else if (check(err, @"SecItemAdd")) {
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")) {
174 Log(@"SecItemAdd added item; ref=%@", itemPersistentRef);//TEMP
175 return (CFTypeRef)itemPersistentRef;
178 Log(@"SecItemAdd failed: info = %@", info); // for help in debugging, dump the input dict
183 - (MYPublicKey*) importPublicKey: (NSData*)keyData {
184 return [[[MYPublicKey alloc] _initWithKeyData: keyData
189 - (MYCertificate*) importCertificate: (NSData*)data
194 SecCertificateRef cert0 = SecCertificateCreateWithData(NULL, (CFDataRef)data);
197 NSMutableDictionary *info = $mdict( {(id)kSecClass, (id)kSecClassCertificate},
198 {(id)kSecValueRef, (id)cert0});
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} );
205 SecCertificateRef cert = (SecCertificateRef) [[self class] _addItemWithInfo: info];
208 MYCertificate *myCert = [[[MYCertificate alloc] initWithCertificateRef: cert] autorelease];
209 AssertEqual(data, myCert.certificateData); //TEMP for debugging
215 #pragma mark GENERATION:
218 - (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
219 algorithm: (CCAlgorithm)algorithm
221 return [MYSymmetricKey _generateSymmetricKeyOfSize: keySizeInBits
222 algorithm: algorithm inKeychain: self];
225 - (MYPrivateKey*) generateRSAKeyPairOfSize: (unsigned)keySize {
226 return [MYPrivateKey _generateRSAKeyPairOfSize: keySize inKeychain: self];
231 #pragma mark REMOVING:
234 - (BOOL) removeAllCertificates {
235 NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassCertificate});
236 return check(SecItemDelete((CFDictionaryRef)query), @"SecItemDelete");
239 - (BOOL) removeAllKeys {
240 NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey});
241 return check(SecItemDelete((CFDictionaryRef)query), @"SecItemDelete");
250 @implementation MYKeyEnumerator
252 - (id) initWithQuery: (NSMutableDictionary*)query {
255 _itemClass = (CFTypeRef)[query objectForKey: (id)kSecAttrKeyClass];
257 [query setObject: (id)kSecClassKey forKey: (id)kSecClass];
259 _itemClass = (CFTypeRef)[query objectForKey: (id)kSecClass];
261 CFRetain(_itemClass);
263 // Ask for all results unless caller specified fewer:
264 CFTypeRef limit = [query objectForKey: (id)kSecMatchLimit];
266 limit = kSecMatchLimitAll;
267 [query setObject: (id)limit forKey: (id)kSecMatchLimit];
270 [query setObject: $true forKey: (id)kSecReturnRef];
272 OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)&_results);
273 if (err && err != errSecItemNotFound) {
274 check(err,@"SecItemCopyMatching");
278 //Log(@"Enumerator results = %@", _results);
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);
285 _results = resultsArray;
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];
296 return [item autorelease];
301 [_currentObject release];
302 CFRelease(_itemClass);
303 if (_results) CFRelease(_results);
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);
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];
342 Assert(NO,@"Unknown _itemClass: %@",_itemClass);
345 return _currentObject;
351 #endif MYCRYPTO_USE_IPHONE_API
355 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
357 Redistribution and use in source and binary forms, with or without modification, are permitted
358 provided that the following conditions are met:
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
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.