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>
39 using namespace Security
;
40 using namespace KeychainCore
;
42 static int hexToDigit(
44 uint8
*rtn
) // RETURNED
46 if((digit
>= '0') && (digit
<= '9')) {
50 if((digit
>= 'a') && (digit
<= 'f')) {
51 *rtn
= digit
- 'a' + 10;
54 if((digit
>= 'A') && (digit
<= 'F')) {
55 *rtn
= digit
- 'A' + 10;
62 * Convert two ascii characters starting at cp to an unsigned char.
63 * Returns nonzero on error.
65 static int hexToUchar(
67 uint8
*rtn
) // RETURNED
71 if(hexToDigit(*cp
++, &c
)) {
75 if(hexToDigit(*cp
, &c
)) {
84 * Given an array of PEM parameter lines, infer parameters for key derivation and
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
,
101 * This format requires PEM parameter lines. We could have gotten here
102 * without them if caller specified wrong format.
104 if(paramLines
== NULL
) {
105 SecImpExpDbg("importWrappedKeyOpenssl: no PEM parameter lines");
106 return errSecUnknownFormat
;
108 CFStringRef dekInfo
= NULL
;
109 CFIndex numLines
= CFArrayGetCount(paramLines
);
110 for(CFIndex dex
=0; dex
<numLines
; dex
++) {
111 CFStringRef str
= (CFStringRef
)CFArrayGetValueAtIndex(paramLines
, dex
);
113 range
= CFStringFind(str
, CFSTR("DEK-Info: "), 0);
114 if(range
.length
!= 0) {
119 if(dekInfo
== NULL
) {
120 SecImpExpDbg("importWrappedKeyOpenssl: no DEK-Info lines");
121 return errSecUnknownFormat
;
124 /* drop down to C strings for low level grunging */
126 if(!CFStringGetCString(dekInfo
, cstr
, sizeof(cstr
), kCFStringEncodingASCII
)) {
127 SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (1)");
128 return errSecUnknownFormat
;
132 * This line looks like this:
133 * DEK-Info: DES-CBC,A22977A0A6A6F696
135 * Now parse, getting the cipher spec and the IV.
137 char *cp
= strchr(cstr
, ':');
139 SecImpExpDbg("importWrappedKeyOpenssl: bad DEK-Info line (2)");
140 return errSecUnknownFormat
;
142 if((cp
[1] == ' ') && (cp
[2] != '\0')) {
143 /* as it normally does... */
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;
154 else if(!strncmp(cp
, "DES-CBC", 7)) {
155 keyAlg
= CSSM_ALGID_DES
;
156 encrAlg
= CSSM_ALGID_DES
;
158 blockSizeInBytes
= 8;
161 SecImpExpDbg("importWrappedKeyOpenssl: unrecognized wrap alg (%s)",
163 return errSecUnknownFormat
;
166 /* these are more or less fixed */
167 pbeAlg
= CSSM_ALGID_PBE_OPENSSL_MD5
;
168 encrMode
= CSSM_ALGMODE_CBCPadIV8
;
169 encrPad
= CSSM_PADDING_PKCS7
;
171 /* now get the ASCII hex version of the IV */
172 cp
= strchr(cp
, ',');
174 SecImpExpDbg("importWrappedKeyOpenssl: No IV in DEK-Info line");
175 return errSecUnknownFormat
;
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
;
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
;
194 return errSecSuccess
;
198 * Common code to derive an openssl-wrap style wrap/unwrap key.
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
)
210 CFDataRef cfPhrase
= NULL
;
211 CSSM_KEY
*passKey
= NULL
;
214 /* passphrase or passkey? */
215 ortn
= impExpPassphraseCommon(keyParams
, cspHand
, SPF_Data
, vp
,
216 (CFTypeRef
*)&cfPhrase
, &passKey
);
220 /* subsequent errors to errOut: */
222 CSSM_CRYPTO_DATA seed
;
223 CSSM_CC_HANDLE ccHand
= 0;
224 CSSM_ACCESS_CREDENTIALS creds
;
226 CSSM_DATA param
= {0, NULL
};
227 CSSM_DATA dummyLabel
;
229 memset(&seed
, 0, sizeof(seed
));
230 if(cfPhrase
!= NULL
) {
231 size_t len
= CFDataGetLength(cfPhrase
);
232 coder
.allocItem(seed
.Param
, len
);
233 memmove(seed
.Param
.Data
, CFDataGetBytePtr(cfPhrase
), len
);
237 memset(&creds
, 0, sizeof(CSSM_ACCESS_CREDENTIALS
));
238 ortn
= CSSM_CSP_CreateDeriveKeyContext(cspHand
,
244 1, // iterCount - yup, this is what openssl does
249 SecImpExpDbg("deriveKeyOpensslWrap: CSSM_CSP_CreateDeriveKeyContext error");
253 memset(derivedKey
, 0, sizeof(CSSM_KEY
));
255 dummyLabel
.Data
= (uint8
*)"temp unwrap key";
256 dummyLabel
.Length
= strlen((char *)dummyLabel
.Data
);
258 ortn
= CSSM_DeriveKey(ccHand
,
259 ¶m
, // i.e., derived IV - don't want one
261 /* not extractable even for the short time this key lives */
262 CSSM_KEYATTR_RETURN_REF
| CSSM_KEYATTR_SENSITIVE
,
264 NULL
, // cred and acl
267 SecImpExpDbg("importWrappedKeyOpenssl: PKCS5 v1.5 CSSM_DeriveKey failure");
272 CSSM_DeleteContext(ccHand
);
274 if(passKey
!= NULL
) {
275 CSSM_FreeKey(cspHand
, NULL
, passKey
, CSSM_FALSE
);
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
288 assert(mExternFormat
== kSecFormatWrappedOpenSSL
);
290 /* I think this is an assert - only private keys are wrapped in opensssl format */
291 assert(mExternType
== kSecItemTypePrivateKey
);
292 assert(cspHand
!= 0);
294 if(keyParams
== NULL
) {
300 impExpKeyUnwrapParams unwrapParams
;
301 CSSM_ALGORITHMS pbeAlg
= CSSM_ALGID_NONE
;
302 CSSM_ALGORITHMS keyAlg
= CSSM_ALGID_NONE
;
303 uint32 keySizeInBits
;
304 unsigned blockSizeInBytes
;
306 memset(&unwrapParams
, 0, sizeof(unwrapParams
));
308 /* parse PEM header lines */
309 ortn
= opensslPbeParams(mPemParamLines
, coder
,
311 unwrapParams
.encrAlg
,
312 unwrapParams
.encrMode
,
313 unwrapParams
.encrPad
,
321 /* derive unwrapping key */
322 CSSM_KEY unwrappingKey
;
324 ortn
= deriveKeyOpensslWrap(keyParams
, cspHand
, VP_Import
, pbeAlg
, keyAlg
,
326 unwrapParams
.iv
, /* salt = IV for these algs */
332 /* set up key to unwrap */
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
;
346 wrappedKey
.KeyData
.Data
= (uint8
*)CFDataGetBytePtr(mExternal
);
347 wrappedKey
.KeyData
.Length
= CFDataGetLength(mExternal
);
349 unwrapParams
.unwrappingKey
= &unwrappingKey
;
352 ortn
= impExpImportKeyCommon(&wrappedKey
, importKeychain
, cspHand
,
353 flags
, keyParams
, &unwrapParams
, NULL
, outArray
);
355 if(unwrappingKey
.KeyData
.Data
!= NULL
) {
356 CSSM_FreeKey(cspHand
, NULL
, &unwrappingKey
, CSSM_FALSE
);
362 * Hard coded parameters for export, we only do one flavor.
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
371 OSStatus
impExpWrappedKeyOpenSslExport(
373 SecItemImportExportFlags flags
,
374 const SecKeyImportExportParameters
*keyParams
, // optional
375 CFMutableDataRef outData
, // output appended here
376 const char **pemHeader
, // RETURNED
377 CFArrayRef
*pemParamLines
) // RETURNED
380 CSSM_CSP_HANDLE cspHand
= 0;
382 bool releaseCspHand
= false;
383 CFMutableArrayRef paramLines
;
388 if(keyParams
== NULL
) {
392 /* we need a CSPDL handle - try to get it from the key */
393 ortn
= SecKeyGetCSPHandle(secKey
, &cspHand
);
395 cspHand
= cuCspStartup(CSSM_FALSE
);
397 return CSSMERR_CSSM_ADDIN_LOAD_FAILED
;
399 releaseCspHand
= true;
401 /* subsequent errors to errOut: */
403 /* 8 bytes of random IV/salt */
405 CSSM_DATA saltIvData
= { 8, saltIv
} ;
406 MacOSError::check(SecRandomCopyBytes(kSecRandomDefault
, sizeof(saltIv
), saltIv
));
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
421 /* wrap the outgoing key */
423 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
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
,
435 * That wrapped key's KeyData is our output
437 CFDataAppendBytes(outData
, wrappedKey
.KeyData
.Data
, wrappedKey
.KeyData
.Length
);
439 /* PEM header depends on key algorithm */
440 switch(wrappedKey
.KeyHeader
.AlgorithmId
) {
442 *pemHeader
= PEM_STRING_RSA
;
445 *pemHeader
= PEM_STRING_DH_PRIVATE
;
448 *pemHeader
= PEM_STRING_DSA
;
450 case CSSM_ALGID_ECDSA
:
451 *pemHeader
= PEM_STRING_ECDSA_PRIVATE
;
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";
459 CSSM_FreeKey(cspHand
, NULL
, &wrappedKey
, CSSM_FALSE
);
462 * Last thing: set up outgoing PEM parameter lines
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
);
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
;
486 if(wrappingKey
.KeyData
.Data
!= NULL
) {
487 CSSM_FreeKey(cspHand
, NULL
, &wrappingKey
, CSSM_FALSE
);