MYPrivateKey.m
author snej@snej.local
Sun Apr 19 21:19:35 2009 -0700 (2009-04-19)
changeset 14 3af1d1c0ceb5
parent 13 6fd9177eb6da
child 17 90a70925562b
permissions -rw-r--r--
* Some cleanup. Got the test cases to pass again.
* Added some missing copyright notices.
     1 //
     2 //  MYPrivateKey.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 4/7/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYPrivateKey.h"
    10 #import "MYCrypto_Private.h"
    11 #import "MYDigest.h"
    12 #import <CommonCrypto/CommonDigest.h>
    13 
    14 #if !TARGET_OS_IPHONE
    15 #import "MYCertGen.h"
    16 #endif
    17 
    18 @implementation MYPrivateKey
    19 
    20 
    21 - (id) initWithKeyRef: (SecKeyRef)privateKey
    22 {
    23     self = [super initWithKeyRef: privateKey];
    24     if (self) {
    25         // No public key given, so look it up:
    26         MYSHA1Digest *digest = self._keyDigest;
    27         if (digest)
    28             _publicKey = [[self.keychain publicKeyWithDigest: digest] retain];
    29         if (!_publicKey) {
    30             // The matching public key won't turn up if it's embedded in a certificate;
    31             // I'd have to search for certs if I wanted to look that up. Skip it for now.
    32             Log(@"MYPrivateKey(%p): Couldn't find matching public key for private key! digest=%@",
    33                 self, digest);
    34             [self release];
    35             return nil;
    36         }
    37     }
    38     return self;
    39 }
    40 
    41 
    42 - (id) _initWithKeyRef: (SecKeyRef)privateKey
    43              publicKey: (MYPublicKey*)publicKey 
    44 {
    45     Assert(publicKey);
    46     self = [super initWithKeyRef: privateKey];
    47     if (self) {
    48         _publicKey = [publicKey retain];
    49     }
    50     return self;
    51 }
    52 
    53 - (id) initWithKeyRef: (SecKeyRef)privateKey
    54          publicKeyRef: (SecKeyRef)publicKeyRef
    55 {
    56     MYPublicKey *publicKey = [[MYPublicKey alloc] initWithKeyRef: publicKeyRef];
    57     self = [self _initWithKeyRef: privateKey publicKey: publicKey];
    58     [publicKey release];
    59     return self;
    60 }
    61 
    62 - (id) _initWithKeyRef: (SecKeyRef)privateKey 
    63          publicKeyData: (NSData*)pubKeyData
    64            forKeychain: (SecKeychainRef)keychain 
    65 {
    66     if (!privateKey) {
    67         [self release];
    68         return nil;
    69     }
    70     MYPublicKey *pubKey = [[MYPublicKey alloc] _initWithKeyData: pubKeyData forKeychain: keychain];
    71     if (!pubKey) {
    72         [self release];
    73         return nil;
    74     }
    75     self = [super initWithKeyRef: privateKey];
    76     if (self) {
    77         _publicKey = pubKey;
    78     } else {
    79         [pubKey removeFromKeychain];
    80         [pubKey release];
    81     }
    82     return self;
    83 }
    84 
    85 
    86 #if !TARGET_OS_IPHONE
    87 
    88 // The public API for this is in MYKeychain.
    89 - (id) _initWithKeyData: (NSData*)privKeyData 
    90           publicKeyData: (NSData*)pubKeyData
    91             forKeychain: (SecKeychainRef)keychain 
    92              alertTitle: (NSString*)title
    93             alertPrompt: (NSString*)prompt
    94 {
    95     // Try to import the private key first, since the user might cancel the passphrase alert.
    96     SecKeyImportExportParameters params = {
    97         .flags = kSecKeySecurePassphrase,
    98         .alertTitle = (CFStringRef) title,
    99         .alertPrompt = (CFStringRef) prompt
   100     };
   101     SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,&params);
   102     return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
   103 }
   104 
   105 // This method is for testing, so unit-tests don't require user intervention.
   106 // It's deliberately not made public, to discourage clients from trying to manage the passphrases
   107 // themselves (this is less secure than letting the Security agent do it.)
   108 - (id) _initWithKeyData: (NSData*)privKeyData 
   109           publicKeyData: (NSData*)pubKeyData
   110             forKeychain: (SecKeychainRef)keychain 
   111              passphrase: (NSString*)passphrase
   112 {
   113     SecKeyImportExportParameters params = {
   114         .passphrase = (CFStringRef) passphrase,
   115     };
   116     SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,&params);
   117     return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
   118 }
   119 
   120 
   121 - (MYIdentity*) createSelfSignedIdentityWithAttributes: (NSDictionary*)attributes {
   122     return MYIdentityCreateSelfSigned(self, attributes);
   123 }
   124 
   125 
   126 #endif
   127 
   128 
   129 - (void) dealloc
   130 {
   131     [_publicKey release];
   132     [super dealloc];
   133 }
   134 
   135 
   136 + (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
   137                                  inKeychain: (MYKeychain*)keychain 
   138 {
   139     Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
   140     SecKeyRef pubKey=NULL, privKey=NULL;
   141     OSStatus err;
   142     
   143 #if MYCRYPTO_USE_IPHONE_API
   144     NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
   145     NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
   146     NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
   147                                     {(id)kSecAttrKeySizeInBits, $object(keySize)},
   148                                     {(id)kSecPublicKeyAttrs, pubKeyAttrs},
   149                                     {(id)kSecPrivateKeyAttrs, privKeyAttrs} );
   150     err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
   151 #else
   152     err = SecKeyCreatePair(keychain.keychainRefOrDefault,
   153                            CSSM_ALGID_RSA, 
   154                            keySize,
   155                            0LL,
   156                            CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,        // public key
   157                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
   158                            CSSM_KEYUSE_ANY,                                 // private key
   159                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
   160                            NULL,                                            // SecAccessRef
   161                            &pubKey, &privKey);
   162 #endif
   163     if (!check(err, @"SecKeyCreatePair")) {
   164         return nil;
   165     } else
   166         return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
   167 }
   168 
   169 
   170 #pragma mark -
   171 #pragma mark ACCESSORS:
   172 
   173 
   174 - (NSString*) description {
   175     return $sprintf(@"%@[%@ %@ /%p]", [self class], 
   176                     self.publicKeyDigest.abbreviatedHexString,
   177                     (self.name ?:@""),
   178                     self.keychainItemRef);
   179 }
   180 
   181 @synthesize publicKey=_publicKey;
   182 
   183 - (MYSHA1Digest*) publicKeyDigest {
   184     return _publicKey.publicKeyDigest;
   185 }
   186 
   187 - (SecExternalItemType) keyType {
   188 #if MYCRYPTO_USE_IPHONE_API
   189     return kSecAttrKeyClassPublic;
   190 #else
   191     return kSecItemTypePrivateKey;
   192 #endif
   193 }
   194 
   195 - (NSData *) keyData {
   196     [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
   197     return nil;
   198 }
   199 
   200 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   201     return [super setValue: valueStr ofAttribute: attr]
   202         && [_publicKey setValue: valueStr ofAttribute: attr];
   203 }
   204 
   205 
   206 #pragma mark -
   207 #pragma mark OPERATIONS:
   208 
   209 
   210 - (BOOL) removeFromKeychain {
   211     return [super removeFromKeychain]
   212         && [_publicKey removeFromKeychain];
   213 }
   214 
   215 
   216 - (NSData*) rawDecryptData: (NSData*)data {
   217     return [self _crypt: data operation: NO];
   218 }
   219 
   220 
   221 - (NSData*) signData: (NSData*)data {
   222     Assert(data);
   223 #if MYCRYPTO_USE_IPHONE_API
   224     uint8_t digest[CC_SHA1_DIGEST_LENGTH];
   225     CC_SHA1(data.bytes,data.length, digest);
   226 
   227     size_t sigLen = 1024;
   228     uint8_t sigBuf[sigLen];
   229     OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
   230                                  digest,sizeof(digest), //data.bytes, data.length,
   231                                  sigBuf, &sigLen);
   232     if(err) {
   233         Warn(@"SecKeyRawSign failed: %i",err);
   234         return nil;
   235     } else
   236         return [NSData dataWithBytes: sigBuf length: sigLen];
   237 #else
   238     NSData *signature = nil;
   239     CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA256WithRSA];
   240     if (!ccHandle) return nil;
   241     CSSM_DATA original = {data.length, (void*)data.bytes};
   242     CSSM_DATA result = {0,NULL};
   243     if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
   244         signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
   245                                    freeWhenDone: YES];
   246     CSSM_DeleteContext(ccHandle);
   247     return signature;
   248 #endif
   249 }
   250 
   251 
   252 #if !TARGET_OS_IPHONE
   253 
   254 - (NSData*) exportKeyInFormat: (SecExternalFormat)format 
   255                       withPEM: (BOOL)withPEM
   256                    alertTitle: (NSString*)title
   257                   alertPrompt: (NSString*)prompt
   258 {
   259     SecKeyImportExportParameters params = {
   260         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   261         .flags = kSecKeySecurePassphrase,
   262         .alertTitle = (CFStringRef)title,
   263         .alertPrompt = (CFStringRef)prompt
   264     };
   265     CFDataRef data = NULL;
   266     if (check(SecKeychainItemExport(self.keyRef,
   267                                     format, (withPEM ?kSecItemPemArmour :0), 
   268                                     &params, &data),
   269               @"SecKeychainItemExport"))
   270         return [(id)CFMakeCollectable(data) autorelease];
   271     else
   272         return nil;
   273 }
   274 
   275 - (NSData*) exportKey {
   276     return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
   277                         alertTitle: @"Export Private Key"
   278                        alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
   279             "You will need to re-enter the passphrase later when importing the key from this file, "
   280             "so keep it in a safe place."];
   281     //FIX: Should make these messages localizable.
   282 }
   283 
   284 
   285 - (NSData*) _exportKeyInFormat: (SecExternalFormat)format
   286                        withPEM: (BOOL)withPEM
   287                     passphrase: (NSString*)passphrase
   288 {
   289     SecKeyImportExportParameters params = {
   290         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   291         .passphrase = (CFStringRef)passphrase
   292     };
   293     CFDataRef data = NULL;
   294     if (check(SecKeychainItemExport(self.keyRef,
   295                                     format, (withPEM ?kSecItemPemArmour :0), 
   296                                     &params, &data),
   297               @"SecKeychainItemExport"))
   298         return [(id)CFMakeCollectable(data) autorelease];
   299     else
   300         return nil;
   301 }
   302 
   303 
   304 - (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
   305                        withAlgorithm: (CCAlgorithm)algorithm
   306                           sizeInBits: (unsigned)sizeInBits
   307 {
   308     // First create a wrapped-key structure from the data:
   309     CSSM_WRAP_KEY wrappedKey = {
   310         .KeyHeader = {
   311             .BlobType = CSSM_KEYBLOB_WRAPPED,
   312             .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
   313             .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
   314             .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
   315             .LogicalKeySizeInBits = sizeInBits,
   316             .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
   317             .KeyUsage = CSSM_KEYUSE_ANY,
   318             .WrapAlgorithmId = self.cssmAlgorithm,
   319         },
   320         .KeyData = {
   321             .Data = (void*)wrappedData.bytes,
   322             .Length = wrappedData.length
   323         }
   324     };
   325         
   326     const CSSM_ACCESS_CREDENTIALS* credentials;
   327     credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
   328                                                type: kSecCredentialTypeDefault error: nil];
   329     CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
   330     CSSM_CC_HANDLE ctx;
   331     if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
   332                                                     self.cssmAlgorithm,
   333                                                     credentials, 
   334                                                     self.cssmKey,
   335                                                     CSSM_PADDING_PKCS1,
   336                                                     &ctx), 
   337                    @"CSSM_CSP_CreateAsymmetricContext"))
   338         return nil;
   339     
   340     // Now unwrap the key:
   341     MYSymmetricKey *result = nil;
   342     CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
   343     CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
   344     CSSM_DATA descriptiveData = {};
   345     if (checkcssm(CSSM_UnwrapKey(ctx, 
   346                                  self.cssmKey,
   347                                  &wrappedKey,
   348                                  wrappedKey.KeyHeader.KeyUsage,
   349                                  wrappedKey.KeyHeader.KeyAttr,
   350                                  &label,
   351                                  NULL,
   352                                  unwrappedKey,
   353                                  &descriptiveData),
   354                   @"CSSM_UnwrapKey")) {
   355         result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
   356     }
   357     // Finally, delete the context
   358     if (!result)
   359         free(unwrappedKey);
   360     CSSM_DeleteContext(ctx);
   361     return result;
   362 }
   363 
   364 
   365 #endif !TARGET_OS_IPHONE
   366 
   367 @end
   368 
   369 
   370 
   371 /*
   372  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   373  
   374  Redistribution and use in source and binary forms, with or without modification, are permitted
   375  provided that the following conditions are met:
   376  
   377  * Redistributions of source code must retain the above copyright notice, this list of conditions
   378  and the following disclaimer.
   379  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   380  and the following disclaimer in the documentation and/or other materials provided with the
   381  distribution.
   382  
   383  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   384  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   385  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   386  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   387  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   388   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   389  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   390  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   391  */