* More work on iPhone compatibility.
* Restored the signature-verification code to MYCertInfo, which I'd removed earlier. I now need it to verify self-signed certs, since the Security framework won't do it for me.
* Merged MYCertificate-iPhone.m into MYCertificate.m since there's more shared code now.
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);
47 RequireTestCase(EnumeratePublicKeys);
48 RequireTestCase(EnumeratePrivateKeys);
49 RequireTestCase(EnumerateSymmetricKeys);
50 RequireTestCase(EnumerateCerts);
51 RequireTestCase(EnumerateIdentities);
55 TestCase(EnumeratePublicKeys) {
56 RequireTestCase(MYKeychain);
57 NSEnumerator *e = [[MYKeychain allKeychains] enumeratePublicKeys];
58 Log(@"Public Key Enumerator = %@", e);
60 for (MYPublicKey *key in e) {
61 Log(@"Trying public key %@", key.keyRef);
63 Log(@"Found %@ -- name=%@", key, key.name);
64 }@catch (NSException *x) {
65 Warn(@"Caught %@",x); //TEMP
70 TestCase(EnumeratePrivateKeys) {
71 RequireTestCase(MYKeychain);
72 NSEnumerator *e = [[MYKeychain allKeychains] enumeratePrivateKeys];
73 Log(@"Key-Pair Enumerator = %@", e);
75 for (MYPrivateKey *key in e) {
76 Log(@"Found %@ -- name=%@ --> %@", key, key.name, key.publicKey);
77 CAssert(key.publicKey);
81 TestCase(EnumerateSymmetricKeys) {
82 RequireTestCase(MYKeychain);
83 NSEnumerator *e = [[MYKeychain allKeychains] enumerateSymmetricKeys];
84 Log(@"Symmetric Key Enumerator = %@", e);
86 for (MYSymmetricKey *key in e) {
87 Log(@"Found %@ -- name=%@", key, key.name);
92 TestCase(EnumerateCerts) {
93 RequireTestCase(MYKeychain);
94 NSEnumerator *e = [[MYKeychain allKeychains] enumerateCertificates];
95 Log(@"Enumerator = %@", e);
97 for (MYCertificate *cert in e) {
98 Log(@"Found %@ -- name=%@, email=%@", cert, cert.commonName, cert.emailAddresses);
99 CAssert(cert.publicKey);
103 TestCase(EnumerateIdentities) {
104 RequireTestCase(MYKeychain);
105 NSEnumerator *e = [[MYKeychain allKeychains] enumerateIdentities];
106 Log(@"Enumerator = %@", e);
108 for (MYIdentity *ident in e) {
109 Log(@"Found %@\n\tcommonName=%@\n\temails=(%@)\n\tkey=%@",
110 ident, ident.commonName,
114 [ident.emailAddresses componentsJoinedByString: @", "],
122 #pragma mark SYMMETRIC KEYS:
125 static void testSymmetricKey( CCAlgorithm algorithm, unsigned sizeInBits, MYKeychain *inKeychain ) {
126 NSAutoreleasePool *pool = [NSAutoreleasePool new];
127 MYSymmetricKey *key = nil;
129 Log(@"--- Testing %3u-bit #%i %s", sizeInBits, (int)algorithm,
130 (inKeychain ?", in keychain" :""));
133 key = [inKeychain generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
135 key = [MYSymmetricKey generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
136 Log(@"Created %@", key);
138 CAssertEq(key.algorithm, algorithm);
139 CAssertEq(key.keySizeInBits, sizeInBits);
140 #if !TARGET_OS_IPHONE
141 CAssert(key.cssmKey != NULL);
144 NSData *keyData = key.keyData;
145 Log(@"Key data = %@", keyData);
146 CAssertEq(keyData.length, sizeInBits/8);
148 // Encrypt a small amount of text:
149 Log(@"Testing encryption / decryption ...");
150 NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
151 NSData *encrypted = [key encryptData: cleartext];
152 Log(@"Encrypted = %u bytes: %@", encrypted.length, encrypted);
153 CAssert(encrypted.length >= cleartext.length);
154 NSData *decrypted = [key decryptData: encrypted];
155 CAssertEqual(decrypted, cleartext);
157 // Encrypt large binary data:
158 cleartext = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
160 encrypted = [key encryptData: cleartext];
161 Log(@"Encrypted = %u bytes", encrypted.length);
162 CAssert(encrypted.length >= cleartext.length);
163 decrypted = [key decryptData: encrypted];
164 CAssertEqual(decrypted, cleartext);
167 Log(@"Testing initWithKeyData:...");
168 MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithKeyData: keyData algorithm: algorithm];
170 Log(@"Key from data = %@",key2);
171 CAssertEqual(key2.keyData, keyData);
172 CAssertEq(key2.algorithm, algorithm);
173 CAssertEq(key2.keySizeInBits, sizeInBits);
174 decrypted = [key2 decryptData: encrypted];
175 CAssertEqual(decrypted, cleartext);
179 #if !TARGET_OS_IPHONE
180 #if 1 // TEMP-ORARILY OUT OF ORDER
181 // Try exporting and importing a wrapped key:
182 Log(@"Testing export/import...");
183 NSData *exported = [key exportWrappedKeyWithPassphrasePrompt: @"Export symmetric key with passphrase:"];
184 Log(@"Exported key: %@", exported);
189 Warn(@"Unable to export wrapped key");
194 MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithWrappedKeyData: exported];
195 Log(@"Reconstituted as %@", key2);
196 CAssertEqual(key2.keyData,key.keyData);
197 decrypted = [key2 decryptData: encrypted];
198 CAssertEqual(decrypted, cleartext);
203 [key removeFromKeychain];
209 TestCase(MYSymmetricKey) {
211 static const CCAlgorithm kTestAlgorithms[kNTests] = {
212 kCCAlgorithmAES128, kCCAlgorithmAES128, kCCAlgorithmAES128,
213 kCCAlgorithmDES, kCCAlgorithm3DES,
214 kCCAlgorithmCAST, kCCAlgorithmCAST, kCCAlgorithmCAST,
215 kCCAlgorithmRC4, kCCAlgorithmRC4, kCCAlgorithmRC4};
217 static const unsigned kTestBitSizes[kNTests] = {
223 for (int useKeychain=0; useKeychain<=1; useKeychain++)
224 for (int testNo=0; testNo<kNTests; testNo++)
225 testSymmetricKey(kTestAlgorithms[testNo],
226 kTestBitSizes[testNo],
227 useKeychain ?[MYKeychain defaultKeychain] :nil);
231 #if !TARGET_OS_IPHONE
232 TestCase(MYSymmetricKeyPassphrase) {
233 Log(@"Prompting for raw passphrase --");
234 NSString *rawPassphrase = [MYSymmetricKey promptForPassphraseWithAlertTitle: @"Raw Passphrase Test"
235 alertPrompt: @"Enter the passphrase 'Testing':"
237 Log(@"You entered: '%@'", rawPassphrase);
238 CAssertEqual(rawPassphrase, @"Testing");
240 Log(@"Prompting for passphrase for key --");
241 MYSymmetricKey *key = [MYSymmetricKey generateFromUserPassphraseWithAlertTitle: @"Symmetric Key Passphrase Test Case"
242 alertPrompt: @"Please enter a passphrase to generate a key:"
245 Log(@"Key from passphrase = %@", key);
248 // Encrypt a small amount of text:
249 Log(@"Testing encryption / decryption ...");
250 NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
251 NSData *encrypted = [key encryptData: cleartext];
252 Log(@"Encrypted = %u bytes: %@", encrypted.length, encrypted);
253 CAssert(encrypted.length >= cleartext.length);
254 NSData *decrypted = [key decryptData: encrypted];
255 CAssertEqual(decrypted, cleartext);
257 // Now test decryption by re-entered passphrase:
258 Log(@"Testing decryption using re-entered passphrase...");
259 MYSymmetricKey *key2 = [MYSymmetricKey generateFromUserPassphraseWithAlertTitle: @"Symmetric Key Passphrase Test Case"
260 alertPrompt: @"Please re-enter the same passphrase:"
263 Log(@"Key from passphrase = %@", key2);
265 decrypted = [key2 decryptData: encrypted];
266 CAssertEqual(decrypted, cleartext);
272 #pragma mark KEY-PAIRS:
275 static void TestUseKeyPair(MYPrivateKey *pair) {
276 Log(@"---- TestUseKeyPair { %@ , %@ }.", pair, pair.publicKey);
278 CAssert(pair.keyRef);
279 MYPublicKey *publicKey = pair.publicKey;
280 CAssert(publicKey.keyRef);
282 NSData *pubKeyData = publicKey.keyData;
283 Log(@"Public key = %@ (%u bytes)",pubKeyData,pubKeyData.length);
288 CAssert([publicKey getModulus: &modulus exponent: &exponent]);
289 Log(@"Modulus = %@", modulus);
290 Log(@"Exponent = %u", exponent);
291 CAssertEq(modulus.length, 2048U/8);
292 CAssertEq(exponent,65537U); // this is what CDSA always seems to use
294 MYSHA1Digest *pubKeyDigest = publicKey.publicKeyDigest;
295 Log(@"Public key digest = %@",pubKeyDigest);
296 CAssertEqual(pair.publicKeyDigest, pubKeyDigest);
298 Log(@"SHA1 of pub key = %@", pubKeyData.my_SHA1Digest.asData);
299 CAssertEqual(pubKeyData.my_SHA1Digest,pubKeyDigest);
302 NSData *data = [@"This is a test. This is only a test!" dataUsingEncoding: NSUTF8StringEncoding];
303 NSData *sig = [pair signData: data];
304 Log(@"Signature = %@ (%u bytes)",sig,sig.length);
306 CAssert( [publicKey verifySignature: sig ofData: data] );
308 // Now let's encrypt...
309 NSData *crypted = [publicKey rawEncryptData: data];
310 Log(@"Encrypted = %@ (%u bytes)",crypted,crypted.length);
312 CAssertEqual([pair rawDecryptData: crypted], data);
313 Log(@"Verified decryption.");
315 // Test creating a standalone public key:
316 MYPublicKey *pub = [[MYPublicKey alloc] initWithKeyRef: publicKey.keyRef];
317 CAssert( [pub verifySignature: sig ofData: data] );
318 Log(@"Verified signature.");
320 // Test creating a public key from data:
321 Log(@"Reconstituting public key from data...");
322 pub = [[MYPublicKey alloc] initWithKeyData: pubKeyData];
324 CAssertEqual(pub.keyData, pubKeyData);
325 CAssertEqual(pub.publicKeyDigest, pubKeyDigest);
326 CAssert( [pub verifySignature: sig ofData: data] );
328 Log(@"Verified signature from reconstituted key.");
330 // Test creating a public key from modulus+exponent:
331 Log(@"Reconstituting public key from modulus+exponent...");
332 pub = [[MYPublicKey alloc] initWithModulus: modulus exponent: exponent];
333 CAssertEqual(pub.keyData, pubKeyData);
338 static void TestWrapSessionKey( MYPrivateKey *privateKey ) {
339 #if !TARGET_OS_IPHONE
340 MYSymmetricKey *sessionKey = [MYSymmetricKey generateSymmetricKeyOfSize: 128 algorithm:kCCAlgorithmAES128];
342 NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
343 NSData *encrypted = [sessionKey encryptData: cleartext];
345 Log(@"Wrapping session key %@, %@", sessionKey, sessionKey.keyData);
346 NSData *wrapped = [privateKey.publicKey wrapSessionKey: sessionKey];
347 Log(@"Wrapped session key = %u bytes: %@", wrapped.length,wrapped);
348 CAssert(wrapped.length >= 128/8);
350 MYSymmetricKey *unwrappedKey = [privateKey unwrapSessionKey: wrapped
351 withAlgorithm: kCCAlgorithmAES128
353 Log(@"Unwrapped session key = %@, %@", unwrappedKey, unwrappedKey.keyData);
354 CAssert(unwrappedKey);
355 CAssertEq(unwrappedKey.algorithm, sessionKey.algorithm);
356 CAssertEq(unwrappedKey.keySizeInBits, sessionKey.keySizeInBits);
357 CAssertEqual(unwrappedKey.keyData, sessionKey.keyData);
359 Log(@"Verifying that unwrapped key works");
360 NSData *decrypted = [unwrappedKey decryptData: encrypted];
361 CAssertEqual(decrypted, cleartext);
366 TestCase(MYGenerateKeyPair) {
367 RequireTestCase(MYKeychain);
369 Log(@"Generating key pair...");
370 MYPrivateKey *pair = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
371 MYPublicKey *publicKey = pair.publicKey;
372 Log(@"...created { %@ , %@ }.", pair, publicKey);
375 TestUseKeyPair(pair);
376 TestWrapSessionKey(pair);
378 [pair setName: @"Test KeyPair Label"];
379 CAssertEqual(pair.name, @"Test KeyPair Label");
380 CAssertEqual(publicKey.name, @"Test KeyPair Label");
381 #if !TARGET_OS_IPHONE
382 [pair setComment: @"This key-pair was generated automatically by a test case."];
383 CAssertEqual(pair.comment, @"This key-pair was generated automatically by a test case.");
384 CAssertEqual(publicKey.comment, @"This key-pair was generated automatically by a test case.");
386 [pair setAlias: @"TestCase@mooseyard.com"];
387 CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
388 CAssertEqual(publicKey.alias, @"TestCase@mooseyard.com");
390 CAssert([pair removeFromKeychain]);
391 Log(@"Removed key-pair.");
396 if ([pair removeFromKeychain])
397 Log(@"Removed key-pair from keychain.");
399 Warn(@"Unable to remove test key-pair from keychain");
405 #if !TARGET_OS_IPHONE
406 TestCase(MYUseIdentity) {
407 MYIdentity *me = nil;//[MYIdentity preferredIdentityForName: @"MYCryptoTest"];
409 NSArray *idents = [[[MYKeychain allKeychains] enumerateIdentities] allObjects];
410 SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
411 [panel setAlternateButtonTitle: @"Cancel"];
412 if ([panel my_runModalForIdentities: idents
413 message: @"Choose an identity for the MYEncoder test case:"]
415 [NSException raise: NSGenericException format: @"User canceled"];
417 me = [panel my_identity];
418 [me makePreferredIdentityForName: @"MYCryptoTest"];
420 CAssert(me,@"No default identity has been set up in the Keychain");
421 TestUseKeyPair(me.privateKey);
427 #pragma mark KEYPAIR EXPORT:
430 static void testKeyPairExportWithPrompt(BOOL withPrompt) {
431 MYKeychain *keychain = [MYKeychain allKeychains];
432 Log(@"Generating key pair...");
433 MYPrivateKey *pair = [keychain generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
435 CAssert(pair.keyRef);
436 CAssert(pair.publicKey.keyRef);
437 Log(@"...created pair.");
440 NSData *pubKeyData = pair.publicKey.keyData;
441 CAssert(pubKeyData.length >= kTestCaseRSAKeySize/8);
442 [pair setName: @"Test KeyPair Label"];
443 CAssertEqual(pair.name, @"Test KeyPair Label");
444 #if !TARGET_OS_IPHONE
445 [pair setComment: @"This key-pair was generated automatically by a test case."];
446 CAssertEqual(pair.comment, @"This key-pair was generated automatically by a test case.");
448 [pair setAlias: @"TestCase@mooseyard.com"];
449 CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
451 #if !TARGET_OS_IPHONE
452 Log(@"Exporting key-pair...");
453 NSString *passphrase = @"passphrase";
456 privKeyData = [pair exportKey];
458 privKeyData = [pair _exportKeyInFormat: kSecFormatWrappedOpenSSL
460 passphrase: passphrase];
461 Log(@"Exported data = %@ (%u bytes)", privKeyData,privKeyData.length);
462 CAssert(privKeyData);
463 [privKeyData writeToFile: @"ExportedPrivKey" atomically: YES];
467 Log(@"Looking up public key of pair in keychain...");
468 MYSHA1Digest *digest = pair.publicKeyDigest;
469 MYPublicKey *foundKey = [keychain publicKeyWithDigest: digest];
470 CAssertEqual(foundKey, pair.publicKey);
471 CAssert([keychain.enumeratePublicKeys.allObjects containsObject: pair.publicKey]);
472 MYPrivateKey *foundPair = [keychain privateKeyWithDigest: digest];
473 CAssertEqual(foundPair, pair);
474 CAssert([keychain.enumeratePrivateKeys.allObjects containsObject: pair]);
476 Log(@"Removing key-pair from keychain...");
477 CAssert([pair removeFromKeychain]);
479 CAssert([keychain publicKeyWithDigest: digest] == nil);
481 #if !TARGET_OS_IPHONE
482 Log(@"Importing key-pair...");
484 pair = [keychain importPublicKey: pubKeyData
485 privateKey: privKeyData];
487 pair = [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
488 publicKeyData: pubKeyData
489 forKeychain: keychain.keychainRefOrDefault
490 passphrase: passphrase]
494 CAssertEqual(pair.publicKey.keyData, pubKeyData);
498 if ([pair removeFromKeychain])
499 Log(@"Removed key-pair from keychain.");
501 Warn(@"Unable to remove test key-pair from keychain");
506 TestCase(KeyPairExport) {
507 RequireTestCase(MYKeychain);
508 RequireTestCase(MYGenerateKeyPair);
509 testKeyPairExportWithPrompt(NO);
512 TestCase(KeyPairExportWithUI) {
513 RequireTestCase(KeyPairExport);
514 testKeyPairExportWithPrompt(YES);