MYPrivateKey.m
author Jens Alfke <jens@mooseyard.com>
Tue Jul 21 10:13:08 2009 -0700 (2009-07-21)
changeset 27 d0aadddb9c64
parent 23 39fec79de6e8
permissions -rw-r--r--
MYCertificate now checks validity of self-signed certs loaded from the keychain (because the Security framework doesn't validate self-signed certs.)
     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*) _keyDigest {
   179     if (_publicKey)
   180         return _publicKey.publicKeyDigest;
   181     else {
   182         NSData *digestData;
   183 #if MYCRYPTO_USE_IPHONE_API
   184         digestData = [self _attribute: kSecAttrApplicationLabel];
   185 #else
   186         digestData = [[self class] _getAttribute: kSecKeyLabel 
   187                                           ofItem: (SecKeychainItemRef)self.keyRef]; 
   188 #endif
   189         return [MYSHA1Digest digestFromDigestData: digestData];
   190     }
   191 }
   192 
   193 - (MYSHA1Digest*) publicKeyDigest {
   194     return self._keyDigest;
   195 }
   196 
   197 - (SecExternalItemType) keyClass {
   198 #if MYCRYPTO_USE_IPHONE_API
   199     return kSecAttrKeyClassPublic;
   200 #else
   201     return kSecItemTypePrivateKey;
   202 #endif
   203 }
   204 
   205 #if MYCRYPTO_USE_IPHONE_API
   206 - (SecExternalItemType) keyType {
   207     return kSecAttrKeyTypeRSA;
   208 }
   209 #endif
   210 
   211 - (NSData *) keyData {
   212     [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
   213     return nil;
   214 }
   215 
   216 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   217     return [super setValue: valueStr ofAttribute: attr]
   218         && [_publicKey setValue: valueStr ofAttribute: attr];
   219 }
   220 
   221 
   222 #pragma mark -
   223 #pragma mark OPERATIONS:
   224 
   225 
   226 - (BOOL) removeFromKeychain {
   227     return [super removeFromKeychain]
   228         && [_publicKey removeFromKeychain];
   229 }
   230 
   231 
   232 - (NSData*) rawDecryptData: (NSData*)data {
   233     return [self _crypt: data operation: NO];
   234 }
   235 
   236 
   237 - (NSData*) signData: (NSData*)data {
   238     Assert(data);
   239 #if MYCRYPTO_USE_IPHONE_API
   240     uint8_t digest[CC_SHA1_DIGEST_LENGTH];
   241     CC_SHA1(data.bytes,data.length, digest);
   242 
   243     size_t sigLen = 1024;
   244     uint8_t sigBuf[sigLen];
   245     OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
   246                                  digest,sizeof(digest), //data.bytes, data.length,
   247                                  sigBuf, &sigLen);
   248     if(err) {
   249         Warn(@"SecKeyRawSign failed: %i",err);
   250         return nil;
   251     } else
   252         return [NSData dataWithBytes: sigBuf length: sigLen];
   253 #else
   254     NSData *signature = nil;
   255     CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA1WithRSA];
   256     if (!ccHandle) return nil;
   257     CSSM_DATA original = {data.length, (void*)data.bytes};
   258     CSSM_DATA result = {0,NULL};
   259     if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
   260         signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
   261                                    freeWhenDone: YES];
   262     CSSM_DeleteContext(ccHandle);
   263     return signature;
   264 #endif
   265 }
   266 
   267 
   268 #if !TARGET_OS_IPHONE
   269 
   270 - (NSData*) exportKeyInFormat: (SecExternalFormat)format 
   271                       withPEM: (BOOL)withPEM
   272                    alertTitle: (NSString*)title
   273                   alertPrompt: (NSString*)prompt
   274 {
   275     SecKeyImportExportParameters params = {
   276         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   277         .flags = kSecKeySecurePassphrase,
   278         .alertTitle = (CFStringRef)title,
   279         .alertPrompt = (CFStringRef)prompt
   280     };
   281     CFDataRef data = NULL;
   282     if (check(SecKeychainItemExport(self.keyRef,
   283                                     format, (withPEM ?kSecItemPemArmour :0), 
   284                                     &params, &data),
   285               @"SecKeychainItemExport"))
   286         return [(id)CFMakeCollectable(data) autorelease];
   287     else
   288         return nil;
   289 }
   290 
   291 - (NSData*) exportKey {
   292     return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
   293                         alertTitle: @"Export Private Key"
   294                        alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
   295             "You will need to re-enter the passphrase later when importing the key from this file, "
   296             "so keep it in a safe place."];
   297     //FIX: Should make these messages localizable.
   298 }
   299 
   300 
   301 - (NSData*) _exportKeyInFormat: (SecExternalFormat)format
   302                        withPEM: (BOOL)withPEM
   303                     passphrase: (NSString*)passphrase
   304 {
   305     SecKeyImportExportParameters params = {
   306         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   307         .passphrase = (CFStringRef)passphrase
   308     };
   309     CFDataRef data = NULL;
   310     if (check(SecKeychainItemExport(self.keyRef,
   311                                     format, (withPEM ?kSecItemPemArmour :0), 
   312                                     &params, &data),
   313               @"SecKeychainItemExport"))
   314         return [(id)CFMakeCollectable(data) autorelease];
   315     else
   316         return nil;
   317 }
   318 
   319 
   320 - (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
   321                        withAlgorithm: (CCAlgorithm)algorithm
   322                           sizeInBits: (unsigned)sizeInBits
   323 {
   324     // First create a wrapped-key structure from the data:
   325     CSSM_WRAP_KEY wrappedKey = {
   326         .KeyHeader = {
   327             .BlobType = CSSM_KEYBLOB_WRAPPED,
   328             .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
   329             .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
   330             .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
   331             .LogicalKeySizeInBits = sizeInBits,
   332             .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
   333             .KeyUsage = CSSM_KEYUSE_ANY,
   334             .WrapAlgorithmId = self.cssmAlgorithm,
   335         },
   336         .KeyData = {
   337             .Data = (void*)wrappedData.bytes,
   338             .Length = wrappedData.length
   339         }
   340     };
   341         
   342     const CSSM_ACCESS_CREDENTIALS* credentials;
   343     credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
   344                                                type: kSecCredentialTypeDefault error: nil];
   345     CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
   346     CSSM_CC_HANDLE ctx;
   347     if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
   348                                                     self.cssmAlgorithm,
   349                                                     credentials, 
   350                                                     self.cssmKey,
   351                                                     CSSM_PADDING_PKCS1,
   352                                                     &ctx), 
   353                    @"CSSM_CSP_CreateAsymmetricContext"))
   354         return nil;
   355     
   356     // Now unwrap the key:
   357     MYSymmetricKey *result = nil;
   358     CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
   359     CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
   360     CSSM_DATA descriptiveData = {};
   361     if (checkcssm(CSSM_UnwrapKey(ctx, 
   362                                  self.cssmKey,
   363                                  &wrappedKey,
   364                                  wrappedKey.KeyHeader.KeyUsage,
   365                                  wrappedKey.KeyHeader.KeyAttr,
   366                                  &label,
   367                                  NULL,
   368                                  unwrappedKey,
   369                                  &descriptiveData),
   370                   @"CSSM_UnwrapKey")) {
   371         result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
   372     }
   373     // Finally, delete the context
   374     if (!result)
   375         free(unwrappedKey);
   376     CSSM_DeleteContext(ctx);
   377     return result;
   378 }
   379 
   380 
   381 #endif !TARGET_OS_IPHONE
   382 
   383 @end
   384 
   385 
   386 
   387 /*
   388  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   389  
   390  Redistribution and use in source and binary forms, with or without modification, are permitted
   391  provided that the following conditions are met:
   392  
   393  * Redistributions of source code must retain the above copyright notice, this list of conditions
   394  and the following disclaimer.
   395  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   396  and the following disclaimer in the documentation and/or other materials provided with the
   397  distribution.
   398  
   399  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   400  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   401  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   402  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   403  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   404   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   405  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   406  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   407  */