MYPrivateKey.m
author snej@snej.local
Sun Apr 12 22:16:14 2009 -0700 (2009-04-12)
changeset 9 aa5eb3fd6ebf
parent 5 b2e360b78189
child 13 6fd9177eb6da
permissions -rw-r--r--
Doc touch-up
     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_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*) decryptData: (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 #endif TARGET_OS_IPHONE
   304 
   305 @end