snej@3: //
snej@3: //  MYPrivateKey.m
snej@3: //  MYCrypto
snej@3: //
snej@3: //  Created by Jens Alfke on 4/7/09.
snej@3: //  Copyright 2009 Jens Alfke. All rights reserved.
snej@3: //
snej@3: 
snej@3: #import "MYPrivateKey.h"
snej@3: #import "MYCrypto_Private.h"
snej@3: #import "MYDigest.h"
snej@3: #import <CommonCrypto/CommonDigest.h>
snej@3: 
snej@5: #if !TARGET_OS_IPHONE
snej@5: #import "MYCertGen.h"
snej@5: #endif
snej@3: 
snej@3: @implementation MYPrivateKey
snej@3: 
snej@3: 
snej@3: - (id) initWithKeyRef: (SecKeyRef)privateKey
snej@3: {
snej@3:     self = [super initWithKeyRef: privateKey];
snej@3:     if (self) {
snej@3:         // No public key given, so look it up:
snej@3:         MYSHA1Digest *digest = self._keyDigest;
snej@3:         if (digest)
snej@3:             _publicKey = [[self.keychain publicKeyWithDigest: digest] retain];
snej@3:         if (!_publicKey) {
snej@3:             // The matching public key won't turn up if it's embedded in a certificate;
snej@3:             // I'd have to search for certs if I wanted to look that up. Skip it for now.
snej@3:             Log(@"MYPrivateKey(%p): Couldn't find matching public key for private key! digest=%@",
snej@3:                 self, digest);
snej@3:             [self release];
snej@3:             return nil;
snej@3:         }
snej@3:     }
snej@3:     return self;
snej@3: }
snej@3: 
snej@3: 
snej@3: - (id) _initWithKeyRef: (SecKeyRef)privateKey
snej@3:              publicKey: (MYPublicKey*)publicKey 
snej@3: {
snej@3:     Assert(publicKey);
snej@3:     self = [super initWithKeyRef: privateKey];
snej@3:     if (self) {
snej@3:         _publicKey = [publicKey retain];
snej@3:     }
snej@3:     return self;
snej@3: }
snej@3: 
snej@3: - (id) initWithKeyRef: (SecKeyRef)privateKey
snej@3:          publicKeyRef: (SecKeyRef)publicKeyRef
snej@3: {
snej@3:     MYPublicKey *publicKey = [[MYPublicKey alloc] initWithKeyRef: publicKeyRef];
snej@3:     self = [self _initWithKeyRef: privateKey publicKey: publicKey];
snej@3:     [publicKey release];
snej@3:     return self;
snej@3: }
snej@3: 
snej@3: - (id) _initWithKeyRef: (SecKeyRef)privateKey 
snej@3:          publicKeyData: (NSData*)pubKeyData
snej@3:            forKeychain: (SecKeychainRef)keychain 
snej@3: {
snej@3:     if (!privateKey) {
snej@3:         [self release];
snej@3:         return nil;
snej@3:     }
snej@3:     MYPublicKey *pubKey = [[MYPublicKey alloc] _initWithKeyData: pubKeyData forKeychain: keychain];
snej@3:     if (!pubKey) {
snej@3:         [self release];
snej@3:         return nil;
snej@3:     }
snej@3:     self = [super initWithKeyRef: privateKey];
snej@3:     if (self) {
snej@3:         _publicKey = pubKey;
snej@3:     } else {
snej@3:         [pubKey removeFromKeychain];
snej@3:         [pubKey release];
snej@3:     }
snej@3:     return self;
snej@3: }
snej@3: 
snej@3: 
snej@3: #if !TARGET_OS_IPHONE
snej@3: 
snej@3: // The public API for this is in MYKeychain.
snej@3: - (id) _initWithKeyData: (NSData*)privKeyData 
snej@3:           publicKeyData: (NSData*)pubKeyData
snej@3:             forKeychain: (SecKeychainRef)keychain 
snej@3:              alertTitle: (NSString*)title
snej@3:             alertPrompt: (NSString*)prompt
snej@3: {
snej@3:     // Try to import the private key first, since the user might cancel the passphrase alert.
snej@3:     SecKeyImportExportParameters params = {
snej@3:         .flags = kSecKeySecurePassphrase,
snej@3:         .alertTitle = (CFStringRef) title,
snej@3:         .alertPrompt = (CFStringRef) prompt
snej@3:     };
snej@3:     SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,&params);
snej@3:     return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
snej@3: }
snej@3: 
snej@3: // This method is for testing, so unit-tests don't require user intervention.
snej@3: // It's deliberately not made public, to discourage clients from trying to manage the passphrases
snej@3: // themselves (this is less secure than letting the Security agent do it.)
snej@3: - (id) _initWithKeyData: (NSData*)privKeyData 
snej@3:           publicKeyData: (NSData*)pubKeyData
snej@3:             forKeychain: (SecKeychainRef)keychain 
snej@3:              passphrase: (NSString*)passphrase
snej@3: {
snej@3:     SecKeyImportExportParameters params = {
snej@3:         .passphrase = (CFStringRef) passphrase,
snej@3:     };
snej@3:     SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,&params);
snej@3:     return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
snej@3: }
snej@3: 
snej@4: 
snej@4: - (MYIdentity*) createSelfSignedIdentityWithAttributes: (NSDictionary*)attributes {
snej@4:     return MYIdentityCreateSelfSigned(self, attributes);
snej@4: }
snej@4: 
snej@4: 
snej@3: #endif
snej@3: 
snej@3: 
snej@3: - (void) dealloc
snej@3: {
snej@3:     [_publicKey release];
snej@3:     [super dealloc];
snej@3: }
snej@3: 
snej@3: 
snej@3: + (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
snej@3:                                  inKeychain: (MYKeychain*)keychain 
snej@3: {
snej@3:     Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
snej@3:     SecKeyRef pubKey=NULL, privKey=NULL;
snej@3:     OSStatus err;
snej@3:     
snej@3: #if MYCRYPTO_USE_IPHONE_API
snej@3:     NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
snej@3:     NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
snej@3:     NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
snej@3:                                     {(id)kSecAttrKeySizeInBits, $object(keySize)},
snej@3:                                     {(id)kSecPublicKeyAttrs, pubKeyAttrs},
snej@3:                                     {(id)kSecPrivateKeyAttrs, privKeyAttrs} );
snej@3:     err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
snej@3: #else
snej@3:     err = SecKeyCreatePair(keychain.keychainRefOrDefault,
snej@3:                            CSSM_ALGID_RSA, 
snej@3:                            keySize,
snej@3:                            0LL,
snej@3:                            CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY,        // public key
snej@3:                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
snej@8:                            CSSM_KEYUSE_ANY,                                 // private key
snej@8:                            CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
snej@8:                            NULL,                                            // SecAccessRef
snej@3:                            &pubKey, &privKey);
snej@3: #endif
snej@3:     if (!check(err, @"SecKeyCreatePair")) {
snej@3:         return nil;
snej@3:     } else
snej@3:         return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
snej@3: }
snej@3: 
snej@3: 
snej@3: #pragma mark -
snej@3: #pragma mark ACCESSORS:
snej@3: 
snej@3: 
snej@3: - (NSString*) description {
snej@8:     return $sprintf(@"%@[%@ %@ /%p]", [self class], 
snej@8:                     self.publicKeyDigest.abbreviatedHexString,
snej@8:                     (self.name ?:@""),
snej@8:                     self.keychainItemRef);
snej@3: }
snej@3: 
snej@3: @synthesize publicKey=_publicKey;
snej@3: 
snej@3: - (MYSHA1Digest*) publicKeyDigest {
snej@3:     return _publicKey.publicKeyDigest;
snej@3: }
snej@3: 
snej@3: - (SecExternalItemType) keyType {
snej@3: #if MYCRYPTO_USE_IPHONE_API
snej@3:     return kSecAttrKeyClassPublic;
snej@3: #else
snej@3:     return kSecItemTypePrivateKey;
snej@3: #endif
snej@3: }
snej@3: 
snej@3: - (NSData *) keyData {
snej@3:     [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
snej@3:     return nil;
snej@3: }
snej@3: 
snej@3: - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
snej@3:     return [super setValue: valueStr ofAttribute: attr]
snej@3:         && [_publicKey setValue: valueStr ofAttribute: attr];
snej@3: }
snej@3: 
snej@3: 
snej@3: #pragma mark -
snej@3: #pragma mark OPERATIONS:
snej@3: 
snej@3: 
snej@3: - (BOOL) removeFromKeychain {
snej@3:     return [super removeFromKeychain]
snej@3:         && [_publicKey removeFromKeychain];
snej@3: }
snej@3: 
snej@3: 
snej@3: - (NSData*) decryptData: (NSData*)data {
snej@3:     return [self _crypt: data operation: NO];
snej@3: }
snej@3: 
snej@3: 
snej@3: - (NSData*) signData: (NSData*)data {
snej@3:     Assert(data);
snej@3: #if MYCRYPTO_USE_IPHONE_API
snej@3:     uint8_t digest[CC_SHA1_DIGEST_LENGTH];
snej@3:     CC_SHA1(data.bytes,data.length, digest);
snej@3: 
snej@3:     size_t sigLen = 1024;
snej@3:     uint8_t sigBuf[sigLen];
snej@3:     OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
snej@3:                                  digest,sizeof(digest), //data.bytes, data.length,
snej@3:                                  sigBuf, &sigLen);
snej@3:     if(err) {
snej@3:         Warn(@"SecKeyRawSign failed: %i",err);
snej@3:         return nil;
snej@3:     } else
snej@3:         return [NSData dataWithBytes: sigBuf length: sigLen];
snej@3: #else
snej@3:     NSData *signature = nil;
snej@3:     CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA256WithRSA];
snej@3:     if (!ccHandle) return nil;
snej@3:     CSSM_DATA original = {data.length, (void*)data.bytes};
snej@3:     CSSM_DATA result = {0,NULL};
snej@3:     if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
snej@3:         signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
snej@3:                                    freeWhenDone: YES];
snej@3:     CSSM_DeleteContext(ccHandle);
snej@3:     return signature;
snej@3: #endif
snej@3: }
snej@3: 
snej@3: 
snej@3: #if !TARGET_OS_IPHONE
snej@3: 
snej@3: - (NSData*) exportKeyInFormat: (SecExternalFormat)format 
snej@3:                       withPEM: (BOOL)withPEM
snej@3:                    alertTitle: (NSString*)title
snej@3:                   alertPrompt: (NSString*)prompt
snej@3: {
snej@3:     SecKeyImportExportParameters params = {
snej@3:         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3:         .flags = kSecKeySecurePassphrase,
snej@3:         .alertTitle = (CFStringRef)title,
snej@3:         .alertPrompt = (CFStringRef)prompt
snej@3:     };
snej@3:     CFDataRef data = NULL;
snej@3:     if (check(SecKeychainItemExport(self.keyRef,
snej@3:                                     format, (withPEM ?kSecItemPemArmour :0), 
snej@3:                                     &params, &data),
snej@3:               @"SecKeychainItemExport"))
snej@3:         return [(id)CFMakeCollectable(data) autorelease];
snej@3:     else
snej@3:         return nil;
snej@3: }
snej@3: 
snej@3: - (NSData*) exportKey {
snej@3:     return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
snej@3:                         alertTitle: @"Export Private Key"
snej@3:                        alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
snej@3:             "You will need to re-enter the passphrase later when importing the key from this file, "
snej@3:             "so keep it in a safe place."];
snej@3:     //FIX: Should make these messages localizable.
snej@3: }
snej@3: 
snej@3: 
snej@3: - (NSData*) _exportKeyInFormat: (SecExternalFormat)format
snej@3:                        withPEM: (BOOL)withPEM
snej@3:                     passphrase: (NSString*)passphrase
snej@3: {
snej@3:     SecKeyImportExportParameters params = {
snej@3:         .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
snej@3:         .passphrase = (CFStringRef)passphrase
snej@3:     };
snej@3:     CFDataRef data = NULL;
snej@3:     if (check(SecKeychainItemExport(self.keyRef,
snej@3:                                     format, (withPEM ?kSecItemPemArmour :0), 
snej@3:                                     &params, &data),
snej@3:               @"SecKeychainItemExport"))
snej@3:         return [(id)CFMakeCollectable(data) autorelease];
snej@3:     else
snej@3:         return nil;
snej@3: }
snej@3: 
snej@3: #endif TARGET_OS_IPHONE
snej@3: 
snej@3: @end