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