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@13: CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP, // 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@13: - (NSData*) rawDecryptData: (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@13: snej@13: - (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData snej@13: withAlgorithm: (CCAlgorithm)algorithm snej@13: sizeInBits: (unsigned)sizeInBits snej@13: { snej@13: // First create a wrapped-key structure from the data: snej@13: CSSM_WRAP_KEY wrappedKey = { snej@13: .KeyHeader = { snej@13: .BlobType = CSSM_KEYBLOB_WRAPPED, snej@13: .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3, snej@13: .AlgorithmId = CSSMFromCCAlgorithm(algorithm), snej@13: .KeyClass = CSSM_KEYCLASS_SESSION_KEY, snej@13: .LogicalKeySizeInBits = sizeInBits, snej@13: .KeyAttr = CSSM_KEYATTR_EXTRACTABLE, snej@13: .KeyUsage = CSSM_KEYUSE_ANY, snej@13: .WrapAlgorithmId = self.cssmAlgorithm, snej@13: }, snej@13: .KeyData = { snej@13: .Data = (void*)wrappedData.bytes, snej@13: .Length = wrappedData.length snej@13: } snej@13: }; snej@13: snej@13: const CSSM_ACCESS_CREDENTIALS* credentials; snej@13: credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED snej@13: type: kSecCredentialTypeDefault error: nil]; snej@13: CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle; snej@13: CSSM_CC_HANDLE ctx; snej@13: if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle, snej@13: self.cssmAlgorithm, snej@13: credentials, snej@13: self.cssmKey, snej@13: CSSM_PADDING_PKCS1, snej@13: &ctx), snej@13: @"CSSM_CSP_CreateAsymmetricContext")) snej@13: return nil; snej@13: snej@13: // Now unwrap the key: snej@13: MYSymmetricKey *result = nil; snej@13: CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY)); snej@14: CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")}; snej@14: CSSM_DATA descriptiveData = {}; snej@13: if (checkcssm(CSSM_UnwrapKey(ctx, snej@13: self.cssmKey, snej@13: &wrappedKey, snej@13: wrappedKey.KeyHeader.KeyUsage, snej@13: wrappedKey.KeyHeader.KeyAttr, snej@14: &label, snej@14: NULL, snej@13: unwrappedKey, snej@14: &descriptiveData), snej@13: @"CSSM_UnwrapKey")) { snej@13: result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease]; snej@13: } snej@13: // Finally, delete the context snej@14: if (!result) snej@14: free(unwrappedKey); snej@13: CSSM_DeleteContext(ctx); snej@13: return result; snej@13: } snej@13: snej@13: snej@13: #endif !TARGET_OS_IPHONE snej@3: snej@3: @end snej@14: snej@14: snej@14: snej@14: /* snej@14: Copyright (c) 2009, Jens Alfke . All rights reserved. snej@14: snej@14: Redistribution and use in source and binary forms, with or without modification, are permitted snej@14: provided that the following conditions are met: snej@14: snej@14: * Redistributions of source code must retain the above copyright notice, this list of conditions snej@14: and the following disclaimer. snej@14: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions snej@14: and the following disclaimer in the documentation and/or other materials provided with the snej@14: distribution. snej@14: snej@14: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR snej@14: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND snej@14: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- snej@14: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES snej@14: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR snej@14: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN snej@14: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF snej@14: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. snej@14: */