]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2004,2011-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | * | |
23 | * SecWrappedKeys.cpp - SecExportRep and SecImportRep methods dealing with | |
24 | * wrapped private keys (other than PKCS8 format). | |
25 | */ | |
26 | ||
27 | #include "SecExternalRep.h" | |
28 | #include "SecImportExportUtils.h" | |
29 | #include "SecImportExportPem.h" | |
30 | #include "SecImportExportCrypto.h" | |
31 | #include <Security/cssmtype.h> | |
32 | #include <Security/cssmapi.h> | |
33 | #include <Security/SecKeyPriv.h> | |
34 | #include <security_asn1/SecNssCoder.h> | |
35 | #include <security_cdsa_utils/cuCdsaUtils.h> | |
b1ab9ed8 | 36 | |
d64be36e | 37 | #include <security_utilities/simulatecrash_assert.h> |
b1ab9ed8 A |
38 | |
39 | using namespace Security; | |
40 | using namespace KeychainCore; | |
41 | ||
42 | static int hexToDigit( | |
43 | char digit, | |
44 | uint8 *rtn) // RETURNED | |
45 | { | |
46 | if((digit >= '0') && (digit <= '9')) { | |
47 | *rtn = digit - '0'; | |
48 | return 0; | |
49 | } | |
50 | if((digit >= 'a') && (digit <= 'f')) { | |
51 | *rtn = digit - 'a' + 10; | |
52 | return 0; | |
53 | } | |
54 | if((digit >= 'A') && (digit <= 'F')) { | |
55 | *rtn = digit - 'A' + 10; | |
56 | return 0; | |
57 | } | |
58 | return -1; | |
59 | } | |
60 | ||
61 | /* | |
62 | * Convert two ascii characters starting at cp to an unsigned char. | |
63 | * Returns nonzero on error. | |
64 | */ | |
65 | static int hexToUchar( | |
66 | const char *cp, | |
67 | uint8 *rtn) // RETURNED | |
68 | { | |
69 | uint8 rtnc = 0; | |
70 | uint8 c; | |
71 | if(hexToDigit(*cp++, &c)) { | |
72 | return -1; | |
73 | } | |
74 | rtnc = c << 4; | |
75 | if(hexToDigit(*cp, &c)) { | |
76 | return -1; | |
77 | } | |
78 | rtnc |= c; | |
79 | *rtn = rtnc; | |
80 | return 0; | |
81 | } | |
82 | ||
83 | /* | |
84 | * Given an array of PEM parameter lines, infer parameters for key derivation and | |
85 | * encryption. | |
86 | */ | |
87 | static OSStatus opensslPbeParams( | |
88 | CFArrayRef paramLines, // elements are CFStrings | |
89 | SecNssCoder &coder, // IV allocd with this | |
90 | /* remaining arguments RETURNED */ | |
91 | CSSM_ALGORITHMS &pbeAlg, | |
92 | CSSM_ALGORITHMS &keyAlg, | |
93 | CSSM_ALGORITHMS &encrAlg, | |
94 | CSSM_ENCRYPT_MODE &encrMode, | |
95 | CSSM_PADDING &encrPad, | |
96 | uint32 &keySizeInBits, | |
97 | unsigned &blockSizeInBytes, | |
98 | CSSM_DATA &iv) | |
99 | { | |
100 | /* | |
101 | * This format requires PEM parameter lines. We could have gotten here | |
102 | * without them if caller specified wrong format. | |
103 | */ | |
104 | if(paramLines == NULL) { | |
105 | SecImpExpDbg("importWrappedKeyOpenssl: no PEM parameter lines"); | |
106 | return errSecUnknownFormat; | |
107 | } | |
108 | CFStringRef dekInfo = NULL; | |
109 | CFIndex numLines = CFArrayGetCount(paramLines); | |
110 | for(CFIndex dex=0; dex<numLines; dex++) { | |
111 | CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(paramLines, dex); | |
112 | CFRange range; | |
113 | range = CFStringFind(str, CFSTR("DEK-Info: "), 0); | |
114 | if(range.length != 0) { | |
115 | dekInfo = str; | |
116 | break; | |
117 | } | |
118 | } | |
119 | if(dekInfo == NULL) { | |
120 | SecImpExpDbg("importWrappedKeyOpenssl: no DEK-Info lines"); | |
121 | return errSecUnknownFormat; | |
122 | } | |
123 | ||
124 | /* drop down to C strings for low level grunging */ | |
125 | char cstr[1024]; | |
126 | if(!CFStringGetCString(dekInfo, cstr, sizeof(cstr), kCFStringEncodingASCII)) { | |
127 | SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (1)"); | |
128 | return errSecUnknownFormat; | |
129 | } | |
130 | ||
131 | /* | |
132 | * This line looks like this: | |
133 | * DEK-Info: DES-CBC,A22977A0A6A6F696 | |
134 | * | |
135 | * Now parse, getting the cipher spec and the IV. | |
136 | */ | |
137 | char *cp = strchr(cstr, ':'); | |
138 | if(cp == NULL) { | |
139 | SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (2)"); | |
140 | return errSecUnknownFormat; | |
141 | } | |
142 | if((cp[1] == ' ') && (cp[2] != '\0')) { | |
143 | /* as it normally does... */ | |
144 | cp += 2; | |
145 | } | |
146 | ||
147 | /* We only support DES and 3DES here */ | |
148 | if(!strncmp(cp, "DES-EDE3-CBC", 12)) { | |
149 | keyAlg = CSSM_ALGID_3DES_3KEY; | |
150 | encrAlg = CSSM_ALGID_3DES_3KEY_EDE; | |
151 | keySizeInBits = 64 * 3; | |
152 | blockSizeInBytes = 8; | |
153 | } | |
154 | else if(!strncmp(cp, "DES-CBC", 7)) { | |
155 | keyAlg = CSSM_ALGID_DES; | |
156 | encrAlg = CSSM_ALGID_DES; | |
157 | keySizeInBits = 64; | |
158 | blockSizeInBytes = 8; | |
159 | } | |
160 | else { | |
161 | SecImpExpDbg("importWrappedKeyOpenssl: unrecognized wrap alg (%s)", | |
162 | cp); | |
163 | return errSecUnknownFormat; | |
164 | } | |
165 | ||
166 | /* these are more or less fixed */ | |
167 | pbeAlg = CSSM_ALGID_PBE_OPENSSL_MD5; | |
168 | encrMode = CSSM_ALGMODE_CBCPadIV8; | |
169 | encrPad = CSSM_PADDING_PKCS7; | |
170 | ||
171 | /* now get the ASCII hex version of the IV */ | |
172 | cp = strchr(cp, ','); | |
173 | if(cp == NULL) { | |
174 | SecImpExpDbg("importWrappedKeyOpenssl: No IV in DEK-Info line"); | |
175 | return errSecUnknownFormat; | |
176 | } | |
177 | if(cp[1] != '\0') { | |
178 | cp++; | |
179 | } | |
180 | ||
181 | /* remainder should be just the IV */ | |
182 | if(strlen(cp) != (blockSizeInBytes * 2)) { | |
183 | SecImpExpDbg("importWrappedKeyOpenssl: bad IV in DEK-Info line (1)"); | |
184 | return errSecUnknownFormat; | |
185 | } | |
186 | ||
187 | coder.allocItem(iv, blockSizeInBytes); | |
188 | for(unsigned dex=0; dex<blockSizeInBytes; dex++) { | |
189 | if(hexToUchar(cp + (dex * 2), &iv.Data[dex])) { | |
190 | SecImpExpDbg("importWrappedKeyOpenssl: bad IV in DEK-Info line (2)"); | |
191 | return errSecUnknownFormat; | |
192 | } | |
193 | } | |
427c49bc | 194 | return errSecSuccess; |
b1ab9ed8 A |
195 | } |
196 | ||
197 | /* | |
198 | * Common code to derive an openssl-wrap style wrap/unwrap key. | |
199 | */ | |
200 | static OSStatus deriveKeyOpensslWrap( | |
201 | const SecKeyImportExportParameters *keyParams, // required | |
202 | CSSM_CSP_HANDLE cspHand, // required | |
203 | impExpVerifyPhrase vp, // import/export | |
204 | CSSM_ALGORITHMS pbeAlg, | |
205 | CSSM_ALGORITHMS keyAlg, | |
206 | uint32 keySizeInBits, | |
207 | const CSSM_DATA &salt, | |
208 | CSSM_KEY_PTR derivedKey) | |
209 | { | |
210 | CFDataRef cfPhrase = NULL; | |
211 | CSSM_KEY *passKey = NULL; | |
212 | OSStatus ortn; | |
213 | ||
214 | /* passphrase or passkey? */ | |
215 | ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, vp, | |
216 | (CFTypeRef *)&cfPhrase, &passKey); | |
217 | if(ortn) { | |
218 | return ortn; | |
219 | } | |
220 | /* subsequent errors to errOut: */ | |
221 | ||
222 | CSSM_CRYPTO_DATA seed; | |
223 | CSSM_CC_HANDLE ccHand = 0; | |
224 | CSSM_ACCESS_CREDENTIALS creds; | |
225 | SecNssCoder coder; | |
226 | CSSM_DATA param = {0, NULL}; | |
227 | CSSM_DATA dummyLabel; | |
228 | ||
229 | memset(&seed, 0, sizeof(seed)); | |
230 | if(cfPhrase != NULL) { | |
427c49bc | 231 | size_t len = CFDataGetLength(cfPhrase); |
b1ab9ed8 A |
232 | coder.allocItem(seed.Param, len); |
233 | memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len); | |
234 | CFRelease(cfPhrase); | |
235 | } | |
236 | ||
237 | memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); | |
238 | ortn = CSSM_CSP_CreateDeriveKeyContext(cspHand, | |
239 | pbeAlg, | |
240 | keyAlg, | |
241 | keySizeInBits, | |
242 | &creds, | |
243 | passKey, // BaseKey | |
244 | 1, // iterCount - yup, this is what openssl does | |
245 | &salt, | |
246 | &seed, | |
247 | &ccHand); | |
248 | if(ortn) { | |
249 | SecImpExpDbg("deriveKeyOpensslWrap: CSSM_CSP_CreateDeriveKeyContext error"); | |
250 | goto errOut; | |
251 | } | |
252 | ||
253 | memset(derivedKey, 0, sizeof(CSSM_KEY)); | |
254 | ||
255 | dummyLabel.Data = (uint8 *)"temp unwrap key"; | |
256 | dummyLabel.Length = strlen((char *)dummyLabel.Data); | |
257 | ||
258 | ortn = CSSM_DeriveKey(ccHand, | |
259 | ¶m, // i.e., derived IV - don't want one | |
260 | CSSM_KEYUSE_ANY, | |
261 | /* not extractable even for the short time this key lives */ | |
262 | CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE, | |
263 | &dummyLabel, | |
264 | NULL, // cred and acl | |
265 | derivedKey); | |
266 | if(ortn) { | |
267 | SecImpExpDbg("importWrappedKeyOpenssl: PKCS5 v1.5 CSSM_DeriveKey failure"); | |
268 | } | |
269 | ||
270 | errOut: | |
271 | if(ccHand != 0) { | |
272 | CSSM_DeleteContext(ccHand); | |
273 | } | |
274 | if(passKey != NULL) { | |
275 | CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE); | |
276 | free(passKey); | |
277 | } | |
278 | return ortn; | |
279 | } | |
280 | ||
281 | OSStatus SecImportRep::importWrappedKeyOpenssl( | |
282 | SecKeychainRef importKeychain, // optional | |
283 | CSSM_CSP_HANDLE cspHand, // required | |
284 | SecItemImportExportFlags flags, | |
285 | const SecKeyImportExportParameters *keyParams, // optional | |
286 | CFMutableArrayRef outArray) // optional, append here | |
287 | { | |
288 | assert(mExternFormat == kSecFormatWrappedOpenSSL); | |
289 | ||
290 | /* I think this is an assert - only private keys are wrapped in opensssl format */ | |
291 | assert(mExternType == kSecItemTypePrivateKey); | |
292 | assert(cspHand != 0); | |
293 | ||
294 | if(keyParams == NULL) { | |
427c49bc | 295 | return errSecParam; |
b1ab9ed8 A |
296 | } |
297 | ||
298 | OSStatus ortn; | |
299 | SecNssCoder coder; | |
300 | impExpKeyUnwrapParams unwrapParams; | |
301 | CSSM_ALGORITHMS pbeAlg = CSSM_ALGID_NONE; | |
302 | CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE; | |
303 | uint32 keySizeInBits; | |
304 | unsigned blockSizeInBytes; | |
305 | ||
306 | memset(&unwrapParams, 0, sizeof(unwrapParams)); | |
307 | ||
308 | /* parse PEM header lines */ | |
309 | ortn = opensslPbeParams(mPemParamLines, coder, | |
310 | pbeAlg, keyAlg, | |
311 | unwrapParams.encrAlg, | |
312 | unwrapParams.encrMode, | |
313 | unwrapParams.encrPad, | |
314 | keySizeInBits, | |
315 | blockSizeInBytes, | |
316 | unwrapParams.iv); | |
317 | if(ortn) { | |
318 | return ortn; | |
319 | } | |
320 | ||
321 | /* derive unwrapping key */ | |
322 | CSSM_KEY unwrappingKey; | |
323 | ||
324 | ortn = deriveKeyOpensslWrap(keyParams, cspHand, VP_Import, pbeAlg, keyAlg, | |
325 | keySizeInBits, | |
326 | unwrapParams.iv, /* salt = IV for these algs */ | |
327 | &unwrappingKey); | |
328 | if(ortn) { | |
329 | return ortn; | |
330 | } | |
331 | ||
332 | /* set up key to unwrap */ | |
333 | CSSM_KEY wrappedKey; | |
334 | CSSM_KEYHEADER &hdr = wrappedKey.KeyHeader; | |
335 | memset(&wrappedKey, 0, sizeof(CSSM_KEY)); | |
336 | hdr.HeaderVersion = CSSM_KEYHEADER_VERSION; | |
337 | /* CspId : don't care */ | |
338 | hdr.BlobType = CSSM_KEYBLOB_WRAPPED; | |
339 | hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL; | |
340 | hdr.AlgorithmId = mKeyAlg; | |
341 | hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY; | |
342 | /* LogicalKeySizeInBits : calculated by CSP during unwrap */ | |
343 | hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE; | |
344 | hdr.KeyUsage = CSSM_KEYUSE_ANY; | |
345 | ||
346 | wrappedKey.KeyData.Data = (uint8 *)CFDataGetBytePtr(mExternal); | |
347 | wrappedKey.KeyData.Length = CFDataGetLength(mExternal); | |
348 | ||
349 | unwrapParams.unwrappingKey = &unwrappingKey; | |
350 | ||
351 | /* GO */ | |
352 | ortn = impExpImportKeyCommon(&wrappedKey, importKeychain, cspHand, | |
353 | flags, keyParams, &unwrapParams, NULL, outArray); | |
354 | ||
355 | if(unwrappingKey.KeyData.Data != NULL) { | |
356 | CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE); | |
357 | } | |
358 | return ortn; | |
359 | } | |
360 | ||
361 | /* | |
362 | * Hard coded parameters for export, we only do one flavor. | |
363 | */ | |
364 | #define OPENSSL_WRAP_KEY_ALG CSSM_ALGID_3DES_3KEY | |
365 | #define OPENSSL_WRAP_PBE_ALG CSSM_ALGID_PBE_OPENSSL_MD5 | |
366 | #define OPENSSL_WRAP_KEY_SIZE (64 * 3) | |
367 | #define OPENSSL_WRAP_ENCR_ALG CSSM_ALGID_3DES_3KEY_EDE | |
368 | #define OPENSSL_WRAP_ENCR_MODE CSSM_ALGMODE_CBCPadIV8 | |
369 | #define OPENSSL_WRAP_ENCR_PAD CSSM_PADDING_PKCS7 | |
370 | ||
371 | OSStatus impExpWrappedKeyOpenSslExport( | |
372 | SecKeyRef secKey, | |
373 | SecItemImportExportFlags flags, | |
374 | const SecKeyImportExportParameters *keyParams, // optional | |
375 | CFMutableDataRef outData, // output appended here | |
376 | const char **pemHeader, // RETURNED | |
377 | CFArrayRef *pemParamLines) // RETURNED | |
378 | { | |
b1ab9ed8 A |
379 | SecNssCoder coder; |
380 | CSSM_CSP_HANDLE cspHand = 0; | |
381 | OSStatus ortn; | |
382 | bool releaseCspHand = false; | |
383 | CFMutableArrayRef paramLines; | |
384 | CFStringRef cfStr; | |
385 | char dekStr[100]; | |
386 | char ivStr[3]; | |
387 | ||
388 | if(keyParams == NULL) { | |
427c49bc | 389 | return errSecParam; |
b1ab9ed8 A |
390 | } |
391 | ||
392 | /* we need a CSPDL handle - try to get it from the key */ | |
393 | ortn = SecKeyGetCSPHandle(secKey, &cspHand); | |
394 | if(ortn) { | |
395 | cspHand = cuCspStartup(CSSM_FALSE); | |
396 | if(cspHand == 0) { | |
397 | return CSSMERR_CSSM_ADDIN_LOAD_FAILED; | |
398 | } | |
399 | releaseCspHand = true; | |
400 | } | |
401 | /* subsequent errors to errOut: */ | |
402 | ||
403 | /* 8 bytes of random IV/salt */ | |
404 | uint8 saltIv[8]; | |
405 | CSSM_DATA saltIvData = { 8, saltIv} ; | |
b54c578e A |
406 | MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(saltIv), saltIv)); |
407 | ||
b1ab9ed8 A |
408 | /* derive wrapping key */ |
409 | CSSM_KEY wrappingKey; | |
410 | wrappingKey.KeyData.Data = NULL; | |
411 | wrappingKey.KeyData.Length = 0; | |
412 | ortn = deriveKeyOpensslWrap(keyParams, cspHand, VP_Export, | |
413 | OPENSSL_WRAP_PBE_ALG, OPENSSL_WRAP_KEY_ALG, | |
414 | OPENSSL_WRAP_KEY_SIZE, | |
415 | saltIvData, // IV == salt for this wrapping alg | |
416 | &wrappingKey); | |
417 | if(ortn) { | |
418 | goto errOut; | |
419 | } | |
420 | ||
421 | /* wrap the outgoing key */ | |
422 | CSSM_KEY wrappedKey; | |
423 | memset(&wrappedKey, 0, sizeof(CSSM_KEY)); | |
424 | ||
425 | ortn = impExpExportKeyCommon(cspHand, secKey, &wrappingKey, &wrappedKey, | |
426 | OPENSSL_WRAP_ENCR_ALG, OPENSSL_WRAP_ENCR_MODE, OPENSSL_WRAP_ENCR_PAD, | |
427 | CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL, | |
428 | CSSM_ATTRIBUTE_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE, | |
429 | NULL, &saltIvData); | |
430 | if(ortn) { | |
431 | goto errOut; | |
432 | } | |
433 | ||
434 | /* | |
435 | * That wrapped key's KeyData is our output | |
436 | */ | |
437 | CFDataAppendBytes(outData, wrappedKey.KeyData.Data, wrappedKey.KeyData.Length); | |
438 | ||
439 | /* PEM header depends on key algorithm */ | |
440 | switch(wrappedKey.KeyHeader.AlgorithmId) { | |
441 | case CSSM_ALGID_RSA: | |
442 | *pemHeader = PEM_STRING_RSA; | |
443 | break; | |
444 | case CSSM_ALGID_DH: | |
445 | *pemHeader = PEM_STRING_DH_PRIVATE; | |
446 | break; | |
447 | case CSSM_ALGID_DSA: | |
448 | *pemHeader = PEM_STRING_DSA; | |
449 | break; | |
450 | case CSSM_ALGID_ECDSA: | |
451 | *pemHeader = PEM_STRING_ECDSA_PRIVATE; | |
452 | break; | |
453 | default: | |
454 | SecImpExpDbg("impExpWrappedKeyOpenSslExport unknown private key alg " | |
455 | "%lu", (unsigned long)wrappedKey.KeyHeader.AlgorithmId); | |
456 | /* punt though I think something is seriously hosed */ | |
457 | *pemHeader = "Private Key"; | |
458 | } | |
459 | CSSM_FreeKey(cspHand, NULL, &wrappedKey, CSSM_FALSE); | |
460 | ||
461 | /* | |
462 | * Last thing: set up outgoing PEM parameter lines | |
463 | */ | |
464 | assert(pemParamLines != NULL); | |
465 | paramLines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
466 | cfStr = CFStringCreateWithCString(NULL, | |
467 | "Proc-Type: 4,ENCRYPTED", kCFStringEncodingASCII); | |
468 | CFArrayAppendValue(paramLines, cfStr); | |
469 | CFRelease(cfStr); // owned by array now */ | |
470 | strcpy(dekStr, "DEK-Info: DES-EDE3-CBC,"); | |
471 | /* next goes the IV */ | |
472 | for(unsigned dex=0; dex<8; dex++) { | |
473 | sprintf(ivStr, "%02X", saltIv[dex]); | |
474 | strcat(dekStr, ivStr); | |
475 | } | |
476 | cfStr = CFStringCreateWithCString(NULL, dekStr, kCFStringEncodingASCII); | |
477 | CFArrayAppendValue(paramLines, cfStr); | |
478 | CFRelease(cfStr); // owned by array now */ | |
479 | /* and an empty line */ | |
480 | cfStr = CFStringCreateWithCString(NULL, "", kCFStringEncodingASCII); | |
481 | CFArrayAppendValue(paramLines, cfStr); | |
482 | CFRelease(cfStr); // owned by array now */ | |
483 | *pemParamLines = paramLines; | |
484 | ||
485 | errOut: | |
486 | if(wrappingKey.KeyData.Data != NULL) { | |
487 | CSSM_FreeKey(cspHand, NULL, &wrappingKey, CSSM_FALSE); | |
488 | } | |
489 | return ortn; | |
490 | ||
491 | } | |
492 |