/* * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * SecImportExportPkcs8.cpp - support for generating and parsing/decoding * private keys in PKCS8 format. * * The current version (as of March 12 2004) can parse and decode every * PKCS8 blob generated by openssl with the exception of those using * double DES encryption. This has been verified by actually generating * those blobs with openssl and decoding them here. * * PLEASE: don't even *think* about changing a single line of code here * without verifying the results against the full import/export regression * test in SecurityTests/clxutils/importExport. * */ #include #include "SecImportExportPkcs8.h" #include "SecPkcs8Templates.h" #include "SecImportExportUtils.h" #include "SecImportExportCrypto.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SecPkcs8Dbg(args...) secdebug("SecPkcs8", ## args) #pragma mark --- PKCS5 v1.5 Key Derivation --- /* * PKCS5 v1.5. Caller has gleaned everything except salt, * iterationCount, and IV from the AlgId.algorithm OID. * * We get salt and iteration count from the incoming alg params. * IV is derived along with the unwrapping key from the passphrase. */ static CSSM_RETURN pkcs5_v15_genKey( CSSM_CSP_HANDLE cspHand, SecNssCoder &coder, const SecKeyImportExportParameters *keyParams, const CSSM_DATA ¶mData, CSSM_ALGORITHMS keyAlg, CSSM_ALGORITHMS pbeHashAlg, uint32 keySizeInBits, uint32 blockSizeInBytes, impExpKeyUnwrapParams *unwrapParams) { CSSM_KEY *passKey = NULL; CFDataRef cfPhrase = NULL; CSSM_RETURN crtn; OSStatus ortn; CSSM_CRYPTO_DATA seed; CSSM_CC_HANDLE ccHand = 0; CSSM_ACCESS_CREDENTIALS creds; /* passphrase or passkey? */ ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, VP_Import, (CFTypeRef *)&cfPhrase, &passKey); if(ortn) { return ortn; } /* subsequent errors to errOut: */ memset(&seed, 0, sizeof(seed)); if(cfPhrase != NULL) { size_t len = CFDataGetLength(cfPhrase); coder.allocItem(seed.Param, len); memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len); CFRelease(cfPhrase); } /* hash algorithm --> PBE alg for CSP */ CSSM_ALGORITHMS pbeAlg; switch(pbeHashAlg) { case CSSM_ALGID_MD2: pbeAlg = CSSM_ALGID_PKCS5_PBKDF1_MD2; break; case CSSM_ALGID_MD5: pbeAlg = CSSM_ALGID_PKCS5_PBKDF1_MD5; break; case CSSM_ALGID_SHA1: pbeAlg = CSSM_ALGID_PKCS5_PBKDF1_SHA1; break; default: /* really shouldn't happen - pbeHashAlg was inferred by * pkcsOidToParams() */ SecPkcs8Dbg("PKCS8: PKCS5 v1/5 bogus hash alg"); crtn = CSSMERR_CSP_INTERNAL_ERROR; goto errOut; } /* Salt and iteration count from alg parameters */ impExpPKCS5_PBE_Parameters pbeParams; memset(&pbeParams, 0, sizeof(pbeParams)); if(coder.decodeItem(paramData, impExpPKCS5_PBE_ParametersTemplate, &pbeParams)) { SecPkcs8Dbg("PKCS8: PKCS5 v1.5 pbeParams decode error"); crtn = errSecUnknownFormat; goto errOut; } uint32 iterCount; if(!p12DataToInt(pbeParams.iterations, iterCount)) { SecPkcs8Dbg("PKCS8: bad PKCS5 v1.5 iteration count"); crtn = errSecUnknownFormat; goto errOut; } /* ask for hard coded 8 bytes of IV */ coder.allocItem(unwrapParams->iv, 8); memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand, pbeAlg, keyAlg, keySizeInBits, &creds, passKey, // BaseKey iterCount, &pbeParams.salt, &seed, &ccHand); if(crtn) { SecPkcs8Dbg("PKCS8: PKCS5 v2 CSSM_CSP_CreateDeriveKeyContext failure"); goto errOut; } memset(unwrapParams->unwrappingKey, 0, sizeof(CSSM_KEY)); CSSM_DATA dummyLabel; dummyLabel.Data = (uint8 *)"temp unwrap key"; dummyLabel.Length = strlen((char *)dummyLabel.Data); crtn = CSSM_DeriveKey(ccHand, &unwrapParams->iv, // IV returned in in/out Param CSSM_KEYUSE_ANY, /* not extractable even for the short time this key lives */ CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE, &dummyLabel, NULL, // cred and acl unwrapParams->unwrappingKey); if(crtn) { SecPkcs8Dbg("PKCS8: PKCS5 v1.5 CSSM_DeriveKey failure"); } errOut: if(ccHand != 0) { CSSM_DeleteContext(ccHand); } if(passKey != NULL) { CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE); free(passKey); } return crtn; } #pragma mark --- PKCS5 v2.0 Key Derivation --- /* * PKCS5 v2.0 has different means of encoding algorithm parameters, * depending on the encryption algorithm. */ /* * Obtain encryption parameters for PKCS5 v2.0, DES and DES3 variants. */ static OSStatus pkcs5_DES_params( const CSSM_DATA ¶mData, // encryptionScheme.parameters CSSM_OID *encrOid, impExpKeyUnwrapParams *unwrapParams, CSSM_ALGORITHMS *keyAlg, // RETURNED uint32 *keySizeInBits, // IN/OUT (returned if 0 on entry) SecNssCoder &coder) { /* Params is iv as OCTET STRING */ if(coder.decodeItem(paramData, kSecAsn1OctetStringTemplate, &unwrapParams->iv)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 DES init vector decode error"); return errSecUnknownFormat; } if(nssCompareCssmData(encrOid, &CSSMOID_PKCS5_DES_EDE3_CBC)) { *keyAlg = CSSM_ALGID_3DES_3KEY; unwrapParams->encrAlg = CSSM_ALGID_3DES_3KEY_EDE; if(*keySizeInBits == 0) { *keySizeInBits = 3 * 64; } } else { *keyAlg = CSSM_ALGID_DES; unwrapParams->encrAlg = CSSM_ALGID_DES; if(*keySizeInBits == 0) { *keySizeInBits = 64; } } unwrapParams->encrPad = CSSM_PADDING_PKCS7; unwrapParams->encrMode = CSSM_ALGMODE_CBCPadIV8; return errSecSuccess; } /* * Obtain encryption parameters for PKCS5 v2.0, RC2 variant. */ static OSStatus pkcs5_RC2_params( const CSSM_DATA ¶mData, // encryptionScheme.parameters impExpKeyUnwrapParams *unwrapParams, CSSM_ALGORITHMS *keyAlg, // RETURNED uint32 *keySizeInBits, // IN/OUT (returned if 0 on entry) SecNssCoder &coder) { /* Params is impExpPKCS5_RC2Params */ impExpPKCS5_RC2Params rc2Params; memset(&rc2Params, 0, sizeof(rc2Params)); if(coder.decodeItem(paramData, impExpPKCS5_RC2ParamsTemplate, &rc2Params)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 RC2 params decode error"); return errSecUnknownFormat; } *keyAlg = CSSM_ALGID_RC2; unwrapParams->encrAlg = CSSM_ALGID_RC2; unwrapParams->encrPad = CSSM_PADDING_PKCS7; unwrapParams->encrMode = CSSM_ALGMODE_CBCPadIV8; /* the version actually maps to effective key size like this */ /* I swear all of this is in the PKCS5 v2.0 spec */ unwrapParams->effectiveKeySizeInBits = 32; // default if(rc2Params.version.Data) { uint32 v; if(!p12DataToInt(rc2Params.version, v)) { SecPkcs8Dbg("PKCS8: bad PKCS5 rc2Params.version"); return errSecUnknownFormat; } switch(v) { case 160: unwrapParams->effectiveKeySizeInBits = 40; break; case 120: unwrapParams->effectiveKeySizeInBits = 64; break; case 58: unwrapParams->effectiveKeySizeInBits = 128; break; default: if(v >= 256) { unwrapParams->effectiveKeySizeInBits = v; } else { /* not in the spec, use as zero */ } break; } } unwrapParams->iv = rc2Params.iv; /* the PKCS5 spec does not give a default for the RC2 key size */ if(*keySizeInBits == 0) { SecPkcs8Dbg("PKCS8: NO RC2 DEFAULT KEYSIZE!"); return errSecUnknownFormat; } return errSecSuccess; } /* * Infer encryption parameters for PKCS5 v2.0, RC5 variant. * All info contained in encryptionScheme.parameters. */ static OSStatus pkcs5_RC5_params( const CSSM_DATA ¶mData, // encryptionScheme.parameters impExpKeyUnwrapParams *unwrapParams, CSSM_ALGORITHMS *keyAlg, // RETURNED uint32 *keySizeInBits, // IN/OUT (returned if 0 on entry) SecNssCoder &coder) { /* Params is a impExpPKCS5_RC5Params */ impExpPKCS5_RC5Params rc5Params; memset(&rc5Params, 0, sizeof(rc5Params)); if(coder.decodeItem(paramData, impExpPKCS5_RC5ParamsTemplate, &rc5Params)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 RC5 params decode error"); return errSecUnknownFormat; } *keyAlg = CSSM_ALGID_RC5; unwrapParams->encrAlg = CSSM_ALGID_RC5; unwrapParams->encrPad = CSSM_PADDING_PKCS7; unwrapParams->encrMode = CSSM_ALGMODE_CBCPadIV8; if(rc5Params.rounds.Data) { if(!p12DataToInt(rc5Params.rounds, unwrapParams->rounds)) { SecPkcs8Dbg("PKCS8: bad PKCS5 rc5Params.rounds"); return errSecUnknownFormat; } } if(rc5Params.blockSizeInBits.Data) { if(!p12DataToInt(rc5Params.blockSizeInBits, unwrapParams->blockSizeInBits)) { SecPkcs8Dbg("PKCS8: bad PKCS5 rc5Params.blockSizeInBits"); return errSecUnknownFormat; } } /* Spec says default iv is zeroes */ unwrapParams->iv = rc5Params.iv; if(unwrapParams->iv.Length == 0) { uint32 len = unwrapParams->blockSizeInBits / 8; coder.allocItem(unwrapParams->iv, len); memset(unwrapParams->iv.Data, 0, len); } /* * Spec does not give a default for key RC5 size, and openssl doesn't * support RC5 for PKCS8. */ if(*keySizeInBits == 0) { SecPkcs8Dbg("PKCS8: NO RC5 DEFAULT KEYSIZE!"); return errSecUnknownFormat; } return errSecSuccess; } /* * Common code to derive a wrap/unwrap key using PBKDF2 (i.e., using PKCS5 v2.0 * key derivation). Caller must CSSM_FreeKey when done. */ static CSSM_RETURN pbkdf2DeriveKey( CSSM_CSP_HANDLE cspHand, SecNssCoder &coder, CSSM_ALGORITHMS keyAlg, uint32 keySizeInBits, uint32 iterationCount, const CSSM_DATA &salt, const SecKeyImportExportParameters *keyParams, // required impExpVerifyPhrase verifyPhrase, // for secure passphrase CSSM_KEY_PTR symKey) // RETURNED { CSSM_KEY *passKey = NULL; CFDataRef cfPhrase = NULL; CSSM_PKCS5_PBKDF2_PARAMS pbeParams; CSSM_RETURN crtn; OSStatus ortn; CSSM_DATA dummyLabel; CSSM_DATA pbeData; uint32 keyAttr; CSSM_CC_HANDLE ccHand = 0; CSSM_ACCESS_CREDENTIALS creds; memset(&pbeParams, 0, sizeof(pbeParams)); /* passphrase or passkey? */ ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, verifyPhrase, (CFTypeRef *)&cfPhrase, &passKey); if(ortn) { return ortn; } /* subsequent errors to errOut: */ if(cfPhrase != NULL) { size_t len = CFDataGetLength(cfPhrase); coder.allocItem(pbeParams.Passphrase, len); memmove(pbeParams.Passphrase.Data, CFDataGetBytePtr(cfPhrase), len); CFRelease(cfPhrase); } memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS)); crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand, CSSM_ALGID_PKCS5_PBKDF2, keyAlg, keySizeInBits, &creds, passKey, // BaseKey iterationCount, &salt, NULL, // seed &ccHand); if(crtn) { SecPkcs8Dbg("PKCS8: PKCS5 v2 CSSM_CSP_CreateDeriveKeyContext failure"); goto errOut; } memset(symKey, 0, sizeof(CSSM_KEY)); /* not extractable even for the short time this key lives */ keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE; dummyLabel.Data = (uint8 *)"temp unwrap key"; dummyLabel.Length = strlen((char *)dummyLabel.Data); pbeParams.PseudoRandomFunction = CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1; pbeData.Data = (uint8 *)&pbeParams; pbeData.Length = sizeof(pbeParams); crtn = CSSM_DeriveKey(ccHand, &pbeData, CSSM_KEYUSE_ANY, keyAttr, &dummyLabel, NULL, // cred and acl symKey); if(crtn) { SecPkcs8Dbg("PKCS8: PKCS5 v2 CSSM_DeriveKey failure"); } errOut: if(ccHand != 0) { CSSM_DeleteContext(ccHand); } if(passKey != NULL) { CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE); free(passKey); } return crtn; } /* * Obtain PKCS5, v.2.0 key derivation and encryption parameters and * derive the key. This one obtains all of the crypt parameters * from the top-level AlgId.Params. What a mess. */ static CSSM_RETURN pkcs5_v2_genKey( CSSM_CSP_HANDLE cspHand, SecNssCoder &coder, const CSSM_DATA ¶mData, const SecKeyImportExportParameters *keyParams, impExpKeyUnwrapParams *unwrapParams) { SecPkcs8Dbg("PKCS8: generating PKCS5 v2.0 key"); CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE; uint32 prf = 0; // CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1... /* caller should check */ assert(keyParams != NULL); /* AlgId.Params --> impExpPKCS5_PBES2_Params */ if(paramData.Length == 0) { SecPkcs8Dbg("PKCS8: empty PKCS5 v2 pbes2Params"); return errSecUnknownFormat; } impExpPKCS5_PBES2_Params pbes2Params; memset(&pbes2Params, 0, sizeof(pbes2Params)); if(coder.decodeItem(paramData, impExpPKCS5_PBES2_ParamsTemplate, &pbes2Params)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 pbes2Params decode error"); return errSecUnknownFormat; } /* * As far as I know the keyDerivationFunc OID must be id-PBKDF2 */ if(!nssCompareCssmData(&pbes2Params.keyDerivationFunc.algorithm, &CSSMOID_PKCS5_PBKDF2)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 unexpected keyDerivationFunc alg"); return errSecUnknownFormat; } /* * The params of the keyDerivationFunc algId are an encoded * impExpPKCS5_PBKDF2_Params. */ impExpPKCS5_PBKDF2_Params pbkdf2Params; memset(&pbkdf2Params, 0, sizeof(pbkdf2Params)); if(coder.decodeItem(pbes2Params.keyDerivationFunc.parameters, impExpPKCS5_PBKDF2_ParamsTemplate, &pbkdf2Params)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 pbkdf2Params decode error"); return errSecUnknownFormat; } /* * Salt and iteration count from the impExpPKCS5_PBKDF2_Params (ignoring the * possible CHOICE for salt source). */ CSSM_DATA salt = pbkdf2Params.salt; uint32 iterCount; if(!p12DataToInt(pbkdf2Params.iterationCount, iterCount)) { SecPkcs8Dbg("PKCS8: bad PKCS5 v2 iteration count"); return errSecUnknownFormat; } /* * Key size optional, use defaults per alg (later) if it's not there */ uint32 keySizeInBits = 0; if(pbkdf2Params.keyLengthInBytes.Data) { uint32 keyLengthInBytes; if(!p12DataToInt(pbkdf2Params.keyLengthInBytes, keyLengthInBytes)) { SecPkcs8Dbg("PKCS8: bad PKCS5 v2 key size"); return errSecUnknownFormat; } keySizeInBits = keyLengthInBytes * 8; } /* else we'll infer key size from the encryption algorithm */ /* prf optional, but if it's there it better be CSSMOID_PKCS5_HMAC_SHA1 */ if(pbkdf2Params.prf.Data) { if(!nssCompareCssmData(&pbkdf2Params.prf, &CSSMOID_PKCS5_HMAC_SHA1)) { SecPkcs8Dbg("PKCS8: PKCS5 v2 unexpected prf OID"); return errSecUnknownFormat; } } prf = CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1; /* * Now process the encryptionScheme, which is even messier - the algParams * varies per encryption algorithm. */ CSSM_X509_ALGORITHM_IDENTIFIER &encrScheme = pbes2Params.encryptionScheme; CSSM_OID *encrOid = &encrScheme.algorithm; OSStatus ortn; CSSM_DATA &encrParam = encrScheme.parameters; if(nssCompareCssmData(encrOid, &CSSMOID_PKCS5_DES_EDE3_CBC) || nssCompareCssmData(encrOid, &CSSMOID_DES_CBC)) { ortn = pkcs5_DES_params(encrParam, encrOid, unwrapParams, &keyAlg, &keySizeInBits, coder); if(ortn) { return ortn; } } else if(nssCompareCssmData(encrOid, &CSSMOID_PKCS5_RC2_CBC)) { ortn = pkcs5_RC2_params(encrParam, unwrapParams, &keyAlg, &keySizeInBits, coder); if(ortn) { return ortn; } } else if(nssCompareCssmData(encrOid, &CSSMOID_PKCS5_RC5_CBC)) { ortn = pkcs5_RC5_params(encrParam, unwrapParams, &keyAlg, &keySizeInBits, coder); if(ortn) { return ortn; } } else { SecPkcs8Dbg("PKCS8: PKCS5 v2 unknown encrScheme.algorithm"); return errSecUnknownFormat; } /* We should be ready to go */ assert(keyAlg != CSSM_ALGID_NONE); assert(unwrapParams->encrAlg != CSSM_ALGID_NONE); /* use all the stuff we just figured out to derive a symmetric decryption key */ return pbkdf2DeriveKey(cspHand, coder, keyAlg, keySizeInBits, iterCount, salt, keyParams, VP_Import, unwrapParams->unwrappingKey); } #pragma mark --- PKCS12 Key Derivation --- /* * PKCS12 style key derivation. Caller has gleaned everything except * salt, iterationCount, and IV from the AlgId.algorithm OID. * * We get salt and iteration count from the incoming alg params. * IV is derived along with the unwrapping key from the passphrase. */ static CSSM_RETURN pkcs12_genKey( CSSM_CSP_HANDLE cspHand, SecNssCoder &coder, const SecKeyImportExportParameters *keyParams, const CSSM_DATA ¶mData, // from algID CSSM_ALGORITHMS keyAlg, // valid on entry CSSM_ALGORITHMS pbeHashAlg, // valid on entry uint32 keySizeInBits, // valid on entry uint32 blockSizeInBytes, // for IV impExpKeyUnwrapParams *unwrapParams) { SecPkcs8Dbg("PKCS8: generating PKCS12 key"); assert(keyAlg != CSSM_ALGID_NONE); assert(pbeHashAlg != CSSM_ALGID_NONE); assert(keySizeInBits != 0); /* get iteration count, salt from alg params */ NSS_P12_PBE_Params pbeParams; if(paramData.Length == 0) { SecPkcs8Dbg("PKCS8: empty P12 pbeParams"); return errSecUnknownFormat; } memset(&pbeParams, 0, sizeof(pbeParams)); if(coder.decodeItem(paramData, NSS_P12_PBE_ParamsTemplate, &pbeParams)) { SecPkcs8Dbg("PKCS8: P12 pbeParams decode error"); return errSecUnknownFormat; } uint32 iterCount = 0; if(!p12DataToInt(pbeParams.iterations, iterCount)) { SecPkcs8Dbg("PKCS8: bad P12 iteration count"); return errSecUnknownFormat; } /* passphrase or passkey? */ CSSM_KEY *passKey = NULL; CFStringRef phraseStr = NULL; CSSM_DATA phraseData = {0, NULL}; CSSM_DATA *phraseDataP = NULL; OSStatus ortn; CSSM_RETURN crtn; assert(keyParams != NULL); ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_String, VP_Import, (CFTypeRef *)&phraseStr, &passKey); if(ortn) { return ortn; } /* subsequent errors to errOut: */ if(phraseStr != NULL) { /* convert to CSSM_DATA for use with p12KeyGen() */ try { p12ImportPassPhrase(phraseStr, coder, phraseData); } catch(...) { SecPkcs8Dbg("PKCS8: p12ImportPassPhrase threw"); crtn = errSecAllocate; goto errOut; } CFRelease(phraseStr); phraseDataP = &phraseData; } /* use p12 module to cook up the key and IV */ if(blockSizeInBytes) { coder.allocItem(unwrapParams->iv, blockSizeInBytes); } crtn = p12KeyGen(cspHand, *unwrapParams->unwrappingKey, true, // isForEncr keyAlg, pbeHashAlg, keySizeInBits, iterCount, pbeParams.salt, phraseDataP, passKey, unwrapParams->iv); if(crtn) { SecPkcs8Dbg("PKCS8: p12KeyGen failed"); } errOut: if(passKey != NULL) { CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE); free(passKey); } return crtn; } #pragma mark --- Public PKCS8 import function --- /* * Called out from SecImportRep::importWrappedKey(). * If cspHand is provided instead of importKeychain, the CSP * handle MUST be for the CSPDL, not for the raw CSP. */ OSStatus impExpPkcs8Import( CFDataRef inData, SecKeychainRef importKeychain, // optional CSSM_CSP_HANDLE cspHand, // required SecItemImportExportFlags flags, const SecKeyImportExportParameters *keyParams, // REQUIRED for unwrap CFMutableArrayRef outArray) // optional, append here { CSSM_KEY wrappedKey; CSSM_KEYHEADER &hdr = wrappedKey.KeyHeader; CSSM_RETURN crtn = CSSM_OK; /* key derivation and encryption parameters gleaned from alg ID */ impExpKeyUnwrapParams unwrapParams; memset(&unwrapParams, 0, sizeof(unwrapParams)); CSSM_ALGORITHMS keyAlg = CSSM_ALGID_NONE; CSSM_ALGORITHMS pbeHashAlg = CSSM_ALGID_NONE; // SHA1 or MD5 uint32 keySizeInBits; uint32 blockSizeInBytes; PKCS_Which pkcs = PW_None; if( (keyParams == NULL) || ( (keyParams->passphrase == NULL) && !(keyParams->flags & kSecKeySecurePassphrase) ) ) { /* passphrase mandatory */ return errSecPassphraseRequired; } assert(cspHand != 0); /* * Top-level decode */ SecNssCoder coder; NSS_EncryptedPrivateKeyInfo encrPrivKeyInfo; memset(&encrPrivKeyInfo, 0, sizeof(encrPrivKeyInfo)); if(coder.decode(CFDataGetBytePtr(inData), CFDataGetLength(inData), kSecAsn1EncryptedPrivateKeyInfoTemplate, &encrPrivKeyInfo)) { SecImpExpDbg("impExpPkcs8Import: error decoding top-level encrPrivKeyInfo"); return errSecUnknownFormat; } /* * The algorithm OID of that top-level struct is the key piece of info * for now... */ bool found = false; found = pkcsOidToParams(&encrPrivKeyInfo.algorithm.algorithm, keyAlg, unwrapParams.encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, unwrapParams.encrPad, unwrapParams.encrMode, pkcs); if(!found) { SecImpExpDbg("impExpPkcs8Import: unknown OID in top-level encrPrivKeyInfo"); return errSecUnknownFormat; } /* * Each PBE method has its own way of filling in the remaining gaps * in impExpKeyUnwrapParams and generating a key. */ CSSM_KEY unwrappingKey; memset(&unwrappingKey, 0, sizeof(unwrappingKey)); unwrapParams.unwrappingKey = &unwrappingKey; CSSM_DATA ¶mData = encrPrivKeyInfo.algorithm.parameters; switch(pkcs) { case PW_PKCS5_v1_5: /* we have everything except iv, iterations, salt */ crtn = pkcs5_v15_genKey(cspHand, coder, keyParams, paramData, keyAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, &unwrapParams); break; case PW_PKCS5_v2: /* obtain everything, including iv, from alg params */ crtn = pkcs5_v2_genKey(cspHand, coder, paramData, keyParams, &unwrapParams); break; case PW_PKCS12: /* we have everything except iv, iterations, salt */ crtn = pkcs12_genKey(cspHand, coder, keyParams, paramData, keyAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes, &unwrapParams); break; case PW_None: /* satisfy compiler */ assert(0); return errSecUnknownFormat; } if(crtn) { SecPkcs8Dbg("PKCS8: key derivation failed"); return crtn; } /* we should be ready to rock'n'roll no matter how we got here */ assert(unwrapParams.encrAlg != CSSM_ALGID_NONE); assert(unwrappingKey.KeyData.Data != NULL); assert(unwrappingKey.KeyHeader.AlgorithmId != CSSM_ALGID_NONE); /* set up key to unwrap */ memset(&wrappedKey, 0, sizeof(CSSM_KEY)); hdr.HeaderVersion = CSSM_KEYHEADER_VERSION; /* CspId : don't care */ hdr.BlobType = CSSM_KEYBLOB_WRAPPED; hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8; /* AlgorithmId : inferred by CSP */ hdr.AlgorithmId = CSSM_ALGID_NONE; hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY; /* LogicalKeySizeInBits : calculated by CSP during unwrap */ hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE; hdr.KeyUsage = CSSM_KEYUSE_ANY; wrappedKey.KeyData = encrPrivKeyInfo.encryptedData; crtn = impExpImportKeyCommon( &wrappedKey, importKeychain, cspHand, flags, keyParams, &unwrapParams, NULL, // default label outArray); CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE); return crtn; } #pragma mark --- Public PKCS8 export function --- #define PKCS5_V2_SALT_LEN 8 #define PKCS5_V2_ITERATIONS 2048 #define PKCS5_V2_DES_IV_SIZE 8 /* * Unlike impExpPkcs8Import(), which can handle every PBE algorithm in the spec * and implemented by openssl, this one has a fixed PBE and encryption scheme. * We do not provide a means at the API for the client to specify these. * * We generate blobs with triple DES encryption, with PKCS5 v2.0 key * derivation. */ OSStatus impExpPkcs8Export( SecKeyRef secKey, SecItemImportExportFlags flags, const SecKeyImportExportParameters *keyParams, // optional CFMutableDataRef outData, // output appended here const char **pemHeader) { DevRandomGenerator rng; SecNssCoder coder; impExpPKCS5_PBES2_Params pbes2Params; CSSM_X509_ALGORITHM_IDENTIFIER &keyDeriveAlgId = pbes2Params.keyDerivationFunc; CSSM_ATTRIBUTE_TYPE formatAttrType = CSSM_ATTRIBUTE_NONE; CSSM_KEYBLOB_FORMAT blobForm = CSSM_KEYBLOB_RAW_FORMAT_NONE; const CSSM_KEY *cssmKey; if(keyParams == NULL) { return errSecParam; } assert(secKey != NULL); assert(outData != NULL); memset(&pbes2Params, 0, sizeof(pbes2Params)); /* * keyDeriveAlgId * parameters is an encoded impExpPKCS5_PBKDF2_Params * We generate random salt */ keyDeriveAlgId.algorithm = CSSMOID_PKCS5_PBKDF2; impExpPKCS5_PBKDF2_Params pbkdf2Params; memset(&pbkdf2Params, 0, sizeof(pbkdf2Params)); coder.allocItem(pbkdf2Params.salt, PKCS5_V2_SALT_LEN); rng.random(pbkdf2Params.salt.Data, PKCS5_V2_SALT_LEN); p12IntToData(PKCS5_V2_ITERATIONS, pbkdf2Params.iterationCount, coder); /* leave pbkdf2Params.keyLengthInBytes NULL for default */ /* openssl can't handle this, which is the default value: pbkdf2Params.prf = CSSMOID_PKCS5_HMAC_SHA1; */ coder.encodeItem(&pbkdf2Params, impExpPKCS5_PBKDF2_ParamsTemplate, keyDeriveAlgId.parameters); /* * encryptionScheme * parameters is an encoded OCTET STRING containing the (random) IV */ CSSM_X509_ALGORITHM_IDENTIFIER &encrScheme = pbes2Params.encryptionScheme; encrScheme.algorithm = CSSMOID_PKCS5_DES_EDE3_CBC; CSSM_DATA rawIv = {0, NULL}; coder.allocItem(rawIv, PKCS5_V2_DES_IV_SIZE); rng.random(rawIv.Data, PKCS5_V2_DES_IV_SIZE); coder.encodeItem(&rawIv, kSecAsn1OctetStringTemplate, encrScheme.parameters); /* * Top level NSS_EncryptedPrivateKeyInfo, whose parameters is the encoded * impExpPKCS5_PBES2_Params. */ NSS_EncryptedPrivateKeyInfo encrPrivKeyInfo; memset(&encrPrivKeyInfo, 0, sizeof(encrPrivKeyInfo)); CSSM_X509_ALGORITHM_IDENTIFIER &topAlgId = encrPrivKeyInfo.algorithm; topAlgId.algorithm = CSSMOID_PKCS5_PBES2; coder.encodeItem(&pbes2Params, impExpPKCS5_PBES2_ParamsTemplate, topAlgId.parameters); /* * Now all we have to do is generate the encrypted key data itself. * When doing a WrapKey op in PKCS8 form, the CSP gives us the * NSS_EncryptedPrivateKeyInfo.encryptedData values. */ /* we need a CSPDL handle - try to get it from the key */ CSSM_CSP_HANDLE cspdlHand = 0; OSStatus ortn; bool releaseCspHand = false; CSSM_DATA encodedKeyInfo = {0, NULL}; ortn = SecKeyGetCSPHandle(secKey, &cspdlHand); if(ortn) { cspdlHand = cuCspStartup(CSSM_FALSE); if(cspdlHand == 0) { return CSSMERR_CSSM_ADDIN_LOAD_FAILED; } releaseCspHand = true; } /* subsequent errors to errOut: */ /* get wrapping key from parameters we just set up */ CSSM_KEY wrappingKey; memset(&wrappingKey, 0, sizeof(CSSM_KEY)); CSSM_RETURN crtn = pbkdf2DeriveKey(cspdlHand, coder, CSSM_ALGID_3DES_3KEY, 3 * 64, PKCS5_V2_ITERATIONS, pbkdf2Params.salt, keyParams, VP_Export, &wrappingKey); if(crtn) { goto errOut; } /* * Special case for DSA, ECDSA: specify that the raw blob, pre-encrypt, is in * the PKCS8 PrivateKeyInfo format that openssl understands. The * default is BSAFE. */ crtn = SecKeyGetCSSMKey(secKey, &cssmKey); if(crtn) { SecImpExpDbg("impExpPkcs8Export SecKeyGetCSSMKey error"); goto errOut; } switch(cssmKey->KeyHeader.AlgorithmId) { case CSSM_ALGID_DSA: case CSSM_ALGID_ECDSA: formatAttrType = CSSM_ATTRIBUTE_PRIVATE_KEY_FORMAT; blobForm = CSSM_KEYBLOB_RAW_FORMAT_PKCS8; break; default: break; } /* GO */ CSSM_KEY wrappedKey; memset(&wrappedKey, 0, sizeof(CSSM_KEY)); crtn = impExpExportKeyCommon(cspdlHand, secKey, &wrappingKey, &wrappedKey, CSSM_ALGID_3DES_3KEY_EDE, CSSM_ALGMODE_CBCPadIV8, CSSM_PADDING_PKCS7, CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8, formatAttrType, blobForm, NULL, &rawIv); if(crtn) { goto errOut; } /* * OK... *that* wrapped key's data goes into the top-level * NSS_EncryptedPrivateKeyInfo, which we then encode; the caller * gets the result of that encoding. */ encrPrivKeyInfo.encryptedData = wrappedKey.KeyData; coder.encodeItem(&encrPrivKeyInfo, kSecAsn1EncryptedPrivateKeyInfoTemplate, encodedKeyInfo); CFDataAppendBytes(outData, encodedKeyInfo.Data, encodedKeyInfo.Length); CSSM_FreeKey(cspdlHand, NULL, &wrappedKey, CSSM_FALSE); *pemHeader = PEM_STRING_PKCS8; errOut: if(wrappingKey.KeyData.Data) { CSSM_FreeKey(cspdlHand, NULL, &wrappingKey, CSSM_FALSE); } if(releaseCspHand) { cuCspDetachUnload(cspdlHand, CSSM_FALSE); } return crtn; }