pkcs12Encode.cpp   [plain text]


/*
 * 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;
}