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 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,¶ms); 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,¶ms); 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@3: CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN, // private key snej@3: CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_PERMANENT, snej@3: 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@3: return $sprintf(@"%@[%@]", [self class], self.publicKeyDigest.abbreviatedHexString); 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: ¶ms, &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: ¶ms, &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