2 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
23 * SecWrappedKeys.cpp - SecExportRep and SecImportRep methods dealing with
24 * wrapped private keys (other than PKCS8 format).
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>
37 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
41 using namespace Security
;
42 using namespace KeychainCore
;
44 static int hexToDigit(
46 uint8
*rtn
) // RETURNED
48 if((digit
>= '0') && (digit
<= '9')) {
52 if((digit
>= 'a') && (digit
<= 'f')) {
53 *rtn
= digit
- 'a' + 10;
56 if((digit
>= 'A') && (digit
<= 'F')) {
57 *rtn
= digit
- 'A' + 10;
64 * Convert two ascii characters starting at cp to an unsigned char.
65 * Returns nonzero on error.
67 static int hexToUchar(
69 uint8
*rtn
) // RETURNED
73 if(hexToDigit(*cp
++, &c
)) {
77 if(hexToDigit(*cp
, &c
)) {
86 * Given an array of PEM parameter lines, infer parameters for key derivation and
89 static OSStatus
opensslPbeParams(
90 CFArrayRef paramLines
, // elements are CFStrings
91 SecNssCoder
&coder
, // IV allocd with this
92 /* remaining arguments RETURNED */
93 CSSM_ALGORITHMS
&pbeAlg
,
94 CSSM_ALGORITHMS
&keyAlg
,
95 CSSM_ALGORITHMS
&encrAlg
,
96 CSSM_ENCRYPT_MODE
&encrMode
,
97 CSSM_PADDING
&encrPad
,
98 uint32
&keySizeInBits
,
99 unsigned &blockSizeInBytes
,
103 * This format requires PEM parameter lines. We could have gotten here
104 * without them if caller specified wrong format.
106 if(paramLines
== NULL
) {
107 SecImpExpDbg("importWrappedKeyOpenssl: no PEM parameter lines");
108 return errSecUnknownFormat
;
110 CFStringRef dekInfo
= NULL
;
111 CFIndex numLines
= CFArrayGetCount(paramLines
);
112 for(CFIndex dex
=0; dex
<numLines
; dex
++) {
113 CFStringRef str
= (CFStringRef
)CFArrayGetValueAtIndex(paramLines
, dex
);
115 range
= CFStringFind(str
, CFSTR("DEK-Info: "), 0);
116 if(range
.length
!= 0) {
121 if(dekInfo
== NULL
) {
122 SecImpExpDbg("importWrappedKeyOpenssl: no DEK-Info lines");
123 return errSecUnknownFormat
;
126 /* drop down to C strings for low level grunging */
128 if(!CFStringGetCString(dekInfo
, cstr
, sizeof(cstr
), kCFStringEncodingASCII
)) {
129 SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (1)");
130 return errSecUnknownFormat
;
134 * This line looks like this:
135 * DEK-Info: DES-CBC,A22977A0A6A6F696
137 * Now parse, getting the cipher spec and the IV.
139 char *cp
= strchr(cstr
, ':');
141 SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (2)");
142 return errSecUnknownFormat
;
144 if((cp
[1] == ' ') && (cp
[2] != '\0')) {
145 /* as it normally does... */
149 /* We only support DES and 3DES here */
150 if(!strncmp(cp
, "DES-EDE3-CBC", 12)) {
151 keyAlg
= CSSM_ALGID_3DES_3KEY
;
152 encrAlg
= CSSM_ALGID_3DES_3KEY_EDE
;
153 keySizeInBits
= 64 * 3;
154 blockSizeInBytes
= 8;
156 else if(!strncmp(cp
, "DES-CBC", 7)) {
157 keyAlg
= CSSM_ALGID_DES
;
158 encrAlg
= CSSM_ALGID_DES
;
160 blockSizeInBytes
= 8;
163 SecImpExpDbg("importWrappedKeyOpenssl: unrecognized wrap alg (%s)",
165 return errSecUnknownFormat
;
168 /* these are more or less fixed */
169 pbeAlg
= CSSM_ALGID_PBE_OPENSSL_MD5
;
170 encrMode
= CSSM_ALGMODE_CBCPadIV8
;
171 encrPad
= CSSM_PADDING_PKCS7
;
173 /* now get the ASCII hex version of the IV */
174 cp
= strchr(cp
, ',');
176 SecImpExpDbg("importWrappedKeyOpenssl: No IV in DEK-Info line");
177 return errSecUnknownFormat
;
183 /* remainder should be just the IV */
184 if(strlen(cp
) != (blockSizeInBytes
* 2)) {
185 SecImpExpDbg("importWrappedKeyOpenssl: bad IV in DEK-Info line (1)");
186 return errSecUnknownFormat
;
189 coder
.allocItem(iv
, blockSizeInBytes
);
190 for(unsigned dex
=0; dex
<blockSizeInBytes
; dex
++) {
191 if(hexToUchar(cp
+ (dex
* 2), &iv
.Data
[dex
])) {
192 SecImpExpDbg("importWrappedKeyOpenssl: bad IV in DEK-Info line (2)");
193 return errSecUnknownFormat
;
200 * Common code to derive an openssl-wrap style wrap/unwrap key.
202 static OSStatus
deriveKeyOpensslWrap(
203 const SecKeyImportExportParameters
*keyParams
, // required
204 CSSM_CSP_HANDLE cspHand
, // required
205 impExpVerifyPhrase vp
, // import/export
206 CSSM_ALGORITHMS pbeAlg
,
207 CSSM_ALGORITHMS keyAlg
,
208 uint32 keySizeInBits
,
209 const CSSM_DATA
&salt
,
210 CSSM_KEY_PTR derivedKey
)
212 CFDataRef cfPhrase
= NULL
;
213 CSSM_KEY
*passKey
= NULL
;
216 /* passphrase or passkey? */
217 ortn
= impExpPassphraseCommon(keyParams
, cspHand
, SPF_Data
, vp
,
218 (CFTypeRef
*)&cfPhrase
, &passKey
);
222 /* subsequent errors to errOut: */
224 CSSM_CRYPTO_DATA seed
;
225 CSSM_CC_HANDLE ccHand
= 0;
226 CSSM_ACCESS_CREDENTIALS creds
;
228 CSSM_DATA param
= {0, NULL
};
229 CSSM_DATA dummyLabel
;
231 memset(&seed
, 0, sizeof(seed
));
232 if(cfPhrase
!= NULL
) {
233 unsigned len
= CFDataGetLength(cfPhrase
);
234 coder
.allocItem(seed
.Param
, len
);
235 memmove(seed
.Param
.Data
, CFDataGetBytePtr(cfPhrase
), len
);
239 memset(&creds
, 0, sizeof(CSSM_ACCESS_CREDENTIALS
));
240 ortn
= CSSM_CSP_CreateDeriveKeyContext(cspHand
,
246 1, // iterCount - yup, this is what openssl does
251 SecImpExpDbg("deriveKeyOpensslWrap: CSSM_CSP_CreateDeriveKeyContext error");
255 memset(derivedKey
, 0, sizeof(CSSM_KEY
));
257 dummyLabel
.Data
= (uint8
*)"temp unwrap key";
258 dummyLabel
.Length
= strlen((char *)dummyLabel
.Data
);
260 ortn
= CSSM_DeriveKey(ccHand
,
261 ¶m
, // i.e., derived IV - don't want one
263 /* not extractable even for the short time this key lives */
264 CSSM_KEYATTR_RETURN_REF
| CSSM_KEYATTR_SENSITIVE
,
266 NULL
, // cred and acl
269 SecImpExpDbg("importWrappedKeyOpenssl: PKCS5 v1.5 CSSM_DeriveKey failure");
274 CSSM_DeleteContext(ccHand
);
276 if(passKey
!= NULL
) {
277 CSSM_FreeKey(cspHand
, NULL
, passKey
, CSSM_FALSE
);
283 OSStatus
SecImportRep::importWrappedKeyOpenssl(
284 SecKeychainRef importKeychain
, // optional
285 CSSM_CSP_HANDLE cspHand
, // required
286 SecItemImportExportFlags flags
,
287 const SecKeyImportExportParameters
*keyParams
, // optional
288 CFMutableArrayRef outArray
) // optional, append here
290 assert(mExternFormat
== kSecFormatWrappedOpenSSL
);
292 /* I think this is an assert - only private keys are wrapped in opensssl format */
293 assert(mExternType
== kSecItemTypePrivateKey
);
294 assert(cspHand
!= 0);
296 if(keyParams
== NULL
) {
302 impExpKeyUnwrapParams unwrapParams
;
303 CSSM_ALGORITHMS pbeAlg
= CSSM_ALGID_NONE
;
304 CSSM_ALGORITHMS keyAlg
= CSSM_ALGID_NONE
;
305 uint32 keySizeInBits
;
306 unsigned blockSizeInBytes
;
308 memset(&unwrapParams
, 0, sizeof(unwrapParams
));
310 /* parse PEM header lines */
311 ortn
= opensslPbeParams(mPemParamLines
, coder
,
313 unwrapParams
.encrAlg
,
314 unwrapParams
.encrMode
,
315 unwrapParams
.encrPad
,
323 /* derive unwrapping key */
324 CSSM_KEY unwrappingKey
;
326 ortn
= deriveKeyOpensslWrap(keyParams
, cspHand
, VP_Import
, pbeAlg
, keyAlg
,
328 unwrapParams
.iv
, /* salt = IV for these algs */
334 /* set up key to unwrap */
336 CSSM_KEYHEADER
&hdr
= wrappedKey
.KeyHeader
;
337 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
338 hdr
.HeaderVersion
= CSSM_KEYHEADER_VERSION
;
339 /* CspId : don't care */
340 hdr
.BlobType
= CSSM_KEYBLOB_WRAPPED
;
341 hdr
.Format
= CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL
;
342 hdr
.AlgorithmId
= mKeyAlg
;
343 hdr
.KeyClass
= CSSM_KEYCLASS_PRIVATE_KEY
;
344 /* LogicalKeySizeInBits : calculated by CSP during unwrap */
345 hdr
.KeyAttr
= CSSM_KEYATTR_EXTRACTABLE
;
346 hdr
.KeyUsage
= CSSM_KEYUSE_ANY
;
348 wrappedKey
.KeyData
.Data
= (uint8
*)CFDataGetBytePtr(mExternal
);
349 wrappedKey
.KeyData
.Length
= CFDataGetLength(mExternal
);
351 unwrapParams
.unwrappingKey
= &unwrappingKey
;
354 ortn
= impExpImportKeyCommon(&wrappedKey
, importKeychain
, cspHand
,
355 flags
, keyParams
, &unwrapParams
, NULL
, outArray
);
357 if(unwrappingKey
.KeyData
.Data
!= NULL
) {
358 CSSM_FreeKey(cspHand
, NULL
, &unwrappingKey
, CSSM_FALSE
);
364 * Hard coded parameters for export, we only do one flavor.
366 #define OPENSSL_WRAP_KEY_ALG CSSM_ALGID_3DES_3KEY
367 #define OPENSSL_WRAP_PBE_ALG CSSM_ALGID_PBE_OPENSSL_MD5
368 #define OPENSSL_WRAP_KEY_SIZE (64 * 3)
369 #define OPENSSL_WRAP_ENCR_ALG CSSM_ALGID_3DES_3KEY_EDE
370 #define OPENSSL_WRAP_ENCR_MODE CSSM_ALGMODE_CBCPadIV8
371 #define OPENSSL_WRAP_ENCR_PAD CSSM_PADDING_PKCS7
373 OSStatus
impExpWrappedKeyOpenSslExport(
375 SecItemImportExportFlags flags
,
376 const SecKeyImportExportParameters
*keyParams
, // optional
377 CFMutableDataRef outData
, // output appended here
378 const char **pemHeader
, // RETURNED
379 CFArrayRef
*pemParamLines
) // RETURNED
381 DevRandomGenerator rng
;
383 CSSM_CSP_HANDLE cspHand
= 0;
385 bool releaseCspHand
= false;
386 CFMutableArrayRef paramLines
;
391 if(keyParams
== NULL
) {
395 /* we need a CSPDL handle - try to get it from the key */
396 ortn
= SecKeyGetCSPHandle(secKey
, &cspHand
);
398 cspHand
= cuCspStartup(CSSM_FALSE
);
400 return CSSMERR_CSSM_ADDIN_LOAD_FAILED
;
402 releaseCspHand
= true;
404 /* subsequent errors to errOut: */
406 /* 8 bytes of random IV/salt */
408 CSSM_DATA saltIvData
= { 8, saltIv
} ;
409 rng
.random(saltIv
, 8);
411 /* derive wrapping key */
412 CSSM_KEY wrappingKey
;
413 wrappingKey
.KeyData
.Data
= NULL
;
414 wrappingKey
.KeyData
.Length
= 0;
415 ortn
= deriveKeyOpensslWrap(keyParams
, cspHand
, VP_Export
,
416 OPENSSL_WRAP_PBE_ALG
, OPENSSL_WRAP_KEY_ALG
,
417 OPENSSL_WRAP_KEY_SIZE
,
418 saltIvData
, // IV == salt for this wrapping alg
424 /* wrap the outgoing key */
426 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
428 ortn
= impExpExportKeyCommon(cspHand
, secKey
, &wrappingKey
, &wrappedKey
,
429 OPENSSL_WRAP_ENCR_ALG
, OPENSSL_WRAP_ENCR_MODE
, OPENSSL_WRAP_ENCR_PAD
,
430 CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL
,
431 CSSM_ATTRIBUTE_NONE
, CSSM_KEYBLOB_RAW_FORMAT_NONE
,
438 * That wrapped key's KeyData is our output
440 CFDataAppendBytes(outData
, wrappedKey
.KeyData
.Data
, wrappedKey
.KeyData
.Length
);
442 /* PEM header depends on key algorithm */
443 switch(wrappedKey
.KeyHeader
.AlgorithmId
) {
445 *pemHeader
= PEM_STRING_RSA
;
448 *pemHeader
= PEM_STRING_DH_PRIVATE
;
451 *pemHeader
= PEM_STRING_DSA
;
453 case CSSM_ALGID_ECDSA
:
454 *pemHeader
= PEM_STRING_ECDSA_PRIVATE
;
457 SecImpExpDbg("impExpWrappedKeyOpenSslExport unknown private key alg "
458 "%lu", (unsigned long)wrappedKey
.KeyHeader
.AlgorithmId
);
459 /* punt though I think something is seriously hosed */
460 *pemHeader
= "Private Key";
462 CSSM_FreeKey(cspHand
, NULL
, &wrappedKey
, CSSM_FALSE
);
465 * Last thing: set up outgoing PEM parameter lines
467 assert(pemParamLines
!= NULL
);
468 paramLines
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
469 cfStr
= CFStringCreateWithCString(NULL
,
470 "Proc-Type: 4,ENCRYPTED", kCFStringEncodingASCII
);
471 CFArrayAppendValue(paramLines
, cfStr
);
472 CFRelease(cfStr
); // owned by array now */
473 strcpy(dekStr
, "DEK-Info: DES-EDE3-CBC,");
474 /* next goes the IV */
475 for(unsigned dex
=0; dex
<8; dex
++) {
476 sprintf(ivStr
, "%02X", saltIv
[dex
]);
477 strcat(dekStr
, ivStr
);
479 cfStr
= CFStringCreateWithCString(NULL
, dekStr
, kCFStringEncodingASCII
);
480 CFArrayAppendValue(paramLines
, cfStr
);
481 CFRelease(cfStr
); // owned by array now */
482 /* and an empty line */
483 cfStr
= CFStringCreateWithCString(NULL
, "", kCFStringEncodingASCII
);
484 CFArrayAppendValue(paramLines
, cfStr
);
485 CFRelease(cfStr
); // owned by array now */
486 *pemParamLines
= paramLines
;
489 if(wrappingKey
.KeyData
.Data
!= NULL
) {
490 CSSM_FreeKey(cspHand
, NULL
, &wrappingKey
, CSSM_FALSE
);