MYPrivateKey.m
author Jens Alfke <jens@mooseyard.com>
Sat Jun 06 15:01:28 2009 -0700 (2009-06-06)
changeset 21 2c300b15b381
parent 17 90a70925562b
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 //  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 #endif
   122 
   123 
   124 - (void) dealloc
   125 {
   126     [_publicKey release];
   127     [super dealloc];
   128 }
   129 
   130 
   131 + (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
   132                                  inKeychain: (MYKeychain*)keychain 
   133 {
   134     Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
   135     SecKeyRef pubKey=NULL, privKey=NULL;
   136     OSStatus err;
   137     
   138 #if MYCRYPTO_USE_IPHONE_API
   139     NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
   140     NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
   141     NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
   142                                     {(id)kSecAttrKeySizeInBits, $object(keySize)},
   143                                     {(id)kSecPublicKeyAttrs, pubKeyAttrs},
   144                                     {(id)kSecPrivateKeyAttrs, privKeyAttrs} );
   145     err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
   146 #else
   147     err = SecKeyCreatePair(keychain.keychainRefOrDefault,
   148                            CSSM_ALGID_RSA, 
   149                            keySize,
   150                            0LL,
   151                            CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,        // public key
   152                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
   153                            CSSM_KEYUSE_ANY,                                 // private key
   154                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
   155                            NULL,                                            // SecAccessRef
   156                            &pubKey, &privKey);
   157 #endif
   158     if (!check(err, @"SecKeyCreatePair")) {
   159         return nil;
   160     } else
   161         return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
   162 }
   163 
   164 
   165 #pragma mark -
   166 #pragma mark ACCESSORS:
   167 
   168 
   169 - (NSString*) description {
   170     return $sprintf(@"%@[%@ %@ /%p]", [self class], 
   171                     self.publicKeyDigest.abbreviatedHexString,
   172                     (self.name ?:@""),
   173                     self.keychainItemRef);
   174 }
   175 
   176 @synthesize publicKey=_publicKey;
   177 
   178 - (MYSHA1Digest*) publicKeyDigest {
   179     return _publicKey.publicKeyDigest;
   180 }
   181 
   182 - (SecExternalItemType) keyType {
   183 #if MYCRYPTO_USE_IPHONE_API
   184     return kSecAttrKeyClassPublic;
   185 #else
   186     return kSecItemTypePrivateKey;
   187 #endif
   188 }
   189 
   190 - (NSData *) keyData {
   191     [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
   192     return nil;
   193 }
   194 
   195 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   196     return [super setValue: valueStr ofAttribute: attr]
   197         && [_publicKey setValue: valueStr ofAttribute: attr];
   198 }
   199 
   200 
   201 #pragma mark -
   202 #pragma mark OPERATIONS:
   203 
   204 
   205 - (BOOL) removeFromKeychain {
   206     return [super removeFromKeychain]
   207         && [_publicKey removeFromKeychain];
   208 }
   209 
   210 
   211 - (NSData*) rawDecryptData: (NSData*)data {
   212     return [self _crypt: data operation: NO];
   213 }
   214 
   215 
   216 - (NSData*) signData: (NSData*)data {
   217     Assert(data);
   218 #if MYCRYPTO_USE_IPHONE_API
   219     uint8_t digest[CC_SHA1_DIGEST_LENGTH];
   220     CC_SHA1(data.bytes,data.length, digest);
   221 
   222     size_t sigLen = 1024;
   223     uint8_t sigBuf[sigLen];
   224     OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
   225                                  digest,sizeof(digest), //data.bytes, data.length,
   226                                  sigBuf, &sigLen);
   227     if(err) {
   228         Warn(@"SecKeyRawSign failed: %i",err);
   229         return nil;
   230     } else
   231         return [NSData dataWithBytes: sigBuf length: sigLen];
   232 #else
   233     NSData *signature = nil;
   234     CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA1WithRSA];
   235     if (!ccHandle) return nil;
   236     CSSM_DATA original = {data.length, (void*)data.bytes};
   237     CSSM_DATA result = {0,NULL};
   238     if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
   239         signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
   240                                    freeWhenDone: YES];
   241     CSSM_DeleteContext(ccHandle);
   242     return signature;
   243 #endif
   244 }
   245 
   246 
   247 #if !TARGET_OS_IPHONE
   248 
   249 - (NSData*) exportKeyInFormat: (SecExternalFormat)format 
   250                       withPEM: (BOOL)withPEM
   251                    alertTitle: (NSString*)title
   252                   alertPrompt: (NSString*)prompt
   253 {
   254     SecKeyImportExportParameters params = {
   255         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   256         .flags = kSecKeySecurePassphrase,
   257         .alertTitle = (CFStringRef)title,
   258         .alertPrompt = (CFStringRef)prompt
   259     };
   260     CFDataRef data = NULL;
   261     if (check(SecKeychainItemExport(self.keyRef,
   262                                     format, (withPEM ?kSecItemPemArmour :0), 
   263                                     &params, &data),
   264               @"SecKeychainItemExport"))
   265         return [(id)CFMakeCollectable(data) autorelease];
   266     else
   267         return nil;
   268 }
   269 
   270 - (NSData*) exportKey {
   271     return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
   272                         alertTitle: @"Export Private Key"
   273                        alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
   274             "You will need to re-enter the passphrase later when importing the key from this file, "
   275             "so keep it in a safe place."];
   276     //FIX: Should make these messages localizable.
   277 }
   278 
   279 
   280 - (NSData*) _exportKeyInFormat: (SecExternalFormat)format
   281                        withPEM: (BOOL)withPEM
   282                     passphrase: (NSString*)passphrase
   283 {
   284     SecKeyImportExportParameters params = {
   285         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   286         .passphrase = (CFStringRef)passphrase
   287     };
   288     CFDataRef data = NULL;
   289     if (check(SecKeychainItemExport(self.keyRef,
   290                                     format, (withPEM ?kSecItemPemArmour :0), 
   291                                     &params, &data),
   292               @"SecKeychainItemExport"))
   293         return [(id)CFMakeCollectable(data) autorelease];
   294     else
   295         return nil;
   296 }
   297 
   298 
   299 - (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
   300                        withAlgorithm: (CCAlgorithm)algorithm
   301                           sizeInBits: (unsigned)sizeInBits
   302 {
   303     // First create a wrapped-key structure from the data:
   304     CSSM_WRAP_KEY wrappedKey = {
   305         .KeyHeader = {
   306             .BlobType = CSSM_KEYBLOB_WRAPPED,
   307             .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
   308             .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
   309             .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
   310             .LogicalKeySizeInBits = sizeInBits,
   311             .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
   312             .KeyUsage = CSSM_KEYUSE_ANY,
   313             .WrapAlgorithmId = self.cssmAlgorithm,
   314         },
   315         .KeyData = {
   316             .Data = (void*)wrappedData.bytes,
   317             .Length = wrappedData.length
   318         }
   319     };
   320         
   321     const CSSM_ACCESS_CREDENTIALS* credentials;
   322     credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
   323                                                type: kSecCredentialTypeDefault error: nil];
   324     CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
   325     CSSM_CC_HANDLE ctx;
   326     if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
   327                                                     self.cssmAlgorithm,
   328                                                     credentials, 
   329                                                     self.cssmKey,
   330                                                     CSSM_PADDING_PKCS1,
   331                                                     &ctx), 
   332                    @"CSSM_CSP_CreateAsymmetricContext"))
   333         return nil;
   334     
   335     // Now unwrap the key:
   336     MYSymmetricKey *result = nil;
   337     CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
   338     CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
   339     CSSM_DATA descriptiveData = {};
   340     if (checkcssm(CSSM_UnwrapKey(ctx, 
   341                                  self.cssmKey,
   342                                  &wrappedKey,
   343                                  wrappedKey.KeyHeader.KeyUsage,
   344                                  wrappedKey.KeyHeader.KeyAttr,
   345                                  &label,
   346                                  NULL,
   347                                  unwrappedKey,
   348                                  &descriptiveData),
   349                   @"CSSM_UnwrapKey")) {
   350         result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
   351     }
   352     // Finally, delete the context
   353     if (!result)
   354         free(unwrappedKey);
   355     CSSM_DeleteContext(ctx);
   356     return result;
   357 }
   358 
   359 
   360 #endif !TARGET_OS_IPHONE
   361 
   362 @end
   363 
   364 
   365 
   366 /*
   367  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   368  
   369  Redistribution and use in source and binary forms, with or without modification, are permitted
   370  provided that the following conditions are met:
   371  
   372  * Redistributions of source code must retain the above copyright notice, this list of conditions
   373  and the following disclaimer.
   374  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   375  and the following disclaimer in the documentation and/or other materials provided with the
   376  distribution.
   377  
   378  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   379  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   380  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   381  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   382  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   383   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   384  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   385  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   386  */