1/* 2 * Copyright (c) 2004,2011-2014 Apple 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> 37 38#include <assert.h> 39 40using namespace Security; 41using namespace KeychainCore; 42 43static 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 */ 66static 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 */ 88static 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 } 195 return errSecSuccess; 196} 197 198/* 199 * Common code to derive an openssl-wrap style wrap/unwrap key. 200 */ 201static 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) { 232 size_t len = CFDataGetLength(cfPhrase); 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 271errOut: 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 282OSStatus 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) { 296 return errSecParam; 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 372OSStatus 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) { 391 return errSecParam; 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 487errOut: 488 if(wrappingKey.KeyData.Data != NULL) { 489 CSSM_FreeKey(cspHand, NULL, &wrappingKey, CSSM_FALSE); 490 } 491 return ortn; 492 493} 494 495