MYPrivateKey.m
author snej@snej.local
Thu Apr 09 22:46:48 2009 -0700 (2009-04-09)
changeset 6 2d7692f9b6b4
parent 4 f4709533c816
child 8 4c0eafa7b233
permissions -rw-r--r--
Updated the README for the 0.1 release.
     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,        // public key
   157                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
   158                            CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN,          // private key
   159                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_PERMANENT,
   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(@"%@[%@]", [self class], self.publicKeyDigest.abbreviatedHexString);
   176 }
   177 
   178 @synthesize publicKey=_publicKey;
   179 
   180 - (MYSHA1Digest*) publicKeyDigest {
   181     return _publicKey.publicKeyDigest;
   182 }
   183 
   184 - (SecExternalItemType) keyType {
   185 #if MYCRYPTO_USE_IPHONE_API
   186     return kSecAttrKeyClassPublic;
   187 #else
   188     return kSecItemTypePrivateKey;
   189 #endif
   190 }
   191 
   192 - (NSData *) keyData {
   193     [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
   194     return nil;
   195 }
   196 
   197 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   198     return [super setValue: valueStr ofAttribute: attr]
   199         && [_publicKey setValue: valueStr ofAttribute: attr];
   200 }
   201 
   202 
   203 #pragma mark -
   204 #pragma mark OPERATIONS:
   205 
   206 
   207 - (BOOL) removeFromKeychain {
   208     return [super removeFromKeychain]
   209         && [_publicKey removeFromKeychain];
   210 }
   211 
   212 
   213 - (NSData*) decryptData: (NSData*)data {
   214     return [self _crypt: data operation: NO];
   215 }
   216 
   217 
   218 - (NSData*) signData: (NSData*)data {
   219     Assert(data);
   220 #if MYCRYPTO_USE_IPHONE_API
   221     uint8_t digest[CC_SHA1_DIGEST_LENGTH];
   222     CC_SHA1(data.bytes,data.length, digest);
   223 
   224     size_t sigLen = 1024;
   225     uint8_t sigBuf[sigLen];
   226     OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
   227                                  digest,sizeof(digest), //data.bytes, data.length,
   228                                  sigBuf, &sigLen);
   229     if(err) {
   230         Warn(@"SecKeyRawSign failed: %i",err);
   231         return nil;
   232     } else
   233         return [NSData dataWithBytes: sigBuf length: sigLen];
   234 #else
   235     NSData *signature = nil;
   236     CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA256WithRSA];
   237     if (!ccHandle) return nil;
   238     CSSM_DATA original = {data.length, (void*)data.bytes};
   239     CSSM_DATA result = {0,NULL};
   240     if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
   241         signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
   242                                    freeWhenDone: YES];
   243     CSSM_DeleteContext(ccHandle);
   244     return signature;
   245 #endif
   246 }
   247 
   248 
   249 #if !TARGET_OS_IPHONE
   250 
   251 - (NSData*) exportKeyInFormat: (SecExternalFormat)format 
   252                       withPEM: (BOOL)withPEM
   253                    alertTitle: (NSString*)title
   254                   alertPrompt: (NSString*)prompt
   255 {
   256     SecKeyImportExportParameters params = {
   257         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   258         .flags = kSecKeySecurePassphrase,
   259         .alertTitle = (CFStringRef)title,
   260         .alertPrompt = (CFStringRef)prompt
   261     };
   262     CFDataRef data = NULL;
   263     if (check(SecKeychainItemExport(self.keyRef,
   264                                     format, (withPEM ?kSecItemPemArmour :0), 
   265                                     &params, &data),
   266               @"SecKeychainItemExport"))
   267         return [(id)CFMakeCollectable(data) autorelease];
   268     else
   269         return nil;
   270 }
   271 
   272 - (NSData*) exportKey {
   273     return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
   274                         alertTitle: @"Export Private Key"
   275                        alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
   276             "You will need to re-enter the passphrase later when importing the key from this file, "
   277             "so keep it in a safe place."];
   278     //FIX: Should make these messages localizable.
   279 }
   280 
   281 
   282 - (NSData*) _exportKeyInFormat: (SecExternalFormat)format
   283                        withPEM: (BOOL)withPEM
   284                     passphrase: (NSString*)passphrase
   285 {
   286     SecKeyImportExportParameters params = {
   287         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
   288         .passphrase = (CFStringRef)passphrase
   289     };
   290     CFDataRef data = NULL;
   291     if (check(SecKeychainItemExport(self.keyRef,
   292                                     format, (withPEM ?kSecItemPemArmour :0), 
   293                                     &params, &data),
   294               @"SecKeychainItemExport"))
   295         return [(id)CFMakeCollectable(data) autorelease];
   296     else
   297         return nil;
   298 }
   299 
   300 #endif TARGET_OS_IPHONE
   301 
   302 @end