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 4/1/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYPublicKey.h"
10 #import "MYPrivateKey.h"
11 #import "MYKeychain.h"
13 #import "MYIdentity.h"
15 #import "MYCrypto+Cocoa.h"
17 #import "MYCrypto_Private.h"
23 #define kTestCaseRSAKeySize 2048
26 #pragma mark KEYCHAIN:
29 TestCase(MYKeychain) {
30 MYKeychain *kc = [MYKeychain defaultKeychain];
31 Log(@"Default keychain = %@", kc);
33 #if !MYCRYPTO_USE_IPHONE_API
37 kc = [MYKeychain allKeychains];
38 Log(@"All-keychains = %@", kc);
40 #if !MYCRYPTO_USE_IPHONE_API
41 CAssertEq(kc.path,nil);
46 #if MYCRYPTO_USE_IPHONE_API
48 RequireTestCase(MYKeychain);
49 MYKeychain *kc = [MYKeychain defaultKeychain];
50 CAssert([kc removeAllCertificates]);
51 CAssert([kc removeAllKeys]);
57 RequireTestCase(EnumeratePublicKeys);
58 RequireTestCase(EnumeratePrivateKeys);
59 RequireTestCase(EnumerateSymmetricKeys);
60 RequireTestCase(EnumerateCerts);
61 RequireTestCase(EnumerateIdentities);
65 TestCase(EnumeratePublicKeys) {
66 RequireTestCase(MYKeychain);
67 NSEnumerator *e = [[MYKeychain allKeychains] enumeratePublicKeys];
68 Log(@"Public Key Enumerator = %@", e);
70 for (MYPublicKey *key in e) {
71 Log(@"Trying public key %@", key.keyRef);
73 Log(@"Found %@ -- name=%@", key, key.name);
74 }@catch (NSException *x) {
75 Warn(@"Caught %@",x); //TEMP
80 TestCase(EnumeratePrivateKeys) {
81 RequireTestCase(MYKeychain);
82 NSEnumerator *e = [[MYKeychain allKeychains] enumeratePrivateKeys];
83 Log(@"Key-Pair Enumerator = %@", e);
85 for (MYPrivateKey *key in e) {
86 Log(@"Found %@ -- name=%@ --> %@", key, key.name, key.publicKey);
87 CAssert(key.publicKey);
91 TestCase(EnumerateSymmetricKeys) {
92 RequireTestCase(MYKeychain);
93 NSEnumerator *e = [[MYKeychain allKeychains] enumerateSymmetricKeys];
94 Log(@"Symmetric Key Enumerator = %@", e);
96 for (MYSymmetricKey *key in e) {
97 Log(@"Found %@ -- name=%@", key, key.name);
102 TestCase(EnumerateCerts) {
103 RequireTestCase(MYKeychain);
104 NSEnumerator *e = [[MYKeychain allKeychains] enumerateCertificates];
105 Log(@"Enumerator = %@", e);
107 for (MYCertificate *cert in e) {
108 Log(@"Found %@ -- key=%@, name=%@, email=%@", cert,
109 cert.publicKey, cert.commonName, cert.emailAddresses);
110 CAssert(cert.publicKey);
112 #if MYCRYPTO_USE_IPHONE_API
113 // Verify that cert has public-key-hash attribute:
114 NSData *digestData = [MYKeychainItem _getAttribute: kSecAttrPublicKeyHash
115 ofItem: cert.certificateRef];
116 CAssert(digestData, @"SecCertificateRef missing kSecAttrPublicKeyHash");
117 MYSHA1Digest *digest = [MYSHA1Digest digestFromDigestData: digestData];
118 CAssertEqual(digest, cert.publicKey.publicKeyDigest);
119 Log(@"kSecAttrPublicKeyHash matches: %@", digest);
121 MYSHA1Digest *digest = cert.publicKey.publicKeyDigest;
123 // Verify that certificateWithDigest will find it:
124 MYCertificate *cert2 = [[MYKeychain allKeychains] certificateWithDigest: digest];
125 Log(@"certificateWithDigest(%@) returned %@", digest,cert2);
127 CAssertEqual(cert2.certificateData, cert.certificateData);
131 TestCase(EnumerateIdentities) {
132 RequireTestCase(MYKeychain);
133 NSEnumerator *e = [[MYKeychain allKeychains] enumerateIdentities];
134 Log(@"Enumerator = %@", e);
136 for (MYIdentity *ident in e) {
137 Log(@"Found %@\n\tcommonName=%@\n\temails=(%@)\n\tkey=%@",
138 ident, ident.commonName,
139 [ident.emailAddresses componentsJoinedByString: @", "],
142 // Verify that identityWithDigest will find it:
143 MYSHA1Digest *digest = ident.publicKey.publicKeyDigest;
144 MYIdentity *ident2 = [[MYKeychain allKeychains] identityWithDigest:digest];
145 Log(@"identityWithDigest(%@) returned %@", digest,ident2);
147 CAssertEqual(ident2.certificateData, ident.certificateData);
153 #pragma mark SYMMETRIC KEYS:
156 static void testSymmetricKey( CCAlgorithm algorithm, unsigned sizeInBits, MYKeychain *inKeychain ) {
157 NSAutoreleasePool *pool = [NSAutoreleasePool new];
158 MYSymmetricKey *key = nil;
160 Log(@"--- Testing %3u-bit #%i %s", sizeInBits, (int)algorithm,
161 (inKeychain ?", in keychain" :""));
164 key = [inKeychain generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
166 key = [MYSymmetricKey generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
167 Log(@"Created %@", key);
169 CAssertEq(key.algorithm, algorithm);
170 CAssertEq(key.keySizeInBits, sizeInBits);
171 #if !TARGET_OS_IPHONE
172 CAssert(key.cssmKey != NULL);
175 NSData *keyData = key.keyData;
176 Log(@"Key data = %@", keyData);
177 CAssertEq(keyData.length, sizeInBits/8);
179 // Encrypt a small amount of text:
180 Log(@"Testing encryption / decryption ...");
181 NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
182 NSData *encrypted = [key encryptData: cleartext];
183 Log(@"Encrypted = %u bytes: %@", encrypted.length, encrypted);
184 CAssert(encrypted.length >= cleartext.length);
185 NSData *decrypted = [key decryptData: encrypted];
186 CAssertEqual(decrypted, cleartext);
188 // Encrypt large binary data:
189 cleartext = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
191 encrypted = [key encryptData: cleartext];
192 Log(@"Encrypted = %u bytes", encrypted.length);
193 CAssert(encrypted.length >= cleartext.length);
194 decrypted = [key decryptData: encrypted];
195 CAssertEqual(decrypted, cleartext);
198 Log(@"Testing initWithKeyData:...");
199 MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithKeyData: keyData algorithm: algorithm];
201 Log(@"Key from data = %@",key2);
202 CAssertEqual(key2.keyData, keyData);
203 CAssertEq(key2.algorithm, algorithm);
204 CAssertEq(key2.keySizeInBits, sizeInBits);
205 decrypted = [key2 decryptData: encrypted];
206 CAssertEqual(decrypted, cleartext);
210 #if !TARGET_OS_IPHONE
211 // Try exporting and importing a wrapped key:
212 Log(@"Testing export/import...");
213 NSData *exported = [key exportWrappedKeyWithPassphrasePrompt: @"Export symmetric key with passphrase:"];
214 Log(@"Exported key: %@", exported);
219 Warn(@"Unable to export wrapped key");
224 MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithWrappedKeyData: exported];
225 Log(@"Reconstituted as %@", key2);
226 CAssertEqual(key2.keyData,key.keyData);
227 decrypted = [key2 decryptData: encrypted];
228 CAssertEqual(decrypted, cleartext);
232 [key removeFromKeychain];
238 TestCase(MYSymmetricKey) {
240 static const CCAlgorithm kTestAlgorithms[kNTests] = {
241 kCCAlgorithmAES128, kCCAlgorithmAES128, kCCAlgorithmAES128,
242 kCCAlgorithmDES, kCCAlgorithm3DES,
243 kCCAlgorithmCAST, kCCAlgorithmCAST, kCCAlgorithmCAST,
244 kCCAlgorithmRC4, kCCAlgorithmRC4, kCCAlgorithmRC4};
246 static const unsigned kTestBitSizes[kNTests] = {
252 for (int useKeychain=0; useKeychain<=1; useKeychain++)
253 for (int testNo=0; testNo<kNTests; testNo++)
254 testSymmetricKey(kTestAlgorithms[testNo],
255 kTestBitSizes[testNo],
256 useKeychain ?[MYKeychain defaultKeychain] :nil);
260 #if !TARGET_OS_IPHONE
261 TestCase(MYSymmetricKeyPassphrase) {
262 Log(@"Prompting for raw passphrase --");
263 NSString *rawPassphrase = [MYSymmetricKey promptForPassphraseWithAlertTitle: @"Raw Passphrase Test"
264 alertPrompt: @"Enter the passphrase 'Testing':"
266 Log(@"You entered: '%@'", rawPassphrase);
267 CAssertEqual(rawPassphrase, @"Testing");
269 Log(@"Prompting for passphrase for key --");
270 MYSymmetricKey *key = [MYSymmetricKey generateFromUserPassphraseWithAlertTitle: @"Symmetric Key Passphrase Test Case"
271 alertPrompt: @"Please enter a passphrase to generate a key:"
274 Log(@"Key from passphrase = %@", key);
277 // Encrypt a small amount of text:
278 Log(@"Testing encryption / decryption ...");
279 NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
280 NSData *encrypted = [key encryptData: cleartext];
281 Log(@"Encrypted = %u bytes: %@", encrypted.length, encrypted);
282 CAssert(encrypted.length >= cleartext.length);
283 NSData *decrypted = [key decryptData: encrypted];
284 CAssertEqual(decrypted, cleartext);
286 // Now test decryption by re-entered passphrase:
287 Log(@"Testing decryption using re-entered passphrase...");
288 MYSymmetricKey *key2 = [MYSymmetricKey generateFromUserPassphraseWithAlertTitle: @"Symmetric Key Passphrase Test Case"
289 alertPrompt: @"Please re-enter the same passphrase:"
292 Log(@"Key from passphrase = %@", key2);
294 decrypted = [key2 decryptData: encrypted];
295 CAssertEqual(decrypted, cleartext);
301 #pragma mark KEY-PAIRS:
304 static void TestUseKeyPair(MYPrivateKey *pair) {
305 Log(@"---- TestUseKeyPair { %@ , %@ }.", pair, pair.publicKey);
307 CAssert(pair.keyRef);
308 MYPublicKey *publicKey = pair.publicKey;
309 CAssert(publicKey.keyRef);
311 NSData *pubKeyData = publicKey.keyData;
312 Log(@"Public key = %@ (%u bytes)",pubKeyData,pubKeyData.length);
317 CAssert([publicKey getModulus: &modulus exponent: &exponent]);
318 Log(@"Modulus = %@", modulus);
319 Log(@"Exponent = %u", exponent);
320 CAssertEq(modulus.length, 2048U/8);
321 CAssertEq(exponent,65537U); // this is what CDSA always seems to use
323 MYSHA1Digest *pubKeyDigest = publicKey.publicKeyDigest;
324 Log(@"Public key digest = %@",pubKeyDigest);
325 CAssertEqual(pair.publicKeyDigest, pubKeyDigest);
327 Log(@"SHA1 of pub key = %@", pubKeyData.my_SHA1Digest.asData);
328 CAssertEqual(pubKeyData.my_SHA1Digest,pubKeyDigest);
331 NSData *data = [@"This is a test. This is only a test!" dataUsingEncoding: NSUTF8StringEncoding];
332 NSData *sig = [pair signData: data];
333 Log(@"Signature = %@ (%u bytes)",sig,sig.length);
335 CAssert( [publicKey verifySignature: sig ofData: data] );
337 // Now let's encrypt...
338 NSData *crypted = [publicKey rawEncryptData: data];
339 Log(@"Encrypted = %@ (%u bytes)",crypted,crypted.length);
341 CAssertEqual([pair rawDecryptData: crypted], data);
342 Log(@"Verified decryption.");
344 // Test creating a standalone public key:
345 MYPublicKey *pub = [[MYPublicKey alloc] initWithKeyRef: publicKey.keyRef];
346 CAssert( [pub verifySignature: sig ofData: data] );
347 Log(@"Verified signature.");
349 // Test creating a public key from data:
350 Log(@"Reconstituting public key from data...");
351 pub = [[MYPublicKey alloc] initWithKeyData: pubKeyData];
353 CAssertEqual(pub.keyData, pubKeyData);
354 CAssertEqual(pub.publicKeyDigest, pubKeyDigest);
355 CAssert( [pub verifySignature: sig ofData: data] );
357 Log(@"Verified signature from reconstituted key.");
359 // Test creating a public key from modulus+exponent:
360 Log(@"Reconstituting public key from modulus+exponent...");
361 pub = [[MYPublicKey alloc] initWithModulus: modulus exponent: exponent];
362 CAssertEqual(pub.keyData, pubKeyData);
367 static void TestWrapSessionKey( MYPrivateKey *privateKey ) {
368 #if !TARGET_OS_IPHONE
369 MYSymmetricKey *sessionKey = [MYSymmetricKey generateSymmetricKeyOfSize: 128 algorithm:kCCAlgorithmAES128];
371 NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
372 NSData *encrypted = [sessionKey encryptData: cleartext];
374 Log(@"Wrapping session key %@, %@", sessionKey, sessionKey.keyData);
375 NSData *wrapped = [privateKey.publicKey wrapSessionKey: sessionKey];
376 Log(@"Wrapped session key = %u bytes: %@", wrapped.length,wrapped);
377 CAssert(wrapped.length >= 128/8);
379 MYSymmetricKey *unwrappedKey = [privateKey unwrapSessionKey: wrapped
380 withAlgorithm: kCCAlgorithmAES128
382 Log(@"Unwrapped session key = %@, %@", unwrappedKey, unwrappedKey.keyData);
383 CAssert(unwrappedKey);
384 CAssertEq(unwrappedKey.algorithm, sessionKey.algorithm);
385 CAssertEq(unwrappedKey.keySizeInBits, sessionKey.keySizeInBits);
386 CAssertEqual(unwrappedKey.keyData, sessionKey.keyData);
388 Log(@"Verifying that unwrapped key works");
389 NSData *decrypted = [unwrappedKey decryptData: encrypted];
390 CAssertEqual(decrypted, cleartext);
395 TestCase(MYGenerateKeyPair) {
396 RequireTestCase(MYKeychain);
398 Log(@"Generating key pair...");
399 MYPrivateKey *pair = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
400 MYPublicKey *publicKey = pair.publicKey;
401 Log(@"...created { %@ , %@ }.", pair, publicKey);
404 TestUseKeyPair(pair);
405 TestWrapSessionKey(pair);
407 [pair setName: @"Test KeyPair Label"];
408 CAssertEqual(pair.name, @"Test KeyPair Label");
409 CAssertEqual(publicKey.name, @"Test KeyPair Label");
410 #if !TARGET_OS_IPHONE
411 [pair setComment: @"This key-pair was generated automatically by a test case."];
412 CAssertEqual(pair.comment, @"This key-pair was generated automatically by a test case.");
413 CAssertEqual(publicKey.comment, @"This key-pair was generated automatically by a test case.");
415 [pair setAlias: @"TestCase@mooseyard.com"];
416 CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
417 CAssertEqual(publicKey.alias, @"TestCase@mooseyard.com");
419 CAssert([pair removeFromKeychain]);
420 Log(@"Removed key-pair.");
425 if ([pair removeFromKeychain])
426 Log(@"Removed key-pair from keychain.");
428 Warn(@"Unable to remove test key-pair from keychain");
434 #if !TARGET_OS_IPHONE
435 TestCase(MYUseIdentity) {
436 MYIdentity *me = nil;//[MYIdentity preferredIdentityForName: @"MYCryptoTest"];
438 NSArray *idents = [[[MYKeychain allKeychains] enumerateIdentities] allObjects];
439 SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
440 [panel setAlternateButtonTitle: @"Cancel"];
441 if ([panel my_runModalForIdentities: idents
442 message: @"Choose an identity for the MYEncoder test case:"]
444 [NSException raise: NSGenericException format: @"User canceled"];
446 me = [panel my_identity];
447 [me makePreferredIdentityForName: @"MYCryptoTest"];
449 CAssert(me,@"No default identity has been set up in the Keychain");
450 TestUseKeyPair(me.privateKey);
456 #pragma mark KEYPAIR EXPORT:
459 static void testKeyPairExportWithPrompt(BOOL withPrompt) {
460 MYKeychain *keychain = [MYKeychain allKeychains];
461 Log(@"Generating key pair...");
462 MYPrivateKey *pair = [keychain generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
464 CAssert(pair.keyRef);
465 CAssert(pair.publicKey.keyRef);
466 Log(@"...created pair.");
469 NSData *pubKeyData = pair.publicKey.keyData;
470 CAssert(pubKeyData.length >= kTestCaseRSAKeySize/8);
471 [pair setName: @"Test KeyPair Label"];
472 CAssertEqual(pair.name, @"Test KeyPair Label");
473 #if !TARGET_OS_IPHONE
474 [pair setComment: @"This key-pair was generated automatically by a test case."];
475 CAssertEqual(pair.comment, @"This key-pair was generated automatically by a test case.");
477 [pair setAlias: @"TestCase@mooseyard.com"];
478 CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
480 #if !TARGET_OS_IPHONE
481 Log(@"Exporting key-pair...");
482 NSString *passphrase = @"passphrase";
485 privKeyData = [pair exportKey];
487 privKeyData = [pair _exportKeyInFormat: kSecFormatWrappedOpenSSL
489 passphrase: passphrase];
490 Log(@"Exported data = %@ (%u bytes)", privKeyData,privKeyData.length);
491 CAssert(privKeyData);
492 [privKeyData writeToFile: @"ExportedPrivKey" atomically: YES];
496 Log(@"Looking up public key of pair in keychain...");
497 MYSHA1Digest *digest = pair.publicKeyDigest;
498 MYPublicKey *foundKey = [keychain publicKeyWithDigest: digest];
499 CAssertEqual(foundKey, pair.publicKey);
500 CAssert([keychain.enumeratePublicKeys.allObjects containsObject: pair.publicKey]);
501 MYPrivateKey *foundPair = [keychain privateKeyWithDigest: digest];
502 CAssertEqual(foundPair, pair);
503 CAssert([keychain.enumeratePrivateKeys.allObjects containsObject: pair]);
505 Log(@"Removing key-pair from keychain...");
506 CAssert([pair removeFromKeychain]);
508 CAssert([keychain publicKeyWithDigest: digest] == nil);
510 #if !TARGET_OS_IPHONE
511 Log(@"Importing key-pair...");
513 pair = [keychain importPublicKey: pubKeyData
514 privateKey: privKeyData];
516 pair = [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
517 publicKeyData: pubKeyData
518 forKeychain: keychain.keychainRefOrDefault
519 passphrase: passphrase]
523 CAssertEqual(pair.publicKey.keyData, pubKeyData);
527 if ([pair removeFromKeychain])
528 Log(@"Removed key-pair from keychain.");
530 Warn(@"Unable to remove test key-pair from keychain");
535 TestCase(KeyPairExport) {
536 RequireTestCase(MYKeychain);
537 RequireTestCase(MYGenerateKeyPair);
538 testKeyPairExportWithPrompt(NO);
541 TestCase(KeyPairExportWithUI) {
542 RequireTestCase(KeyPairExport);
543 testKeyPairExportWithPrompt(YES);