MYCryptoTest.m
author Jens Alfke <jens@mooseyard.com>
Sat Jun 06 15:01:28 2009 -0700 (2009-06-06)
changeset 21 2c300b15b381
parent 16 c409dbc4f068
child 23 39fec79de6e8
permissions -rw-r--r--
* Created class MYCertificateRequest, factored out of MYCertificateInfo.
* Added method to create a MYIdentity directly from a MYCertificateRequest.
* Added raw modulus+exponent accessor and initializer for MYPublicKey.
* Removed obsolete MYCertGen code, and the MYPrivateKey identity-creation method that used it.
     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(EnumerateKeys) {
    47     RequireTestCase(MYKeychain);
    48     NSEnumerator *e = [[MYKeychain allKeychains] enumeratePublicKeys];
    49     Log(@"Public Key Enumerator = %@", e);
    50     CAssert(e);
    51     for (MYPublicKey *key in e) {
    52         Log(@"Found %@ -- name=%@", key, key.name);
    53     }
    54     
    55     e = [[MYKeychain allKeychains] enumeratePrivateKeys];
    56     Log(@"Key-Pair Enumerator = %@", e);
    57     CAssert(e);
    58     for (MYPrivateKey *key in e) {
    59         Log(@"Found %@ -- name=%@", key, key.name);
    60     }
    61     
    62     e = [[MYKeychain allKeychains] enumerateSymmetricKeys];
    63     Log(@"Symmetric Key Enumerator = %@", e);
    64     CAssert(e);
    65     for (MYSymmetricKey *key in e) {
    66         Log(@"Found %@ -- name=%@", key, key.name);
    67     }
    68 }
    69 
    70 
    71 TestCase(EnumerateCerts) {
    72     RequireTestCase(MYKeychain);
    73     NSEnumerator *e = [[MYKeychain allKeychains] enumerateCertificates];
    74     Log(@"Enumerator = %@", e);
    75     CAssert(e);
    76     for (MYCertificate *cert in e) {
    77         //Log(@"Found %@ -- name=%@, email=%@", cert, cert.commonName, cert.emailAddresses);
    78     }
    79 }
    80 
    81 TestCase(EnumerateIdentities) {
    82     RequireTestCase(MYKeychain);
    83     NSEnumerator *e = [[MYKeychain allKeychains] enumerateIdentities];
    84     Log(@"Enumerator = %@", e);
    85     CAssert(e);
    86     for (MYIdentity *ident in e) {
    87         Log(@"Found %@\n\tcommonName=%@\n\temails=(%@)\n\tkey=%@",
    88             ident, ident.commonName, 
    89 #if TARGET_OS_IPHONE
    90             nil,
    91 #else
    92             [ident.emailAddresses componentsJoinedByString: @", "],
    93 #endif
    94             ident.privateKey);
    95     }
    96 }
    97 
    98 
    99 #pragma mark -
   100 #pragma mark SYMMETRIC KEYS:
   101 
   102 
   103 static void testSymmetricKey( CCAlgorithm algorithm, unsigned sizeInBits, MYKeychain *inKeychain ) {
   104     NSAutoreleasePool *pool = [NSAutoreleasePool new];
   105     MYSymmetricKey *key = nil;
   106     @try{
   107         Log(@"--- Testing %3u-bit #%i %s", sizeInBits, (int)algorithm,
   108             (inKeychain ?", in keychain" :""));
   109         // Generate key:
   110         if (inKeychain)
   111             key = [inKeychain generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
   112         else
   113             key = [MYSymmetricKey generateSymmetricKeyOfSize: sizeInBits algorithm: algorithm];
   114         Log(@"Created %@", key);
   115     CAssert(key);
   116         CAssertEq(key.algorithm, algorithm);
   117         CAssertEq(key.keySizeInBits, sizeInBits);
   118     #if !TARGET_OS_IPHONE
   119         CAssert(key.cssmKey != NULL);
   120     #endif
   121         
   122         NSData *keyData = key.keyData;
   123         Log(@"Key data = %@", keyData);
   124         CAssertEq(keyData.length, sizeInBits/8);
   125         
   126         // Encrypt a small amount of text:
   127         Log(@"Testing encryption / decryption ...");
   128         NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
   129         NSData *encrypted = [key encryptData: cleartext];
   130         Log(@"Encrypted = %u bytes: %@", encrypted.length, encrypted);
   131         CAssert(encrypted.length >= cleartext.length);
   132         NSData *decrypted = [key decryptData: encrypted];
   133         CAssertEqual(decrypted, cleartext);
   134         
   135         // Encrypt large binary data:
   136         cleartext = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"];
   137         CAssert(cleartext);
   138         encrypted = [key encryptData: cleartext];
   139         Log(@"Encrypted = %u bytes", encrypted.length);
   140         CAssert(encrypted.length >= cleartext.length);
   141         decrypted = [key decryptData: encrypted];
   142         CAssertEqual(decrypted, cleartext);
   143         
   144     #if 1
   145         Log(@"Testing initWithKeyData:...");
   146         MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithKeyData: keyData algorithm: algorithm];
   147         CAssert(key2);
   148         Log(@"Key from data = %@",key2);
   149         CAssertEqual(key2.keyData, keyData);
   150         CAssertEq(key2.algorithm, algorithm);
   151         CAssertEq(key2.keySizeInBits, sizeInBits);
   152         decrypted = [key2 decryptData: encrypted];
   153         CAssertEqual(decrypted, cleartext);
   154         [key2 release];
   155     #endif
   156 
   157     #if !TARGET_OS_IPHONE
   158 #if 1 // TEMP-ORARILY OUT OF ORDER
   159         // Try exporting and importing a wrapped key:
   160         Log(@"Testing export/import...");
   161         NSData *exported = [key exportWrappedKeyWithPassphrasePrompt: @"Export symmetric key with passphrase:"];
   162         Log(@"Exported key: %@", exported);
   163     #if 1
   164         CAssert(exported);
   165     #else
   166         if (!exported)
   167             Warn(@"Unable to export wrapped key");
   168         else
   169     #endif
   170         {
   171             CAssert(exported);
   172             MYSymmetricKey *key2 = [[MYSymmetricKey alloc] initWithWrappedKeyData: exported];
   173             Log(@"Reconstituted as %@", key2);
   174             CAssertEqual(key2.keyData,key.keyData);
   175             decrypted = [key2 decryptData: encrypted];
   176             CAssertEqual(decrypted, cleartext);
   177         }
   178 #endif 0
   179     #endif
   180     }@finally{
   181         [key removeFromKeychain];
   182     }
   183     [pool drain];
   184 }
   185 
   186 
   187 TestCase(MYSymmetricKey) {
   188     #define kNTests 11
   189     static const CCAlgorithm kTestAlgorithms[kNTests] = {
   190         kCCAlgorithmAES128, kCCAlgorithmAES128, kCCAlgorithmAES128,
   191         kCCAlgorithmDES, kCCAlgorithm3DES,
   192         kCCAlgorithmCAST, kCCAlgorithmCAST, kCCAlgorithmCAST,
   193         kCCAlgorithmRC4, kCCAlgorithmRC4, kCCAlgorithmRC4};
   194     
   195     static const unsigned kTestBitSizes[kNTests] = {
   196         128, 192, 256,
   197         64, 3*64,
   198         40, 80, 128,
   199         32, 200, 512*8};
   200 
   201     for (int useKeychain=0; useKeychain<=1; useKeychain++)
   202         for (int testNo=0; testNo<kNTests; testNo++) 
   203             testSymmetricKey(kTestAlgorithms[testNo], 
   204                              kTestBitSizes[testNo],
   205                              useKeychain ?[MYKeychain defaultKeychain] :nil);
   206 }
   207 
   208 
   209 #if !TARGET_OS_IPHONE
   210 TestCase(MYSymmetricKeyPassphrase) {
   211     Log(@"Prompting for raw passphrase --");
   212     NSString *rawPassphrase = [MYSymmetricKey promptForPassphraseWithAlertTitle: @"Raw Passphrase Test" 
   213                                                                     alertPrompt: @"Enter the passphrase 'Testing':"
   214                                                                        creating: YES];
   215     Log(@"You entered: '%@'", rawPassphrase);
   216     CAssertEqual(rawPassphrase, @"Testing");
   217     
   218     Log(@"Prompting for passphrase for key --");
   219     MYSymmetricKey *key = [MYSymmetricKey generateFromUserPassphraseWithAlertTitle: @"Symmetric Key Passphrase Test Case" 
   220                                                                        alertPrompt: @"Please enter a passphrase to generate a key:"
   221                                                                           creating: YES
   222                                                                               salt: @"wahooma"];
   223     Log(@"Key from passphrase = %@", key);
   224     CAssert(key);
   225 
   226     // Encrypt a small amount of text:
   227     Log(@"Testing encryption / decryption ...");
   228     NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
   229     NSData *encrypted = [key encryptData: cleartext];
   230     Log(@"Encrypted = %u bytes: %@", encrypted.length, encrypted);
   231     CAssert(encrypted.length >= cleartext.length);
   232     NSData *decrypted = [key decryptData: encrypted];
   233     CAssertEqual(decrypted, cleartext);
   234     
   235     // Now test decryption by re-entered passphrase:
   236     Log(@"Testing decryption using re-entered passphrase...");
   237     MYSymmetricKey *key2 = [MYSymmetricKey generateFromUserPassphraseWithAlertTitle: @"Symmetric Key Passphrase Test Case" 
   238                                                                         alertPrompt: @"Please re-enter the same passphrase:" 
   239                                                                            creating: NO
   240                                                                                salt: @"wahooma"];
   241     Log(@"Key from passphrase = %@", key2);
   242     CAssert(key2);
   243     decrypted = [key2 decryptData: encrypted];
   244     CAssertEqual(decrypted, cleartext);
   245 }
   246 #endif
   247 
   248 
   249 #pragma mark -
   250 #pragma mark KEY-PAIRS:
   251 
   252 
   253 static void TestUseKeyPair(MYPrivateKey *pair) {
   254     Log(@"---- TestUseKeyPair { %@ , %@ }.", pair, pair.publicKey);
   255     CAssert(pair);
   256     CAssert(pair.keyRef);
   257     MYPublicKey *publicKey = pair.publicKey;
   258     CAssert(publicKey.keyRef);
   259     
   260     NSData *pubKeyData = publicKey.keyData;
   261     Log(@"Public key = %@ (%u bytes)",pubKeyData,pubKeyData.length);
   262     CAssert(pubKeyData);
   263     
   264     NSData *modulus;
   265     unsigned exponent;
   266     CAssert([publicKey getModulus: &modulus exponent: &exponent]);
   267     Log(@"Modulus = %@", modulus);
   268     Log(@"Exponent = %u", exponent);
   269     CAssertEq(modulus.length, 2048U/8);
   270     CAssertEq(exponent,65537U); // this is what CDSA always seems to use
   271     
   272     MYSHA1Digest *pubKeyDigest = publicKey.publicKeyDigest;
   273     Log(@"Public key digest = %@",pubKeyDigest);
   274     CAssertEqual(pair.publicKeyDigest, pubKeyDigest);
   275     
   276     Log(@"SHA1 of pub key = %@", pubKeyData.my_SHA1Digest.asData);
   277     CAssertEqual(pubKeyData.my_SHA1Digest,pubKeyDigest);
   278     
   279     // Let's sign data:
   280     NSData *data = [@"This is a test. This is only a test!" dataUsingEncoding: NSUTF8StringEncoding];
   281     NSData *sig = [pair signData: data];
   282     Log(@"Signature = %@ (%u bytes)",sig,sig.length);
   283     CAssert(sig);
   284     CAssert( [publicKey verifySignature: sig ofData: data] );
   285     
   286     // Now let's encrypt...
   287     NSData *crypted = [publicKey rawEncryptData: data];
   288     Log(@"Encrypted = %@ (%u bytes)",crypted,crypted.length);
   289     CAssert(crypted);
   290     CAssertEqual([pair rawDecryptData: crypted], data);
   291     Log(@"Verified decryption.");
   292     
   293     // Test creating a standalone public key:
   294     MYPublicKey *pub = [[MYPublicKey alloc] initWithKeyRef: publicKey.keyRef];
   295     CAssert( [pub verifySignature: sig ofData: data] );
   296     Log(@"Verified signature.");
   297     
   298     // Test creating a public key from data:
   299     Log(@"Reconstituting public key from data...");
   300     pub = [[MYPublicKey alloc] initWithKeyData: pubKeyData];
   301     CAssert(pub);
   302     CAssertEqual(pub.keyData, pubKeyData);
   303     CAssertEqual(pub.publicKeyDigest, pubKeyDigest);
   304     CAssert( [pub verifySignature: sig ofData: data] );
   305     [pub release];
   306     Log(@"Verified signature from reconstituted key.");
   307     
   308     // Test creating a public key from modulus+exponent:
   309     Log(@"Reconstituting public key from modulus+exponent...");
   310     pub = [[MYPublicKey alloc] initWithModulus: modulus exponent: exponent];
   311     CAssertEqual(pub.keyData, pubKeyData);
   312     [pub release];
   313 }
   314 
   315 
   316 static void TestWrapSessionKey( MYPrivateKey *privateKey ) {
   317 #if !TARGET_OS_IPHONE
   318     MYSymmetricKey *sessionKey = [MYSymmetricKey generateSymmetricKeyOfSize: 128 algorithm:kCCAlgorithmAES128];
   319     CAssert(sessionKey);
   320     NSData *cleartext = [@"This is a test. This is only a test." dataUsingEncoding: NSUTF8StringEncoding];
   321     NSData *encrypted = [sessionKey encryptData: cleartext];
   322 
   323     Log(@"Wrapping session key %@, %@", sessionKey, sessionKey.keyData);
   324     NSData *wrapped = [privateKey.publicKey wrapSessionKey: sessionKey];
   325     Log(@"Wrapped session key = %u bytes: %@", wrapped.length,wrapped);
   326     CAssert(wrapped.length >= 128/8);
   327     
   328     MYSymmetricKey *unwrappedKey = [privateKey unwrapSessionKey: wrapped
   329                                                   withAlgorithm: kCCAlgorithmAES128
   330                                                      sizeInBits: 128];
   331     Log(@"Unwrapped session key = %@, %@", unwrappedKey, unwrappedKey.keyData);
   332     CAssert(unwrappedKey);
   333     CAssertEq(unwrappedKey.algorithm, sessionKey.algorithm);
   334     CAssertEq(unwrappedKey.keySizeInBits, sessionKey.keySizeInBits);
   335     CAssertEqual(unwrappedKey.keyData, sessionKey.keyData);
   336 
   337     Log(@"Verifying that unwrapped key works");
   338     NSData *decrypted = [unwrappedKey decryptData: encrypted];
   339     CAssertEqual(decrypted, cleartext);
   340 #endif
   341 }
   342 
   343 
   344 TestCase(MYGenerateKeyPair) {
   345     RequireTestCase(MYKeychain);
   346     
   347     Log(@"Generating key pair...");
   348     MYPrivateKey *pair = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
   349     MYPublicKey *publicKey = pair.publicKey;
   350     Log(@"...created { %@ , %@ }.", pair, publicKey);
   351     
   352     @try{
   353         TestUseKeyPair(pair);
   354         TestWrapSessionKey(pair);
   355         
   356         [pair setName: @"Test KeyPair Label"];
   357         CAssertEqual(pair.name, @"Test KeyPair Label");
   358         CAssertEqual(publicKey.name, @"Test KeyPair Label");
   359 #if !TARGET_OS_IPHONE
   360         [pair setComment: @"This key-pair was generated automatically by a test case."];
   361         CAssertEqual(pair.comment, @"This key-pair was generated automatically by a test case.");
   362         CAssertEqual(publicKey.comment, @"This key-pair was generated automatically by a test case.");
   363 #endif
   364         [pair setAlias: @"TestCase@mooseyard.com"];
   365         CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
   366         CAssertEqual(publicKey.alias, @"TestCase@mooseyard.com");
   367         
   368         CAssert([pair removeFromKeychain]);
   369         Log(@"Removed key-pair.");
   370         pair = nil;
   371         
   372     }@finally {
   373         if (pair) {
   374             if ([pair removeFromKeychain])
   375                 Log(@"Removed key-pair from keychain.");
   376             else
   377                 Warn(@"Unable to remove test key-pair from keychain");
   378         }
   379     }
   380 }
   381 
   382 
   383 #if !TARGET_OS_IPHONE
   384 TestCase(MYUseIdentity) {
   385     MYIdentity *me = nil;//[MYIdentity preferredIdentityForName: @"MYCryptoTest"];
   386     if (!me) {
   387         NSArray *idents = [[[MYKeychain allKeychains] enumerateIdentities] allObjects];
   388         SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
   389         [panel setAlternateButtonTitle: @"Cancel"];
   390         if ([panel my_runModalForIdentities: idents 
   391                                     message: @"Choose an identity for the MYEncoder test case:"]
   392             != NSOKButton) {
   393             [NSException raise: NSGenericException format: @"User canceled"];
   394         }
   395         me = [panel my_identity];
   396         [me makePreferredIdentityForName: @"MYCryptoTest"];
   397     }
   398     CAssert(me,@"No default identity has been set up in the Keychain");
   399     TestUseKeyPair(me.privateKey);
   400 }
   401 #endif
   402 
   403 
   404 #pragma mark -
   405 #pragma mark KEYPAIR EXPORT:
   406 
   407 
   408 static void testKeyPairExportWithPrompt(BOOL withPrompt) {
   409     MYKeychain *keychain = [MYKeychain allKeychains];
   410     Log(@"Generating key pair...");
   411     MYPrivateKey *pair = [keychain generateRSAKeyPairOfSize: kTestCaseRSAKeySize];
   412     CAssert(pair);
   413     CAssert(pair.keyRef);
   414     CAssert(pair.publicKey.keyRef);
   415     Log(@"...created pair.");
   416     
   417     @try{
   418         NSData *pubKeyData = pair.publicKey.keyData;
   419         CAssert(pubKeyData.length >= kTestCaseRSAKeySize/8);
   420         [pair setName: @"Test KeyPair Label"];
   421         CAssertEqual(pair.name, @"Test KeyPair Label");
   422 #if !TARGET_OS_IPHONE
   423         [pair setComment: @"This key-pair was generated automatically by a test case."];
   424         CAssertEqual(pair.comment, @"This key-pair was generated automatically by a test case.");
   425 #endif
   426         [pair setAlias: @"TestCase@mooseyard.com"];
   427         CAssertEqual(pair.alias, @"TestCase@mooseyard.com");
   428         
   429 #if !TARGET_OS_IPHONE
   430         Log(@"Exporting key-pair...");
   431         NSString *passphrase = @"passphrase";
   432         NSData *privKeyData;
   433         if (withPrompt)
   434             privKeyData = [pair exportKey];
   435         else
   436             privKeyData = [pair _exportKeyInFormat: kSecFormatWrappedOpenSSL
   437                                           withPEM: YES
   438                                        passphrase: passphrase];
   439         Log(@"Exported data = %@ (%u bytes)", privKeyData,privKeyData.length);
   440         CAssert(privKeyData);
   441         [privKeyData writeToFile: @"ExportedPrivKey" atomically: YES];
   442 #endif
   443         
   444         // Check key lookup:
   445         Log(@"Looking up public key of pair in keychain...");
   446         MYSHA1Digest *digest = pair.publicKeyDigest;
   447         MYPublicKey *foundKey = [keychain publicKeyWithDigest: digest];
   448         CAssertEqual(foundKey, pair.publicKey);
   449         CAssert([keychain.enumeratePublicKeys.allObjects containsObject: pair.publicKey]);
   450         MYPrivateKey *foundPair = [keychain privateKeyWithDigest: digest];
   451         CAssertEqual(foundPair, pair);
   452         CAssert([keychain.enumeratePrivateKeys.allObjects containsObject: pair]);
   453         
   454         Log(@"Removing key-pair from keychain...");
   455         CAssert([pair removeFromKeychain]);
   456         pair = nil;
   457         CAssert([keychain publicKeyWithDigest: digest] == nil);
   458         
   459 #if !TARGET_OS_IPHONE
   460         Log(@"Importing key-pair...");
   461         if (withPrompt) {
   462             pair = [keychain importPublicKey: pubKeyData 
   463                                   privateKey: privKeyData];
   464         } else {
   465             pair = [[[MYPrivateKey alloc] _initWithKeyData: privKeyData
   466                                              publicKeyData: pubKeyData
   467                                                forKeychain: keychain.keychainRefOrDefault
   468                                                 passphrase: passphrase]
   469                     autorelease];
   470         }
   471         CAssert(pair);
   472         CAssertEqual(pair.publicKey.keyData, pubKeyData);
   473 #endif
   474     }@finally {
   475         if (pair) {
   476             if ([pair removeFromKeychain])
   477                 Log(@"Removed key-pair from keychain.");
   478             else
   479                 Warn(@"Unable to remove test key-pair from keychain");
   480         }
   481     }
   482 }
   483 
   484 TestCase(KeyPairExport) {
   485     RequireTestCase(MYKeychain);
   486     RequireTestCase(MYGenerateKeyPair);
   487     testKeyPairExportWithPrompt(NO);
   488 }
   489 
   490 TestCase(KeyPairExportWithUI) {
   491     RequireTestCase(KeyPairExport);
   492     testKeyPairExportWithPrompt(YES);
   493 }
   494 
   495 
   496 #endif DEBUG
   497