MYCryptoTest.m
author Jens Alfke <jens@mooseyard.com>
Sun Jun 07 21:53:56 2009 -0700 (2009-06-07)
changeset 23 39fec79de6e8
parent 21 2c300b15b381
child 26 d9c2a06d4e4e
permissions -rw-r--r--
A snapshot taken during the long, agonizing crawl toward getting everything running on iPhone.
     1 //
     2 //  MYCryptoTest.m
     3 //  MYCrypto-iPhone
     4 //
     5 //  Created by Jens Alfke on 4/1/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYPublicKey.h"
    10 #import "MYPrivateKey.h"
    11 #import "MYKeychain.h"
    12 #import "MYDigest.h"
    13 #import "MYIdentity.h"
    14 #if !TARGET_OS_IPHONE
    15 #import "MYCrypto+Cocoa.h"
    16 #endif
    17 #import "MYCrypto_Private.h"
    18 
    19 
    20 #if DEBUG
    21 
    22 
    23 #define kTestCaseRSAKeySize 2048
    24 
    25 #pragma mark -
    26 #pragma mark KEYCHAIN:
    27 
    28 
    29 TestCase(MYKeychain) {
    30     MYKeychain *kc = [MYKeychain defaultKeychain];
    31     Log(@"Default keychain = %@", kc);
    32     CAssert(kc);
    33 #if !MYCRYPTO_USE_IPHONE_API
    34     CAssert(kc.path);
    35 #endif
    36     
    37     kc = [MYKeychain allKeychains];
    38     Log(@"All-keychains = %@", kc);
    39     CAssert(kc);
    40 #if !MYCRYPTO_USE_IPHONE_API
    41     CAssertEq(kc.path,nil);
    42 #endif
    43 }
    44 
    45 
    46 TestCase(Enumerate) {
    47     RequireTestCase(EnumeratePublicKeys);
    48     RequireTestCase(EnumeratePrivateKeys);
    49     RequireTestCase(EnumerateSymmetricKeys);
    50     RequireTestCase(EnumerateCerts);
    51     RequireTestCase(EnumerateIdentities);
    52 }
    53 
    54 
    55 TestCase(EnumeratePublicKeys) {
    56     RequireTestCase(MYKeychain);
    57     NSEnumerator *e = [[MYKeychain allKeychains] enumeratePublicKeys];
    58     Log(@"Public Key Enumerator = %@", e);
    59     CAssert(e);
    60     for (MYPublicKey *key in e) {
    61         Log(@"Trying public key %@", key.keyRef);
    62         @try{
    63             Log(@"Found %@ -- name=%@", key, key.name);
    64         }@catch (NSException *x) {
    65             Warn(@"Caught %@",x); //TEMP
    66         }
    67     }
    68 }
    69 
    70 TestCase(EnumeratePrivateKeys) {
    71     RequireTestCase(MYKeychain);
    72     NSEnumerator *e = [[MYKeychain allKeychains] enumeratePrivateKeys];
    73     Log(@"Key-Pair Enumerator = %@", e);
    74     CAssert(e);
    75     for (MYPrivateKey *key in e) {
    76         Log(@"Found %@ -- name=%@ --> %@", key, key.name, key.publicKey);
    77         CAssert(key.publicKey);
    78     }
    79 }
    80 
    81 TestCase(EnumerateSymmetricKeys) {
    82     RequireTestCase(MYKeychain);
    83     NSEnumerator *e = [[MYKeychain allKeychains] enumerateSymmetricKeys];
    84     Log(@"Symmetric Key Enumerator = %@", e);
    85     CAssert(e);
    86     for (MYSymmetricKey *key in e) {
    87         Log(@"Found %@ -- name=%@", key, key.name);
    88     }
    89 }
    90 
    91 
    92 TestCase(EnumerateCerts) {
    93     RequireTestCase(MYKeychain);
    94     NSEnumerator *e = [[MYKeychain allKeychains] enumerateCertificates];
    95     Log(@"Enumerator = %@", e);
    96     CAssert(e);
    97     for (MYCertificate *cert in e) {
    98         Log(@"Found %@ -- name=%@, email=%@", cert, cert.commonName, cert.emailAddresses);
    99         CAssert(cert.publicKey);
   100     }
   101 }
   102 
   103 TestCase(EnumerateIdentities) {
   104     RequireTestCase(MYKeychain);
   105     NSEnumerator *e = [[MYKeychain allKeychains] enumerateIdentities];
   106     Log(@"Enumerator = %@", e);
   107     CAssert(e);
   108     for (MYIdentity *ident in e) {
   109         Log(@"Found %@\n\tcommonName=%@\n\temails=(%@)\n\tkey=%@",
   110             ident, ident.commonName, 
   111 #if TARGET_OS_IPHONE
   112             nil,
   113 #else
   114             [ident.emailAddresses componentsJoinedByString: @", "],
   115 #endif
   116             ident.privateKey);
   117     }
   118 }
   119 
   120 
   121 #pragma mark -
   122 #pragma mark SYMMETRIC KEYS:
   123 
   124 
   125 static void testSymmetricKey( CCAlgorithm algorithm, unsigned sizeInBits, MYKeychain *inKeychain ) {
   126     NSAutoreleasePool *pool = [NSAutoreleasePool new];
   127     MYSymmetricKey *key = nil;
   128     @try{
   129         Log(@"--- Testing %3u-bit #%i %s", sizeInBits, (int)algorithm,
   130             (inKeychain ?", in keychain" :""));
   131         // Generate key:
   132         if (inKeychain)
   133             key = [inKeychain generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
   134         else
   135             key = [MYSymmetricKey generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
   136         Log(@"Created %@", key);
   137         CAssert(key);
   138         CAssertEq(key.algorithm, algorithm);
   139         CAssertEq(key.keySizeInBits, sizeInBits);
   140     #if !TARGET_OS_IPHONE
   141         CAssert(key.cssmKey != NULL);
   142     #endif
   143         
   144         NSData *keyData = key.keyData;
   145         Log(@"Key data = %@", keyData);
   146         CAssertEq(keyData.length, sizeInBits/8);
   147         
   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);
   156         
   157         // Encrypt large binary data:
   158         cleartext = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
   159         CAssert(cleartext);
   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);
   165         
   166     #if 1
   167         Log(@"Testing initWithKeyData:...");
   168         MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithKeyData: keyData algorithm: algorithm];
   169         CAssert(key2);
   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);
   176         [key2 release];
   177     #endif
   178 
   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);
   185     #if 1
   186         CAssert(exported);
   187     #else
   188         if (!exported)
   189             Warn(@"Unable to export wrapped key");
   190         else
   191     #endif
   192         {
   193             CAssert(exported);
   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);
   199         }
   200 #endif 0
   201     #endif
   202     }@finally{
   203         [key removeFromKeychain];
   204     }
   205     [pool drain];
   206 }
   207 
   208 
   209 TestCase(MYSymmetricKey) {
   210     #define kNTests 11
   211     static const CCAlgorithm kTestAlgorithms[kNTests] = {
   212         kCCAlgorithmAES128, kCCAlgorithmAES128, kCCAlgorithmAES128,
   213         kCCAlgorithmDES, kCCAlgorithm3DES,
   214         kCCAlgorithmCAST, kCCAlgorithmCAST, kCCAlgorithmCAST,
   215         kCCAlgorithmRC4, kCCAlgorithmRC4, kCCAlgorithmRC4};
   216     
   217     static const unsigned kTestBitSizes[kNTests] = {
   218         128, 192, 256,
   219         64, 3*64,
   220         40, 80, 128,
   221         32, 200, 512*8};
   222 
   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);
   228 }
   229 
   230 
   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':"
   236                                                                        creating: YES];
   237     Log(@"You entered: '%@'", rawPassphrase);
   238     CAssertEqual(rawPassphrase, @"Testing");
   239     
   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:"
   243                                                                           creating: YES
   244                                                                               salt: @"wahooma"];
   245     Log(@"Key from passphrase = %@", key);
   246     CAssert(key);
   247 
   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);
   256     
   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:" 
   261                                                                            creating: NO
   262                                                                                salt: @"wahooma"];
   263     Log(@"Key from passphrase = %@", key2);
   264     CAssert(key2);
   265     decrypted = [key2 decryptData: encrypted];
   266     CAssertEqual(decrypted, cleartext);
   267 }
   268 #endif
   269 
   270 
   271 #pragma mark -
   272 #pragma mark KEY-PAIRS:
   273 
   274 
   275 static void TestUseKeyPair(MYPrivateKey *pair) {
   276     Log(@"---- TestUseKeyPair { %@ , %@ }.", pair, pair.publicKey);
   277     CAssert(pair);
   278     CAssert(pair.keyRef);
   279     MYPublicKey *publicKey = pair.publicKey;
   280     CAssert(publicKey.keyRef);
   281     
   282     NSData *pubKeyData = publicKey.keyData;
   283     Log(@"Public key = %@ (%u bytes)",pubKeyData,pubKeyData.length);
   284     CAssert(pubKeyData);
   285     
   286     NSData *modulus;
   287     unsigned exponent;
   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
   293     
   294     MYSHA1Digest *pubKeyDigest = publicKey.publicKeyDigest;
   295     Log(@"Public key digest = %@",pubKeyDigest);
   296     CAssertEqual(pair.publicKeyDigest, pubKeyDigest);
   297     
   298     Log(@"SHA1 of pub key = %@", pubKeyData.my_SHA1Digest.asData);
   299     CAssertEqual(pubKeyData.my_SHA1Digest,pubKeyDigest);
   300     
   301     // Let's sign data:
   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);
   305     CAssert(sig);
   306     CAssert( [publicKey verifySignature: sig ofData: data] );
   307     
   308     // Now let's encrypt...
   309     NSData *crypted = [publicKey rawEncryptData: data];
   310     Log(@"Encrypted = %@ (%u bytes)",crypted,crypted.length);
   311     CAssert(crypted);
   312     CAssertEqual([pair rawDecryptData: crypted], data);
   313     Log(@"Verified decryption.");
   314     
   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.");
   319     
   320     // Test creating a public key from data:
   321     Log(@"Reconstituting public key from data...");
   322     pub = [[MYPublicKey alloc] initWithKeyData: pubKeyData];
   323     CAssert(pub);
   324     CAssertEqual(pub.keyData, pubKeyData);
   325     CAssertEqual(pub.publicKeyDigest, pubKeyDigest);
   326     CAssert( [pub verifySignature: sig ofData: data] );
   327     [pub release];
   328     Log(@"Verified signature from reconstituted key.");
   329     
   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);
   334     [pub release];
   335 }
   336 
   337 
   338 static void TestWrapSessionKey( MYPrivateKey *privateKey ) {
   339 #if !TARGET_OS_IPHONE
   340     MYSymmetricKey *sessionKey = [MYSymmetricKey generateSymmetricKeyOfSize: 128 algorithm:kCCAlgorithmAES128];
   341     CAssert(sessionKey);
   342     NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
   343     NSData *encrypted = [sessionKey encryptData: cleartext];
   344 
   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);
   349     
   350     MYSymmetricKey *unwrappedKey = [privateKey unwrapSessionKey: wrapped
   351                                                   withAlgorithm: kCCAlgorithmAES128
   352                                                      sizeInBits: 128];
   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);
   358 
   359     Log(@"Verifying that unwrapped key works");
   360     NSData *decrypted = [unwrappedKey decryptData: encrypted];
   361     CAssertEqual(decrypted, cleartext);
   362 #endif
   363 }
   364 
   365 
   366 TestCase(MYGenerateKeyPair) {
   367     RequireTestCase(MYKeychain);
   368     
   369     Log(@"Generating key pair...");
   370     MYPrivateKey *pair = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
   371     MYPublicKey *publicKey = pair.publicKey;
   372     Log(@"...created { %@ , %@ }.", pair, publicKey);
   373     
   374     @try{
   375         TestUseKeyPair(pair);
   376         TestWrapSessionKey(pair);
   377         
   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.");
   385 #endif
   386         [pair setAlias: @"TestCase@mooseyard.com"];
   387         CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
   388         CAssertEqual(publicKey.alias, @"TestCase@mooseyard.com");
   389         
   390         CAssert([pair removeFromKeychain]);
   391         Log(@"Removed key-pair.");
   392         pair = nil;
   393         
   394     }@finally {
   395         if (pair) {
   396             if ([pair removeFromKeychain])
   397                 Log(@"Removed key-pair from keychain.");
   398             else
   399                 Warn(@"Unable to remove test key-pair from keychain");
   400         }
   401     }
   402 }
   403 
   404 
   405 #if !TARGET_OS_IPHONE
   406 TestCase(MYUseIdentity) {
   407     MYIdentity *me = nil;//[MYIdentity preferredIdentityForName: @"MYCryptoTest"];
   408     if (!me) {
   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:"]
   414             != NSOKButton) {
   415             [NSException raise: NSGenericException format: @"User canceled"];
   416         }
   417         me = [panel my_identity];
   418         [me makePreferredIdentityForName: @"MYCryptoTest"];
   419     }
   420     CAssert(me,@"No default identity has been set up in the Keychain");
   421     TestUseKeyPair(me.privateKey);
   422 }
   423 #endif
   424 
   425 
   426 #pragma mark -
   427 #pragma mark KEYPAIR EXPORT:
   428 
   429 
   430 static void testKeyPairExportWithPrompt(BOOL withPrompt) {
   431     MYKeychain *keychain = [MYKeychain allKeychains];
   432     Log(@"Generating key pair...");
   433     MYPrivateKey *pair = [keychain generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
   434     CAssert(pair);
   435     CAssert(pair.keyRef);
   436     CAssert(pair.publicKey.keyRef);
   437     Log(@"...created pair.");
   438     
   439     @try{
   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.");
   447 #endif
   448         [pair setAlias: @"TestCase@mooseyard.com"];
   449         CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
   450         
   451 #if !TARGET_OS_IPHONE
   452         Log(@"Exporting key-pair...");
   453         NSString *passphrase = @"passphrase";
   454         NSData *privKeyData;
   455         if (withPrompt)
   456             privKeyData = [pair exportKey];
   457         else
   458             privKeyData = [pair _exportKeyInFormat: kSecFormatWrappedOpenSSL
   459                                           withPEM: YES
   460                                        passphrase: passphrase];
   461         Log(@"Exported data = %@ (%u bytes)", privKeyData,privKeyData.length);
   462         CAssert(privKeyData);
   463         [privKeyData writeToFile: @"ExportedPrivKey" atomically: YES];
   464 #endif
   465         
   466         // Check key lookup:
   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]);
   475         
   476         Log(@"Removing key-pair from keychain...");
   477         CAssert([pair removeFromKeychain]);
   478         pair = nil;
   479         CAssert([keychain publicKeyWithDigest: digest] == nil);
   480         
   481 #if !TARGET_OS_IPHONE
   482         Log(@"Importing key-pair...");
   483         if (withPrompt) {
   484             pair = [keychain importPublicKey: pubKeyData 
   485                                   privateKey: privKeyData];
   486         } else {
   487             pair = [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
   488                                              publicKeyData: pubKeyData
   489                                                forKeychain: keychain.keychainRefOrDefault
   490                                                 passphrase: passphrase]
   491                     autorelease];
   492         }
   493         CAssert(pair);
   494         CAssertEqual(pair.publicKey.keyData, pubKeyData);
   495 #endif
   496     }@finally {
   497         if (pair) {
   498             if ([pair removeFromKeychain])
   499                 Log(@"Removed key-pair from keychain.");
   500             else
   501                 Warn(@"Unable to remove test key-pair from keychain");
   502         }
   503     }
   504 }
   505 
   506 TestCase(KeyPairExport) {
   507     RequireTestCase(MYKeychain);
   508     RequireTestCase(MYGenerateKeyPair);
   509     testKeyPairExportWithPrompt(NO);
   510 }
   511 
   512 TestCase(KeyPairExportWithUI) {
   513     RequireTestCase(KeyPairExport);
   514     testKeyPairExportWithPrompt(YES);
   515 }
   516 
   517 
   518 #endif DEBUG
   519