+/*
+ * Copyright (c) 2003-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@
+ */
+
+/*
+ * pkcs12Encode.h - P12Coder encoding engine.
+ *
+ * Unlike the decoding side of P12Coder, which can parse PFXs with
+ * more or less arbitrary structures, this encoding engine has
+ * a specific layout for what bags go where in the jungle of
+ * SafeContents and ContentInfos. It would be impractical to allow
+ * (or to expect) the app to specify this structure.
+ *
+ * The knowledge of how a PFX is built out of various components
+ * is encapsulated in the authSafeBuild() member function. The rest
+ * of the functions in this file are pretty much "PFX-structure-
+ * agnostic", so if one wanted to change the overall PFX structure,
+ * one would only have to focus on the authSafeBuild() function.
+ */
+
+#include "pkcs12Coder.h"
+#include "pkcs12Debug.h"
+#include "pkcs12Crypto.h"
+#include "pkcs12Templates.h"
+#include "pkcs12Utils.h"
+#include <Security/cssmerr.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <Security/oidsattr.h>
+
+void P12Coder::encode(
+ CFDataRef *cpfx) // RETURNED
+{
+ p12EncodeLog("encode top");
+ SecNssCoder localCdr;
+ NSS_P12_DecodedPFX pfx;
+
+ memset(&pfx, 0, sizeof(pfx));
+ p12IntToData(3, pfx.version, localCdr);
+ authSafeBuild(pfx.authSafe, localCdr);
+ macSignPfx(pfx, localCdr);
+ CSSM_DATA derPfx = {0, NULL};
+ if(localCdr.encodeItem(&pfx, NSS_P12_DecodedPFXTemplate, derPfx)) {
+ p12ErrorLog("Error encoding top-level pfx\n");
+ P12_THROW_ENCODE;
+ }
+ CFDataRef cp = CFDataCreate(NULL, derPfx.Data, derPfx.Length);
+ *cpfx = cp;
+}
+
+void P12Coder::macSignPfx(
+ NSS_P12_DecodedPFX &pfx,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("macSignPfx");
+ NSS_P12_MacData *macData = localCdr.mallocn<NSS_P12_MacData>();
+ pfx.macData = macData;
+ p12GenSalt(macData->macSalt, localCdr);
+ p12IntToData(mMacIterCount, macData->iterations, localCdr);
+ NSS_P7_DigestInfo &digInfo = macData->mac;
+
+ /* this is not negotiable; it's the only one P12 allows */
+ localCdr.allocCopyItem(CSSMOID_SHA1, digInfo.digestAlgorithm.algorithm);
+ /* null algorithm parameters */
+ p12NullAlgParams(digInfo.digestAlgorithm);
+
+ const CSSM_DATA *macPhrase = getMacPassPhrase();
+ const CSSM_KEY *macPassKey = getMacPassKey();
+ if((macPhrase == NULL) && (macPassKey == NULL)) {
+ p12ErrorLog("no passphrase set\n");
+ CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_PASSPHRASE);
+ }
+
+ CSSM_RETURN crtn = p12GenMac(mCspHand,
+ *pfx.authSafe.content.data,
+ CSSM_ALGID_SHA1, mMacIterCount, macData->macSalt,
+ macPhrase, macPassKey, localCdr, digInfo.digest);
+ if(crtn) {
+ p12ErrorLog("Error generating PFX MAC\n");
+ CssmError::throwMe(crtn);
+ }
+}
+
+/*
+ * This is the heart of the encoding engine. All knowledge of
+ * "what bags go where" is here. The PFX structure implemented here
+ * is derived from empirical observation of PFXs obtained from
+ * Mozilla 1.2b and from the DoD test vectors for "Conformance
+ * Testing of Relying Party Client Certificate Path Processing
+ * Logic", written by Cygnacom, Septemtber 28, 2001.
+ *
+ * The PFX structure is as follows:
+ *
+ * -- One AuthenticatedSafe element (a PKCS7 ContentInfo) containing
+ * all certificates and CRLs.
+ *
+ * ContentInfo.type = CT_EncryptedData
+ * Encryption algorithm is our "weak" encryption Alg, default
+ * of CSSMOID_PKCS12_pbeWithSHAAnd40BitRC4.
+ *
+ * -- One AuthenticatedSafe element containing all private keys in
+ * the form of ShroudedKeyBags.
+ *
+ * ContentInfo.type = CT_Data
+ * Encryption algorithm for shrouded key bags is our "strong"
+ * encryption, default CSSMOID_PKCS12_pbeWithSHAAnd3Key3DESCBC
+ *
+ * -- Everything else goes in another AuthenticatedSafe element.
+ *
+ * ContentInfo.type = CT_EncryptedData
+ * Encryption algorithm is our "strong" encryption Alg
+ */
+void P12Coder::authSafeBuild(
+ NSS_P7_DecodedContentInfo &authSafe,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("authSafeBuild top");
+
+ /* how many contentInfos are we going to build? */
+ unsigned numContents = 0;
+ if(mCerts.size() || mCrls.size()) {
+ numContents++;
+ }
+ if(mKeys.size()) {
+ numContents++;
+ }
+ if(mOpaques.size()) {
+ numContents++;
+ }
+
+ if(numContents == 0) {
+ p12ErrorLog("authSafeBuild: no contents\n");
+ MacOSError::throwMe(paramErr);
+ }
+
+ NSS_P7_DecodedContentInfo **contents =
+ (NSS_P7_DecodedContentInfo **)p12NssNullArray(numContents,
+ localCdr);
+ unsigned contentDex = 0;
+
+ NSS_P12_SafeBag **safeBags;
+
+ /* certs & crls */
+ unsigned numBags = mCerts.size() + mCrls.size();
+ p12EncodeLog("authSafeBuild : %u certs + CRLS", numBags);
+ if(numBags) {
+ safeBags = (NSS_P12_SafeBag **)p12NssNullArray(numBags, localCdr);
+ unsigned bagDex = 0;
+ for(unsigned dex=0; dex<mCerts.size(); dex++) {
+ safeBags[bagDex++] = certBagBuild(mCerts[dex], localCdr);
+ }
+ for(unsigned dex=0; dex<mCrls.size(); dex++) {
+ safeBags[bagDex++] = crlBagBuild(mCrls[dex], localCdr);
+ }
+ contents[contentDex++] = safeContentsBuild(safeBags,
+ CT_EncryptedData, &mWeakEncrAlg, mWeakEncrIterCount, localCdr);
+ }
+
+ /* shrouded keys - encrypted at bag level */
+ numBags = mKeys.size();
+ if(numBags) {
+ p12EncodeLog("authSafeBuild : %u keys", numBags);
+ safeBags = (NSS_P12_SafeBag **)p12NssNullArray(numBags, localCdr);
+ unsigned bagDex = 0;
+ for(unsigned dex=0; dex<numBags; dex++) {
+ safeBags[bagDex++] = keyBagBuild(mKeys[dex], localCdr);
+ }
+ contents[contentDex++] = safeContentsBuild(safeBags,
+ CT_Data, NULL, 0, localCdr);
+ }
+
+ /* opaque */
+ numBags = mOpaques.size();
+ if(numBags) {
+ p12EncodeLog("authSafeBuild : %u opaques", numBags);
+ safeBags = (NSS_P12_SafeBag **)p12NssNullArray(numBags, localCdr);
+ unsigned bagDex = 0;
+ for(unsigned dex=0; dex<numBags; dex++) {
+ safeBags[bagDex++] = opaqueBagBuild(mOpaques[dex], localCdr);
+ }
+ contents[contentDex++] = safeContentsBuild(safeBags,
+ CT_EncryptedData, &mStrongEncrAlg, mStrongEncrIterCount, localCdr);
+ }
+
+ /*
+ * Encode the whole elements array into authSafe.content.data
+ */
+ NSS_P12_AuthenticatedSafe safe;
+ safe.info = contents;
+ CSSM_DATA *adata = localCdr.mallocn<CSSM_DATA>();
+ authSafe.content.data = adata;
+ adata->Data = NULL;
+ adata->Length = 0;
+ if(localCdr.encodeItem(&safe, NSS_P12_AuthenticatedSafeTemplate,
+ *adata)) {
+ p12ErrorLog("authSafeBuild: error encoding auth safe\n");
+ P12_THROW_ENCODE;
+ }
+ authSafe.type = CT_Data;
+ authSafe.contentType = CSSMOID_PKCS7_Data;
+}
+
+/*
+ * Build a AuthSafe element of specified type out of the
+ * specified array of bags.
+ */
+NSS_P7_DecodedContentInfo *P12Coder::safeContentsBuild(
+ NSS_P12_SafeBag **bags,
+ NSS_P7_CI_Type type, // CT_Data, CT_EncryptedData
+ CSSM_OID *encrOid, // only for CT_EncryptedData
+ unsigned iterCount, // ditto
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("safeContentsBuild type %u", (unsigned)type);
+
+ /*
+ * First, encode the bag array as a SafeContents
+ */
+ CSSM_DATA encSafeContents = {0, NULL};
+ NSS_P12_SafeContents safeContents = {bags};
+ if(localCdr.encodeItem(&safeContents,
+ NSS_P12_SafeContentsTemplate, encSafeContents)) {
+ p12ErrorLog("error encoding SafeContents\n");
+ P12_THROW_ENCODE;
+ }
+
+ NSS_P7_DecodedContentInfo *dci =
+ localCdr.mallocn<NSS_P7_DecodedContentInfo>();
+ dci->type = type;
+ if(type == CT_Data) {
+ /* plaintext gets encoded as an octet string */
+ localCdr.allocCopyItem(CSSMOID_PKCS7_Data, dci->contentType);
+ dci->content.data = localCdr.mallocn<CSSM_DATA>();
+ localCdr.allocCopyItem(encSafeContents, *dci->content.data);
+ }
+ else if(type == CT_EncryptedData) {
+ /* encrypt the encoded SafeContents */
+ localCdr.allocCopyItem(CSSMOID_PKCS7_EncryptedData,
+ dci->contentType);
+ dci->content.encryptData = localCdr.mallocn<NSS_P7_EncryptedData>();
+ NSS_P7_EncryptedData *ed = dci->content.encryptData;
+ assert(encrOid != NULL);
+ encryptData(encSafeContents, *encrOid, iterCount, *ed, localCdr);
+ }
+ else {
+ p12ErrorLog("bad type in safeContentsBuild\n");
+ CssmError::throwMe(CSSMERR_CL_INTERNAL_ERROR);
+ }
+ return dci;
+}
+
+/*
+ * Encrypt the specified plaintext with specified algorithm.
+ * Drop result and other interesting info into an NSS_P7_EncryptedData.
+ */
+void P12Coder::encryptData(
+ const CSSM_DATA &ptext,
+ CSSM_OID &encrOid,
+ unsigned iterCount,
+ NSS_P7_EncryptedData &ed,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("encryptData");
+
+ /* do the raw encrypt first to make sure we can do it... */
+ CSSM_ALGORITHMS keyAlg; // e.g., CSSM_ALGID_DES
+ CSSM_ALGORITHMS encrAlg; // e.g., CSSM_ALGID_3DES_3KEY_EDE
+ CSSM_ALGORITHMS pbeHashAlg; // SHA1 or MD5
+ uint32 keySizeInBits;
+ uint32 blockSizeInBytes; // for IV, optional
+ CSSM_PADDING padding; // CSSM_PADDING_PKCS7, etc.
+ CSSM_ENCRYPT_MODE mode; // CSSM_ALGMODE_CBCPadIV8, etc.
+ PKCS_Which pkcs;
+
+ bool found = pkcsOidToParams(&encrOid,
+ keyAlg, encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes,
+ padding, mode, pkcs);
+ if(!found || (pkcs != PW_PKCS12)) {
+ p12ErrorLog("encryptData encrAlg not understood\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
+ }
+
+ /* Salt: we generate random bytes */
+ CSSM_DATA salt;
+ p12GenSalt(salt, localCdr);
+
+ const CSSM_DATA *pwd = getEncrPassPhrase();
+ const CSSM_KEY *passKey = getEncrPassKey();
+ if((pwd == NULL) && (passKey == NULL)) {
+ p12ErrorLog("no passphrase set\n");
+ CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_PASSPHRASE);
+ }
+ CSSM_DATA ctext = {0, NULL};
+
+ CSSM_RETURN crtn = p12Encrypt(mCspHand, ptext,
+ keyAlg, encrAlg, pbeHashAlg,
+ keySizeInBits, blockSizeInBytes,
+ padding, mode,
+ iterCount, salt,
+ pwd, passKey, localCdr,
+ ctext);
+ if(crtn) {
+ CssmError::throwMe(crtn);
+ }
+
+ /* Now fill in the NSS_P7_EncryptedData */
+ p12IntToData(0, ed.version, localCdr);
+ NSS_P7_EncrContentInfo &eci = ed.contentInfo;
+ localCdr.allocCopyItem(CSSMOID_PKCS7_Data, eci.contentType);
+ algIdBuild(eci.encrAlg, encrOid, salt, iterCount, localCdr);
+ eci.encrContent = ctext;
+}
+
+/*
+ * Fill in an CSSM_X509_ALGORITHM_IDENTIFIER with parameters in
+ * the form of an encoded NSS_P12_PBE_Params
+ */
+void P12Coder::algIdBuild(
+ CSSM_X509_ALGORITHM_IDENTIFIER &algId,
+ const CSSM_OID &algOid,
+ const CSSM_DATA &salt,
+ unsigned iterCount,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("algIdBuild");
+ localCdr.allocCopyItem(algOid, algId.algorithm);
+ NSS_P12_PBE_Params pbeParams;
+ pbeParams.salt = salt;
+ p12IntToData(iterCount, pbeParams.iterations, localCdr);
+ if(localCdr.encodeItem(&pbeParams, NSS_P12_PBE_ParamsTemplate,
+ algId.parameters)) {
+ p12ErrorLog("error encoding NSS_P12_PBE_Params\n");
+ P12_THROW_ENCODE;
+ }
+}
+
+#pragma mark --- Individual Bag Builders ---
+
+NSS_P12_SafeBag *P12Coder::certBagBuild(
+ P12CertBag *cert,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("certBagBuild");
+
+ NSS_P12_SafeBag *safeBag = localCdr.mallocn<NSS_P12_SafeBag>();
+ safeBag->bagId = CSSMOID_PKCS12_certBag;
+ safeBag->type = BT_CertBag;
+
+ NSS_P12_CertBag *certBag = localCdr.mallocn<NSS_P12_CertBag>();
+ safeBag->bagValue.certBag = certBag;
+ const CSSM_OID *certTypeOid = NULL;
+ switch(cert->certType()) {
+ case CT_X509:
+ certTypeOid = &CSSMOID_PKCS9_X509Certificate;
+ break;
+ case CT_SDSI:
+ certTypeOid = &CSSMOID_PKCS9_SdsiCertificate;
+ break;
+ default:
+ p12ErrorLog("unknown certType on encode\n");
+ CssmError::throwMe(CSSMERR_CSP_INTERNAL_ERROR);
+
+ }
+
+ /* copies not needed, same scope as P12CertBag */
+ certBag->bagType = *certTypeOid;
+ certBag->type = cert->certType();
+ certBag->certValue = cert->certData();
+ safeBag->bagAttrs = cert->getAllAttrs();
+ return safeBag;
+}
+
+NSS_P12_SafeBag *P12Coder::crlBagBuild(
+ P12CrlBag *crl,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("crlBagBuild");
+
+ NSS_P12_SafeBag *safeBag = localCdr.mallocn<NSS_P12_SafeBag>();
+ safeBag->bagId = CSSMOID_PKCS12_crlBag;
+ safeBag->type = BT_CrlBag;
+
+ NSS_P12_CrlBag *crlBag = localCdr.mallocn<NSS_P12_CrlBag>();
+ safeBag->bagValue.crlBag = crlBag;
+ const CSSM_OID *crlTypeOid = NULL;
+ switch(crl->crlType()) {
+ case CRT_X509:
+ crlTypeOid = &CSSMOID_PKCS9_X509Crl;
+ break;
+ default:
+ p12ErrorLog("unknown crlType on encode\n");
+ CssmError::throwMe(CSSMERR_CSP_INTERNAL_ERROR);
+
+ }
+
+ /* copies not needed, same scope as P12CrlBag */
+ crlBag->bagType = *crlTypeOid;
+ crlBag->type = crl->crlType();
+ crlBag->crlValue = crl->crlData();
+ safeBag->bagAttrs = crl->getAllAttrs();
+ return safeBag;
+}
+
+NSS_P12_SafeBag *P12Coder::keyBagBuild(
+ P12KeyBag *key,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("keyBagBuild");
+
+ NSS_P12_SafeBag *safeBag = localCdr.mallocn<NSS_P12_SafeBag>();
+ safeBag->bagId = CSSMOID_PKCS12_shroudedKeyBag;
+ safeBag->type = BT_ShroudedKeyBag;
+
+ NSS_EncryptedPrivateKeyInfo *keyInfo = localCdr.
+ mallocn<NSS_EncryptedPrivateKeyInfo>();
+ safeBag->bagValue.shroudedKeyBag = keyInfo;
+ safeBag->bagAttrs = key->getAllAttrs();
+
+ /* Prepare for key wrap */
+ CSSM_DATA salt;
+ p12GenSalt(salt, localCdr);
+ algIdBuild(keyInfo->algorithm, mStrongEncrAlg, salt,
+ mStrongEncrIterCount, localCdr);
+
+ CSSM_ALGORITHMS keyAlg; // e.g., CSSM_ALGID_DES
+ CSSM_ALGORITHMS encrAlg; // e.g., CSSM_ALGID_3DES_3KEY_EDE
+ CSSM_ALGORITHMS pbeHashAlg; // SHA1 or MD5
+ uint32 keySizeInBits;
+ uint32 blockSizeInBytes; // for IV, optional
+ CSSM_PADDING padding; // CSSM_PADDING_PKCS7, etc.
+ CSSM_ENCRYPT_MODE mode; // CSSM_ALGMODE_CBCPadIV8, etc.
+ PKCS_Which pkcs;
+
+ bool found = pkcsOidToParams(&mStrongEncrAlg,
+ keyAlg, encrAlg, pbeHashAlg, keySizeInBits, blockSizeInBytes,
+ padding, mode, pkcs);
+ if(!found || (pkcs != PW_PKCS12)) {
+ /* app config error - they gave us bogus algorithm */
+ p12ErrorLog("keyBagBuild encrAlg not understood\n");
+ CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
+ }
+ const CSSM_DATA *encrPhrase = getEncrPassPhrase();
+ const CSSM_KEY *passKey = getEncrPassKey();
+ if((encrPhrase == NULL) && (passKey == NULL)) {
+ p12ErrorLog("no passphrase set\n");
+ CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_PASSPHRASE);
+ }
+ CSSM_DATA shroudedBits = {0, NULL};
+
+ CSSM_RETURN crtn = p12WrapKey(mCspHand,
+ key->key(), key->privKeyCreds(),
+ keyAlg, encrAlg, pbeHashAlg,
+ keySizeInBits, blockSizeInBytes,
+ padding, mode,
+ mStrongEncrIterCount, salt,
+ encrPhrase,
+ passKey,
+ localCdr,
+ shroudedBits);
+ if(crtn) {
+ p12ErrorLog("Error wrapping private key\n");
+ CssmError::throwMe(crtn);
+ }
+
+ keyInfo->encryptedData = shroudedBits;
+ return safeBag;
+}
+
+NSS_P12_SafeBag *P12Coder::opaqueBagBuild(
+ P12OpaqueBag *opaque,
+ SecNssCoder &localCdr)
+{
+ p12EncodeLog("opaqueBagBuild");
+ NSS_P12_SafeBag *safeBag = localCdr.mallocn<NSS_P12_SafeBag>();
+ safeBag->bagId = opaque->oid();
+ safeBag->bagValue.secretBag = &opaque->blob();
+ safeBag->bagAttrs = opaque->getAllAttrs();
+ return safeBag;
+}