MYCertificate now checks validity of self-signed certs loaded from the keychain (because the Security framework doesn't validate self-signed certs.)
5 // Created by Jens Alfke on 4/2/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYSymmetricKey.h"
11 #import "MYCrypto_Private.h"
13 #if !MYCRYPTO_USE_IPHONE_API
15 #import <Security/cssmtype.h>
18 CSSM_ALGORITHMS CSSMFromCCAlgorithm( CCAlgorithm ccAlgorithm ) {
19 static const CSSM_ALGORITHMS kCSSMAlgorithms[] = {
20 CSSM_ALGID_AES, CSSM_ALGID_DES, CSSM_ALGID_3DES_3KEY, CSSM_ALGID_CAST, CSSM_ALGID_RC4
22 if (ccAlgorithm >=0 && ccAlgorithm <= kCCAlgorithmRC4)
23 return kCSSMAlgorithms[ccAlgorithm];
25 return CSSM_ALGID_NONE;
28 static const char *kCCAlgorithmNames[] = {"AES", "DES", "DES^3", "CAST", "RC4"};
31 /** Undocumented Security function. Unfortunately this is the only way I can find to create
32 a SecKeyRef from a CSSM_KEY. */
33 extern OSStatus SecKeyCreate(const CSSM_KEY *key, SecKeyRef* keyRef) WEAK_IMPORT_ATTRIBUTE;
35 static CSSM_KEY* cssmKeyFromData( NSData *keyData, CSSM_ALGORITHMS algorithm,
36 MYKeychain *keychain);
39 static CSSM_KEY* unwrapCssmKeyFromData(NSData *wrappedData,
40 CSSM_ALGORITHMS algorithm,
42 static CSSM_ENCRYPT_MODE defaultModeForAlgorithm(CSSM_ALGORITHMS algorithm);
43 static CSSM_PADDING defaultPaddingForAlgorithm(CSSM_ALGORITHMS algorithm);
46 static CSSM_DATA makeSalt( id salty, size_t length );
47 static CSSM_RETURN impExpCreatePassKey(
48 const SecKeyImportExportParameters *keyParams, // required
49 CSSM_CSP_HANDLE cspHand, // MUST be CSPDL
50 BOOL verifyPhrase, // use 2nd passphrase textfield for verification?
51 CSSM_KEY_PTR *passKey); // mallocd and RETURNED
55 @implementation MYSymmetricKey
58 - (id) _initWithCSSMKey: (CSSM_KEY*)cssmKey {
63 SecKeyRef keyRef = NULL;
64 if (SecKeyCreate == NULL) {
65 // If weak-linked SPI fn no longer exists
66 Warn(@"Unable to call SecKeyCreate SPI -- not available");
70 if (!check(SecKeyCreate(cssmKey,&keyRef), @"SecKeyCreate")) {
74 self = [self initWithKeyRef: keyRef];
76 _ownedCSSMKey = cssmKey; // (so I'll remember to free it)
82 - (id) _initWithKeyData: (NSData*)keyData
83 algorithm: (CCAlgorithm)algorithm
84 inKeychain: (MYKeychain*)keychain
86 Assert(algorithm <= kCCAlgorithmRC4);
88 CSSM_KEY *key = cssmKeyFromData(keyData, CSSMFromCCAlgorithm(algorithm), keychain);
89 return [self _initWithCSSMKey: key];
92 - (id) initWithKeyData: (NSData*)keyData
93 algorithm: (CCAlgorithm)algorithm
95 return [self _initWithKeyData: keyData algorithm: algorithm inKeychain: nil];
98 + (MYSymmetricKey*) _generateSymmetricKeyOfSize: (unsigned)keySizeInBits
99 algorithm: (CCAlgorithm)algorithm
100 inKeychain: (MYKeychain*)keychain
102 Assert(algorithm <= kCCAlgorithmRC4);
103 CSSM_KEYATTR_FLAGS flags = CSSM_KEYATTR_EXTRACTABLE;
105 flags |= CSSM_KEYATTR_PERMANENT; // | CSSM_KEYATTR_SENSITIVE; //FIX: Re-enable this bit
107 flags |= CSSM_KEYATTR_RETURN_REF;
108 keychain = [MYKeychain defaultKeychain]; // establish a context for the key
110 CSSM_KEYUSE usage = CSSM_KEYUSE_ANY;
111 SecKeyRef keyRef = NULL;
112 if (!check(SecKeyGenerate(keychain.keychainRefOrDefault,
113 CSSMFromCCAlgorithm(algorithm),
115 0, usage, flags, NULL, &keyRef),
116 @"SecKeyGenerate")) {
119 return [[[self alloc] initWithKeyRef: keyRef] autorelease];
122 + (MYSymmetricKey*) generateSymmetricKeyOfSize: (unsigned)keySizeInBits
123 algorithm: (CCAlgorithm)algorithm {
124 return [self _generateSymmetricKeyOfSize: keySizeInBits
129 + (NSString*) promptForPassphraseWithAlertTitle: (NSString*)alertTitle
130 alertPrompt: (NSString*)prompt
131 creating: (BOOL)creating
133 // Ask the user for a passphrase:
134 SecKeyImportExportParameters params = {
135 .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
136 .flags = kSecKeySecurePassphrase,
137 .alertTitle = (CFStringRef)alertTitle,
138 .alertPrompt = (CFStringRef)prompt,
139 .keyUsage = CSSM_KEYUSE_ANY,
140 .keyAttributes = CSSM_KEYATTR_EXTRACTABLE
142 CSSM_CSP_HANDLE cspHandle = [[MYKeychain defaultKeychain] CSPHandle];
143 CSSM_KEY *passphraseKey = NULL;
144 if (impExpCreatePassKey(¶ms,
147 &passphraseKey) != CSSM_OK)
150 MYSymmetricKey *key = [[self alloc] _initWithCSSMKey: passphraseKey];
151 NSData *keyData = key.keyData;
153 NSString *passphrase = [[NSString alloc] initWithData: keyData
154 encoding: NSUTF8StringEncoding];
156 return [passphrase autorelease];
160 #define PKCS5_V2_SALT_LEN 8
161 #define PKCS5_V2_ITERATIONS 2048
162 #define PKCS5_V2_DES_IV_SIZE 8
164 + (MYSymmetricKey*) generateFromUserPassphraseWithAlertTitle: (NSString*)alertTitle
165 alertPrompt: (NSString*)prompt
166 creating: (BOOL)creating
169 MYSymmetricKey *generatedKey = nil;
171 // Ask the user for a passphrase:
172 SecKeyImportExportParameters params = {
173 .version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
174 .flags = kSecKeySecurePassphrase,
175 .alertTitle = (CFStringRef)alertTitle,
176 .alertPrompt = (CFStringRef)prompt,
177 .keyUsage = CSSM_KEYUSE_ANY,
178 .keyAttributes = CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_SENSITIVE
180 CSSM_CSP_HANDLE cspHandle = [[MYKeychain defaultKeychain] CSPHandle];
181 CSSM_KEY *passphraseKey = NULL;
182 if (impExpCreatePassKey(¶ms,
185 &passphraseKey) != CSSM_OK)
188 CSSM_DATA saltData = makeSalt(saltObj,PKCS5_V2_SALT_LEN);
189 CSSM_CRYPTO_DATA seed = {};
191 // Now use the secure passphrase to generate a symmetric key:
192 CSSM_CC_HANDLE ctx = 0;
193 CSSM_ACCESS_CREDENTIALS credentials = {};
194 if (checkcssm(CSSM_CSP_CreateDeriveKeyContext(cspHandle,
195 CSSM_ALGID_PKCS5_PBKDF2,
203 @"CSSM_CSP_CreateDeriveKeyContext")) {
204 CSSM_PKCS5_PBKDF2_PARAMS params = {.PseudoRandomFunction=CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1};
205 CSSM_DATA paramData = {.Data=(void*)¶ms, .Length=sizeof(params)};
206 CSSM_KEY *cssmKey = calloc(1,sizeof(CSSM_KEY));
207 if (checkcssm(CSSM_DeriveKey(ctx,
210 CSSM_KEYATTR_EXTRACTABLE, //| CSSM_KEYATTR_SENSITIVE,
214 @"CSSM_DeriveKey")) {
215 generatedKey = [[[self alloc] _initWithCSSMKey: cssmKey] autorelease];
218 CSSM_DeleteContext(ctx);
219 CSSM_FreeKey(cspHandle, &credentials, passphraseKey, YES);
227 CSSM_FreeKey(self.cssmCSPHandle, NULL, _ownedCSSMKey, YES);
232 #if !TARGET_OS_IPHONE
233 - (id) initWithWrappedKeyData: (NSData*)wrappedKeyData {
234 return [self _initWithCSSMKey: unwrapCssmKeyFromData(wrappedKeyData,
235 CSSM_ALGID_AES,128)];
239 - (NSData*) exportWrappedKeyWithPassphrasePrompt: (NSString*)prompt
241 // Prompt user for a passphrase to use for the wrapping key:
242 MYSymmetricKey *wrappingKey = [MYSymmetricKey
243 generateFromUserPassphraseWithAlertTitle: @"Export Key"
246 salt: [MYCryptor randomKeyOfLength: PKCS5_V2_SALT_LEN*8]];
249 Log(@"Wrapping using %@",wrappingKey);
251 // Create the context:
252 CSSM_ACCESS_CREDENTIALS credentials = {};
253 CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
254 CSSM_ALGORITHMS algorithm = wrappingKey.cssmAlgorithm;
255 uint8 iv[16] = {0}; // Right size for AES. Are zeros OK? //FIX: Support other algorithms
256 CSSM_DATA ivData = {.Data=(void*)&iv, .Length=sizeof(iv)};
258 if (!checkcssm(CSSM_CSP_CreateSymmetricContext(cspHandle,
259 algorithm, //CSSM_ALGID_3DES_3KEY_EDE
260 defaultModeForAlgorithm(algorithm),
264 defaultPaddingForAlgorithm(algorithm),
267 @"CSSM_CSP_CreateSymmetricContext"))
271 NSData *result = nil;
272 CSSM_WRAP_KEY wrappedKey = {};
273 if (checkcssm(CSSM_WrapKey(ctx, &credentials, self.cssmKey, NULL, &wrappedKey),
275 // ...and copy the wrapped key data to the result NSData:
276 result = [NSData dataWithBytes: wrappedKey.KeyData.Data length: wrappedKey.KeyData.Length];
277 CSSM_FreeKey(cspHandle, &credentials, &wrappedKey, NO);
279 // Finally, delete the context
280 CSSM_DeleteContext(ctx);
286 - (SecExternalItemType) keyClass {
287 return kSecItemTypeSessionKey;
290 - (CCAlgorithm) algorithm {
291 CSSM_ALGORITHMS cssmAlg;
292 cssmAlg = self.cssmKey->KeyHeader.AlgorithmId;
295 return kCCAlgorithmAES128;
297 return kCCAlgorithmDES;
298 case CSSM_ALGID_3DES_3KEY:
299 return kCCAlgorithm3DES;
300 case CSSM_ALGID_CAST:
301 return kCCAlgorithmCAST;
303 return kCCAlgorithmRC4;
305 Warn(@"CSSM_ALGORITHMS #%u doesn't map to any CCAlgorithm", cssmAlg);
306 return (CCAlgorithm)-1;
310 - (const char*) algorithmName {
311 CCAlgorithm a = self.algorithm;
312 if (a >= 0 && a <= kCCAlgorithmRC4)
313 return kCCAlgorithmNames[a];
319 - (NSString*) description {
320 return $sprintf(@"%@[%u-bit %s]", [self class], self.keySizeInBits, self.algorithmName);
324 - (NSData*) _cryptData: (NSData*)data operation: (CCOperation)op options: (CCOptions)options
326 NSData *keyData = self.keyData;
327 Assert(keyData, @"Couldn't get key data");
328 NSMutableData *output = [NSMutableData dataWithLength: data.length + 256];
329 size_t bytesWritten = 0;
330 CCCryptorStatus status = CCCrypt(op, self.algorithm, options,
331 keyData.bytes, keyData.length, NULL,
332 data.bytes, data.length, output.mutableBytes, output.length,
335 Warn(@"MYSymmetricKey: CCCrypt returned error %i",status);
338 output.length = bytesWritten;
342 - (NSData*) encryptData: (NSData*)data {
343 return [self _cryptData: data operation: kCCEncrypt options: kCCOptionPKCS7Padding];
347 - (NSData*) decryptData: (NSData*)data {
348 return [self _cryptData: data operation: kCCDecrypt options: kCCOptionPKCS7Padding];
358 static CSSM_KEY* cssmKeyFromData( NSData *keyData,
359 CSSM_ALGORITHMS algorithm,
360 MYKeychain *keychain ) {
361 // Thanks to Jim Murphy for showing the way!
362 if (!keychain) keychain = [MYKeychain defaultKeychain];
363 CSSM_CC_HANDLE ccHandle;
364 if (!checkcssm(CSSM_CSP_CreateSymmetricContext([keychain CSPHandle],
365 CSSM_ALGID_NONE, CSSM_ALGMODE_WRAP,
367 CSSM_PADDING_NONE, NULL,
369 @"CSSM_CSP_CreateSymmetricContext"))
372 CSSM_KEY wrappedKey = {
374 .BlobType = CSSM_KEYBLOB_RAW,
375 .Format = CSSM_KEYBLOB_RAW_FORMAT_NONE,
376 .AlgorithmId = algorithm,
377 .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
378 .LogicalKeySizeInBits = keyData.length*8,
379 .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
380 .KeyUsage = CSSM_KEYUSE_ANY
383 .Data = (void*)keyData.bytes,
384 .Length = keyData.length
388 CSSM_KEY *outKey = calloc(sizeof(CSSM_KEY),1);
390 if (!checkcssm(CSSM_UnwrapKey(ccHandle,
394 CSSM_KEYATTR_EXTRACTABLE,
399 @"CSSM_UnwrapKey")) {
403 CSSM_DeleteContext(ccHandle);
408 #if !TARGET_OS_IPHONE
409 static CSSM_KEY* unwrapCssmKeyFromData(NSData *wrappedData,
410 CSSM_ALGORITHMS algorithm,
411 unsigned sizeInBits) {
412 Warn(@"MYSymmetricKey: unwrapping is unimplemented; sorry");
414 #if 0 //not finished yet
415 // First create a wrapped-key structure from the data:
416 CSSM_WRAP_KEY wrappedKey = {
418 .BlobType = CSSM_KEYBLOB_WRAPPED,
419 .Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
420 .AlgorithmId = algorithm,
421 .KeyClass = CSSM_KEYCLASS_SESSION_KEY,
422 .LogicalKeySizeInBits = sizeInBits,
423 .KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
424 .KeyUsage = CSSM_KEYUSE_ANY,
425 .WrapAlgorithmId = CSSM_ALGID_AES,
428 .Data = (void*)wrappedData.bytes,
429 .Length = wrappedData.length
437 // Create salt data of a specific length from an arbitrary NSObject. */
438 static CSSM_DATA makeSalt( id salty, size_t length ) {
439 // Convert to NSData if necessary:
441 if (![salty isKindOfClass: [NSData class]])
442 salty = [[salty description] dataUsingEncoding: NSUTF8StringEncoding];
443 // Repeat enough times to fill the desired length:
444 NSMutableData *salt = [[salty mutableCopy] autorelease];
445 CAssert(salt.length>0);
446 while (salt.length < length) {
447 [salt appendData: salt];
449 // Truncate to length and return it:
450 salt.length = length;
451 return (CSSM_DATA){.Data=(void*)salt.bytes, .Length=salt.length};
456 // Code from Keychain.framework:
458 #if !TARGET_OS_IPHONE
460 static CSSM_ENCRYPT_MODE defaultModeForAlgorithm(CSSM_ALGORITHMS algorithm) {
462 // 8-byte block ciphers
464 case CSSM_ALGID_3DES_3KEY_EDE:
467 return CSSM_ALGMODE_CBCPadIV8; break;
468 // 16-byte block ciphers
470 return CSSM_ALGMODE_CBCPadIV8; break;
474 return CSSM_ALGMODE_NONE; break;
477 Warn(@"Asked for the default mode for algorithm %d, but don't know that algorithm.\n", algorithm);
478 return CSSM_ALGMODE_NONE;
484 static CSSM_PADDING defaultPaddingForAlgorithm(CSSM_ALGORITHMS algorithm) {
486 /* 8-byte block ciphers */
488 case CSSM_ALGID_3DES_3KEY_EDE:
491 return CSSM_PADDING_PKCS5; break;
492 /* 16-byte block ciphers */
494 return CSSM_PADDING_PKCS7; break;
498 return CSSM_PADDING_NONE; break;
499 /* RSA/DSA asymmetric */
502 return CSSM_PADDING_PKCS1; break;
505 Warn(@"Asked for the default padding mode for %d, but don't know that algorithm.\n", algorithm);
506 return CSSM_PADDING_NONE;
513 // Code below was copied from SecImportExportUtils.cpp in Apple's libsecurity_keychain project
517 * Given a context specified via a CSSM_CC_HANDLE, add a new
518 * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType,
519 * AttributeLength, and an untyped pointer.
521 static CSSM_RETURN impExpAddContextAttribute(CSSM_CC_HANDLE CCHandle,
522 uint32 AttributeType,
523 uint32 AttributeLength,
524 const void *AttributePtr)
526 CSSM_CONTEXT_ATTRIBUTE newAttr;
528 newAttr.AttributeType = AttributeType;
529 newAttr.AttributeLength = AttributeLength;
530 newAttr.Attribute.Data = (CSSM_DATA_PTR)AttributePtr;
531 return CSSM_UpdateContextAttributes(CCHandle, 1, &newAttr);
535 * Add a CFString to a crypto context handle.
537 static CSSM_RETURN impExpAddStringAttr(
538 CSSM_CC_HANDLE ccHand,
540 CSSM_ATTRIBUTE_TYPE attrType)
542 /* CFStrings are passed as external rep in UTF8 encoding by convention */
544 outData = CFStringCreateExternalRepresentation(NULL,
545 str, kCFStringEncodingUTF8, 0); // lossByte 0 ==> no loss allowed
546 if(outData == NULL) {
547 Warn(@"impExpAddStringAttr: bad string format");
552 attrData.Data = (uint8 *)CFDataGetBytePtr(outData);
553 attrData.Length = CFDataGetLength(outData);
554 CSSM_RETURN crtn = impExpAddContextAttribute(ccHand, attrType, sizeof(CSSM_DATA),
558 Warn(@"impExpAddStringAttr: CSSM_UpdateContextAttributes error");
564 * Generate a secure passphrase key. Caller must eventually CSSM_FreeKey the result.
566 static CSSM_RETURN impExpCreatePassKey(
567 const SecKeyImportExportParameters *keyParams, // required
568 CSSM_CSP_HANDLE cspHand, // MUST be CSPDL
569 BOOL verifyPhrase, // use 2nd passphrase textfield for verification?
570 CSSM_KEY_PTR *passKey) // mallocd and RETURNED
573 CSSM_CC_HANDLE ccHand;
575 CSSM_DATA dummyLabel;
576 CSSM_KEY_PTR ourKey = NULL;
578 Log(@"Generating secure passphrase key");
579 ourKey = (CSSM_KEY_PTR)malloc(sizeof(CSSM_KEY));
583 memset(ourKey, 0, sizeof(CSSM_KEY));
585 crtn = CSSM_CSP_CreateKeyGenContext(cspHand,
586 CSSM_ALGID_SECURE_PASSPHRASE,
587 4, // keySizeInBits must be non zero
595 checkcssm(crtn,@"CSSM_CSP_CreateKeyGenContext");
598 /* subsequent errors to errOut: */
600 /* additional context attributes specific to this type of key gen */
601 CAssert(keyParams != NULL); // or we wouldn't be here
602 if(keyParams->alertTitle != NULL) {
603 crtn = impExpAddStringAttr(ccHand, keyParams->alertTitle,
604 CSSM_ATTRIBUTE_ALERT_TITLE);
609 if(keyParams->alertPrompt != NULL) {
610 crtn = impExpAddStringAttr(ccHand, keyParams->alertPrompt,
611 CSSM_ATTRIBUTE_PROMPT);
616 verifyAttr = verifyPhrase ? 1 : 0;
617 crtn = impExpAddContextAttribute(ccHand, CSSM_ATTRIBUTE_VERIFY_PASSPHRASE,
618 sizeof(uint32), (const void *)verifyAttr);
620 checkcssm(crtn,@"impExpAddContextAttribute");
624 dummyLabel.Data = (uint8 *)"Secure Passphrase";
625 dummyLabel.Length = strlen((char *)dummyLabel.Data);
627 uint32 keyAttr = keyParams->keyAttributes;
628 if (keyAttr & CSSM_KEYATTR_SENSITIVE)
629 keyAttr |= CSSM_KEYATTR_RETURN_REF;
631 keyAttr |= CSSM_KEYATTR_EXTRACTABLE;
633 crtn = CSSM_GenerateKey(ccHand,
634 keyParams->keyUsage ?: CSSM_KEYUSE_ANY,
640 checkcssm(crtn,@"CSSM_GenerateKey");
643 CSSM_DeleteContext(ccHand);
644 if(crtn == CSSM_OK) {
647 else if(ourKey != NULL) {
654 #endif !MYCRYPTO_USE_IPHONE_API
659 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
661 Redistribution and use in source and binary forms, with or without modification, are permitted
662 provided that the following conditions are met:
664 * Redistributions of source code must retain the above copyright notice, this list of conditions
665 and the following disclaimer.
666 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
667 and the following disclaimer in the documentation and/or other materials provided with the
670 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
671 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
672 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
673 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
674 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
675 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
676 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
677 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.