X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/SecImportExportPkcs8.cpp diff --git a/Security/libsecurity_keychain/lib/SecImportExportPkcs8.cpp b/Security/libsecurity_keychain/lib/SecImportExportPkcs8.cpp new file mode 100644 index 00000000..13547958 --- /dev/null +++ b/Security/libsecurity_keychain/lib/SecImportExportPkcs8.cpp @@ -0,0 +1,978 @@ +/* + * Copyright (c) 2000-2004,2011-2014 Apple 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; +}