2 * Copyright (c) 2004,2011-2014 Apple 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>
40 using namespace Security
;
41 using namespace KeychainCore
;
43 static int hexToDigit(
45 uint8
*rtn
) // RETURNED
47 if((digit
>= '0') && (digit
<= '9')) {
51 if((digit
>= 'a') && (digit
<= 'f')) {
52 *rtn
= digit
- 'a' + 10;
55 if((digit
>= 'A') && (digit
<= 'F')) {
56 *rtn
= digit
- 'A' + 10;
63 * Convert two ascii characters starting at cp to an unsigned char.
64 * Returns nonzero on error.
66 static int hexToUchar(
68 uint8
*rtn
) // RETURNED
72 if(hexToDigit(*cp
++, &c
)) {
76 if(hexToDigit(*cp
, &c
)) {
85 * Given an array of PEM parameter lines, infer parameters for key derivation and
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
,
102 * This format requires PEM parameter lines. We could have gotten here
103 * without them if caller specified wrong format.
105 if(paramLines
== NULL
) {
106 SecImpExpDbg("importWrappedKeyOpenssl: no PEM parameter lines");
107 return errSecUnknownFormat
;
109 CFStringRef dekInfo
= NULL
;
110 CFIndex numLines
= CFArrayGetCount(paramLines
);
111 for(CFIndex dex
=0; dex
<numLines
; dex
++) {
112 CFStringRef str
= (CFStringRef
)CFArrayGetValueAtIndex(paramLines
, dex
);
114 range
= CFStringFind(str
, CFSTR("DEK-Info: "), 0);
115 if(range
.length
!= 0) {
120 if(dekInfo
== NULL
) {
121 SecImpExpDbg("importWrappedKeyOpenssl: no DEK-Info lines");
122 return errSecUnknownFormat
;
125 /* drop down to C strings for low level grunging */
127 if(!CFStringGetCString(dekInfo
, cstr
, sizeof(cstr
), kCFStringEncodingASCII
)) {
128 SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (1)");
129 return errSecUnknownFormat
;
133 * This line looks like this:
134 * DEK-Info: DES-CBC,A22977A0A6A6F696
136 * Now parse, getting the cipher spec and the IV.
138 char *cp
= strchr(cstr
, ':');
140 SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (2)");
141 return errSecUnknownFormat
;
143 if((cp
[1] == ' ') && (cp
[2] != '\0')) {
144 /* as it normally does... */
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;
155 else if(!strncmp(cp
, "DES-CBC", 7)) {
156 keyAlg
= CSSM_ALGID_DES
;
157 encrAlg
= CSSM_ALGID_DES
;
159 blockSizeInBytes
= 8;
162 SecImpExpDbg("importWrappedKeyOpenssl: unrecognized wrap alg (%s)",
164 return errSecUnknownFormat
;
167 /* these are more or less fixed */
168 pbeAlg
= CSSM_ALGID_PBE_OPENSSL_MD5
;
169 encrMode
= CSSM_ALGMODE_CBCPadIV8
;
170 encrPad
= CSSM_PADDING_PKCS7
;
172 /* now get the ASCII hex version of the IV */
173 cp
= strchr(cp
, ',');
175 SecImpExpDbg("importWrappedKeyOpenssl: No IV in DEK-Info line");
176 return errSecUnknownFormat
;
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
;
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
;
195 return errSecSuccess
;
199 * Common code to derive an openssl-wrap style wrap/unwrap key.
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
)
211 CFDataRef cfPhrase
= NULL
;
212 CSSM_KEY
*passKey
= NULL
;
215 /* passphrase or passkey? */
216 ortn
= impExpPassphraseCommon(keyParams
, cspHand
, SPF_Data
, vp
,
217 (CFTypeRef
*)&cfPhrase
, &passKey
);
221 /* subsequent errors to errOut: */
223 CSSM_CRYPTO_DATA seed
;
224 CSSM_CC_HANDLE ccHand
= 0;
225 CSSM_ACCESS_CREDENTIALS creds
;
227 CSSM_DATA param
= {0, NULL
};
228 CSSM_DATA dummyLabel
;
230 memset(&seed
, 0, sizeof(seed
));
231 if(cfPhrase
!= NULL
) {
232 size_t len
= CFDataGetLength(cfPhrase
);
233 coder
.allocItem(seed
.Param
, len
);
234 memmove(seed
.Param
.Data
, CFDataGetBytePtr(cfPhrase
), len
);
238 memset(&creds
, 0, sizeof(CSSM_ACCESS_CREDENTIALS
));
239 ortn
= CSSM_CSP_CreateDeriveKeyContext(cspHand
,
245 1, // iterCount - yup, this is what openssl does
250 SecImpExpDbg("deriveKeyOpensslWrap: CSSM_CSP_CreateDeriveKeyContext error");
254 memset(derivedKey
, 0, sizeof(CSSM_KEY
));
256 dummyLabel
.Data
= (uint8
*)"temp unwrap key";
257 dummyLabel
.Length
= strlen((char *)dummyLabel
.Data
);
259 ortn
= CSSM_DeriveKey(ccHand
,
260 ¶m
, // i.e., derived IV - don't want one
262 /* not extractable even for the short time this key lives */
263 CSSM_KEYATTR_RETURN_REF
| CSSM_KEYATTR_SENSITIVE
,
265 NULL
, // cred and acl
268 SecImpExpDbg("importWrappedKeyOpenssl: PKCS5 v1.5 CSSM_DeriveKey failure");
273 CSSM_DeleteContext(ccHand
);
275 if(passKey
!= NULL
) {
276 CSSM_FreeKey(cspHand
, NULL
, passKey
, CSSM_FALSE
);
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
289 assert(mExternFormat
== kSecFormatWrappedOpenSSL
);
291 /* I think this is an assert - only private keys are wrapped in opensssl format */
292 assert(mExternType
== kSecItemTypePrivateKey
);
293 assert(cspHand
!= 0);
295 if(keyParams
== NULL
) {
301 impExpKeyUnwrapParams unwrapParams
;
302 CSSM_ALGORITHMS pbeAlg
= CSSM_ALGID_NONE
;
303 CSSM_ALGORITHMS keyAlg
= CSSM_ALGID_NONE
;
304 uint32 keySizeInBits
;
305 unsigned blockSizeInBytes
;
307 memset(&unwrapParams
, 0, sizeof(unwrapParams
));
309 /* parse PEM header lines */
310 ortn
= opensslPbeParams(mPemParamLines
, coder
,
312 unwrapParams
.encrAlg
,
313 unwrapParams
.encrMode
,
314 unwrapParams
.encrPad
,
322 /* derive unwrapping key */
323 CSSM_KEY unwrappingKey
;
325 ortn
= deriveKeyOpensslWrap(keyParams
, cspHand
, VP_Import
, pbeAlg
, keyAlg
,
327 unwrapParams
.iv
, /* salt = IV for these algs */
333 /* set up key to unwrap */
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
;
347 wrappedKey
.KeyData
.Data
= (uint8
*)CFDataGetBytePtr(mExternal
);
348 wrappedKey
.KeyData
.Length
= CFDataGetLength(mExternal
);
350 unwrapParams
.unwrappingKey
= &unwrappingKey
;
353 ortn
= impExpImportKeyCommon(&wrappedKey
, importKeychain
, cspHand
,
354 flags
, keyParams
, &unwrapParams
, NULL
, outArray
);
356 if(unwrappingKey
.KeyData
.Data
!= NULL
) {
357 CSSM_FreeKey(cspHand
, NULL
, &unwrappingKey
, CSSM_FALSE
);
363 * Hard coded parameters for export, we only do one flavor.
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
372 OSStatus
impExpWrappedKeyOpenSslExport(
374 SecItemImportExportFlags flags
,
375 const SecKeyImportExportParameters
*keyParams
, // optional
376 CFMutableDataRef outData
, // output appended here
377 const char **pemHeader
, // RETURNED
378 CFArrayRef
*pemParamLines
) // RETURNED
380 DevRandomGenerator rng
;
382 CSSM_CSP_HANDLE cspHand
= 0;
384 bool releaseCspHand
= false;
385 CFMutableArrayRef paramLines
;
390 if(keyParams
== NULL
) {
394 /* we need a CSPDL handle - try to get it from the key */
395 ortn
= SecKeyGetCSPHandle(secKey
, &cspHand
);
397 cspHand
= cuCspStartup(CSSM_FALSE
);
399 return CSSMERR_CSSM_ADDIN_LOAD_FAILED
;
401 releaseCspHand
= true;
403 /* subsequent errors to errOut: */
405 /* 8 bytes of random IV/salt */
407 CSSM_DATA saltIvData
= { 8, saltIv
} ;
408 rng
.random(saltIv
, 8);
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
423 /* wrap the outgoing key */
425 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
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
,
437 * That wrapped key's KeyData is our output
439 CFDataAppendBytes(outData
, wrappedKey
.KeyData
.Data
, wrappedKey
.KeyData
.Length
);
441 /* PEM header depends on key algorithm */
442 switch(wrappedKey
.KeyHeader
.AlgorithmId
) {
444 *pemHeader
= PEM_STRING_RSA
;
447 *pemHeader
= PEM_STRING_DH_PRIVATE
;
450 *pemHeader
= PEM_STRING_DSA
;
452 case CSSM_ALGID_ECDSA
:
453 *pemHeader
= PEM_STRING_ECDSA_PRIVATE
;
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";
461 CSSM_FreeKey(cspHand
, NULL
, &wrappedKey
, CSSM_FALSE
);
464 * Last thing: set up outgoing PEM parameter lines
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
);
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
;
488 if(wrappingKey
.KeyData
.Data
!= NULL
) {
489 CSSM_FreeKey(cspHand
, NULL
, &wrappingKey
, CSSM_FALSE
);