* More work on iPhone compatibility.
* Restored the signature-verification code to MYCertInfo, which I'd removed earlier. I now need it to verify self-signed certs, since the Security framework won't do it for me.
* Merged MYCertificate-iPhone.m into MYCertificate.m since there's more shared code now.
5 // Created by Jens Alfke on 4/7/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYPrivateKey.h"
10 #import "MYCrypto_Private.h"
12 #import <CommonCrypto/CommonDigest.h>
18 @implementation MYPrivateKey
21 - (id) initWithKeyRef: (SecKeyRef)privateKey
23 self = [super initWithKeyRef: privateKey];
25 // No public key given, so look it up:
26 MYSHA1Digest *digest = self._keyDigest;
28 _publicKey = [[self.keychain publicKeyWithDigest: digest] retain];
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=%@",
42 - (id) _initWithKeyRef: (SecKeyRef)privateKey
43 publicKey: (MYPublicKey*)publicKey
46 self = [super initWithKeyRef: privateKey];
48 _publicKey = [publicKey retain];
53 - (id) initWithKeyRef: (SecKeyRef)privateKey
54 publicKeyRef: (SecKeyRef)publicKeyRef
56 MYPublicKey *publicKey = [[MYPublicKey alloc] initWithKeyRef: publicKeyRef];
57 self = [self _initWithKeyRef: privateKey publicKey: publicKey];
62 - (id) _initWithKeyRef: (SecKeyRef)privateKey
63 publicKeyData: (NSData*)pubKeyData
64 forKeychain: (SecKeychainRef)keychain
70 MYPublicKey *pubKey = [[MYPublicKey alloc] _initWithKeyData: pubKeyData forKeychain: keychain];
75 self = [super initWithKeyRef: privateKey];
79 [pubKey removeFromKeychain];
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
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
101 SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,¶ms);
102 return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
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
113 SecKeyImportExportParameters params = {
114 .passphrase = (CFStringRef) passphrase,
116 SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,¶ms);
117 return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
126 [_publicKey release];
131 + (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
132 inKeychain: (MYKeychain*)keychain
134 Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
135 SecKeyRef pubKey=NULL, privKey=NULL;
138 #if MYCRYPTO_USE_IPHONE_API
139 NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
140 NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
141 NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
142 {(id)kSecAttrKeySizeInBits, $object(keySize)},
143 {(id)kSecPublicKeyAttrs, pubKeyAttrs},
144 {(id)kSecPrivateKeyAttrs, privKeyAttrs} );
145 err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
147 err = SecKeyCreatePair(keychain.keychainRefOrDefault,
151 CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP, // public key
152 CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
153 CSSM_KEYUSE_ANY, // private key
154 CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
155 NULL, // SecAccessRef
158 if (!check(err, @"SecKeyCreatePair")) {
161 return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
166 #pragma mark ACCESSORS:
169 - (NSString*) description {
170 return $sprintf(@"%@[%@ %@ /%p]", [self class],
171 self.publicKeyDigest.abbreviatedHexString,
173 self.keychainItemRef);
176 @synthesize publicKey=_publicKey;
178 - (MYSHA1Digest*) _keyDigest {
180 return _publicKey.publicKeyDigest;
183 #if MYCRYPTO_USE_IPHONE_API
184 digestData = [self _attribute: kSecAttrApplicationLabel];
186 digestData = [[self class] _getAttribute: kSecKeyLabel
187 ofItem: (SecKeychainItemRef)self.keyRef];
189 return [MYSHA1Digest digestFromDigestData: digestData];
193 - (MYSHA1Digest*) publicKeyDigest {
194 return self._keyDigest;
197 - (SecExternalItemType) keyClass {
198 #if MYCRYPTO_USE_IPHONE_API
199 return kSecAttrKeyClassPublic;
201 return kSecItemTypePrivateKey;
205 #if MYCRYPTO_USE_IPHONE_API
206 - (SecExternalItemType) keyType {
207 return kSecAttrKeyTypeRSA;
211 - (NSData *) keyData {
212 [NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
216 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
217 return [super setValue: valueStr ofAttribute: attr]
218 && [_publicKey setValue: valueStr ofAttribute: attr];
223 #pragma mark OPERATIONS:
226 - (BOOL) removeFromKeychain {
227 return [super removeFromKeychain]
228 && [_publicKey removeFromKeychain];
232 - (NSData*) rawDecryptData: (NSData*)data {
233 return [self _crypt: data operation: NO];
237 - (NSData*) signData: (NSData*)data {
239 #if MYCRYPTO_USE_IPHONE_API
240 uint8_t digest[CC_SHA1_DIGEST_LENGTH];
241 CC_SHA1(data.bytes,data.length, digest);
243 size_t sigLen = 1024;
244 uint8_t sigBuf[sigLen];
245 OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
246 digest,sizeof(digest), //data.bytes, data.length,
249 Warn(@"SecKeyRawSign failed: %i",err);
252 return [NSData dataWithBytes: sigBuf length: sigLen];
254 NSData *signature = nil;
255 CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA1WithRSA];
256 if (!ccHandle) return nil;
257 CSSM_DATA original = {data.length, (void*)data.bytes};
258 CSSM_DATA result = {0,NULL};
259 if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
260 signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
262 CSSM_DeleteContext(ccHandle);
268 #if !TARGET_OS_IPHONE
270 - (NSData*) exportKeyInFormat: (SecExternalFormat)format
271 withPEM: (BOOL)withPEM
272 alertTitle: (NSString*)title
273 alertPrompt: (NSString*)prompt
275 SecKeyImportExportParameters params = {
276 .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
277 .flags = kSecKeySecurePassphrase,
278 .alertTitle = (CFStringRef)title,
279 .alertPrompt = (CFStringRef)prompt
281 CFDataRef data = NULL;
282 if (check(SecKeychainItemExport(self.keyRef,
283 format, (withPEM ?kSecItemPemArmour :0),
285 @"SecKeychainItemExport"))
286 return [(id)CFMakeCollectable(data) autorelease];
291 - (NSData*) exportKey {
292 return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
293 alertTitle: @"Export Private Key"
294 alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
295 "You will need to re-enter the passphrase later when importing the key from this file, "
296 "so keep it in a safe place."];
297 //FIX: Should make these messages localizable.
301 - (NSData*) _exportKeyInFormat: (SecExternalFormat)format
302 withPEM: (BOOL)withPEM
303 passphrase: (NSString*)passphrase
305 SecKeyImportExportParameters params = {
306 .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
307 .passphrase = (CFStringRef)passphrase
309 CFDataRef data = NULL;
310 if (check(SecKeychainItemExport(self.keyRef,
311 format, (withPEM ?kSecItemPemArmour :0),
313 @"SecKeychainItemExport"))
314 return [(id)CFMakeCollectable(data) autorelease];
320 - (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
321 withAlgorithm: (CCAlgorithm)algorithm
322 sizeInBits: (unsigned)sizeInBits
324 // First create a wrapped-key structure from the data:
325 CSSM_WRAP_KEY wrappedKey = {
327 .BlobType = CSSM_KEYBLOB_WRAPPED,
328 .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
329 .AlgorithmId = CSSMFromCCAlgorithm(algorithm),
330 .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
331 .LogicalKeySizeInBits = sizeInBits,
332 .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
333 .KeyUsage = CSSM_KEYUSE_ANY,
334 .WrapAlgorithmId = self.cssmAlgorithm,
337 .Data = (void*)wrappedData.bytes,
338 .Length = wrappedData.length
342 const CSSM_ACCESS_CREDENTIALS* credentials;
343 credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
344 type: kSecCredentialTypeDefault error: nil];
345 CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
347 if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
353 @"CSSM_CSP_CreateAsymmetricContext"))
356 // Now unwrap the key:
357 MYSymmetricKey *result = nil;
358 CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
359 CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
360 CSSM_DATA descriptiveData = {};
361 if (checkcssm(CSSM_UnwrapKey(ctx,
364 wrappedKey.KeyHeader.KeyUsage,
365 wrappedKey.KeyHeader.KeyAttr,
370 @"CSSM_UnwrapKey")) {
371 result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
373 // Finally, delete the context
376 CSSM_DeleteContext(ctx);
381 #endif !TARGET_OS_IPHONE
388 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
390 Redistribution and use in source and binary forms, with or without modification, are permitted
391 provided that the following conditions are met:
393 * Redistributions of source code must retain the above copyright notice, this list of conditions
394 and the following disclaimer.
395 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
396 and the following disclaimer in the documentation and/or other materials provided with the
399 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
400 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
401 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
402 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
403 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
404 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
405 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
406 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.