#include "p12Crypto.h"
#include "pkcs12Utils.h"
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <Security/cssmapple.h>

static void appFreeCssmMemory(
	CSSM_HANDLE	hand,
	void 			*p)
{
	CSSM_API_MEMORY_FUNCS memFuncs;
	CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
	if(crtn) {
		cssmPerror("CSSM_GetAPIMemoryFunctions", crtn);

		return;
	}
	memFuncs.free_func(p, memFuncs.AllocRef);
}

#define KEY_LABEL	"p12 key"

CSSM_RETURN p12KeyGen_app(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_KEY			&key,
	bool				isForEncr,	
	CSSM_ALGORITHMS		keyAlg,
	CSSM_ALGORITHMS		pbeHashAlg,	
	uint32				keySizeInBits,
	uint32				iterCount,
	const CSSM_DATA		&salt,
	const CSSM_DATA		&pwd,		
	CSSM_DATA			&iv)		
{
	CSSM_RETURN					crtn;
	CSSM_CC_HANDLE 				ccHand;
	CSSM_DATA					dummyLabel;
	CSSM_ACCESS_CREDENTIALS		creds;

	memset(&key, 0, sizeof(CSSM_KEY));
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));

	CSSM_ALGORITHMS deriveAlg = CSSM_ALGID_NONE;
	if(pbeHashAlg != CSSM_ALGID_SHA1) {
		return CSSMERR_CSP_INVALID_ALGORITHM;
	}
	if(isForEncr) {

		deriveAlg = CSSM_ALGID_PKCS12_PBE_ENCR;
	}
	else {
		deriveAlg = CSSM_ALGID_PKCS12_PBE_MAC;
	}
	CSSM_CRYPTO_DATA seed;
	seed.Param = pwd;
	seed.Callback = NULL;
	seed.CallerCtx = NULL;

	crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
		deriveAlg,
		keyAlg,
		keySizeInBits,
		&creds,
		NULL,			
		iterCount,
		&salt,
		&seed,			
		&ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateDeriveKeyContext", crtn);
		return crtn;
	}

	dummyLabel.Length = strlen(KEY_LABEL);
	dummyLabel.Data = (uint8 *)KEY_LABEL;

	crtn = CSSM_DeriveKey(ccHand,
		&iv,
		CSSM_KEYUSE_ANY,

		CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE,
		&dummyLabel,
		NULL,			
		&key);
	CSSM_DeleteContext(ccHand);
	if(crtn) {
		cuPrintError("CSSM_DeriveKey", crtn);
	}
	return crtn;
}

CSSM_RETURN p12Decrypt_app(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		&cipherText,
	CSSM_ALGORITHMS		keyAlg,				
	CSSM_ALGORITHMS		encrAlg,
	CSSM_ALGORITHMS		pbeHashAlg,			
	uint32				keySizeInBits,
	uint32				blockSizeInBytes,	
	CSSM_PADDING		padding,			
	CSSM_ENCRYPT_MODE	mode,				
	uint32				iterCount,
	const CSSM_DATA		&salt,
	const CSSM_DATA		&pwd,		
	SecNssCoder			&coder,		
	CSSM_DATA			&plainText)
{
	CSSM_RETURN crtn;
	CSSM_KEY ckey;
	CSSM_CC_HANDLE ccHand = 0;

	CSSM_DATA iv = {0, NULL};
	CSSM_DATA_PTR ivPtr = NULL;
	if(blockSizeInBytes) {
		coder.allocItem(iv, blockSizeInBytes);
		ivPtr = &iv;
	}

	crtn = p12KeyGen_app(cspHand, ckey, true, keyAlg, pbeHashAlg,
		keySizeInBits, iterCount, salt, pwd, iv);
	if(crtn) {
		return crtn;
	}	

	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
		encrAlg,
		mode,
		NULL,			
		&ckey,
		ivPtr,			
		padding,	
		NULL,			
		&ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateSymmetricContext", crtn);
		return crtn;
	}

	CSSM_DATA ourPtext = {0, NULL};
	CSSM_DATA remData = {0, NULL};
	uint32 bytesDecrypted;
	crtn = CSSM_DecryptData(ccHand,
		&cipherText,
		1,
		&ourPtext,
		1,
		&bytesDecrypted,
		&remData);
	if(crtn) {
		cuPrintError("CSSM_EncryptData", crtn);
	}
	else {
		coder.allocCopyItem(ourPtext, plainText);
		plainText.Length = bytesDecrypted;

		appFreeCssmMemory(cspHand, ourPtext.Data);
	}

	if(remData.Data) {
		appFreeCssmMemory(cspHand, remData.Data);
	}
	CSSM_DeleteContext(ccHand);
	CSSM_FreeKey(cspHand, NULL, &ckey, CSSM_FALSE);
	return crtn;
}

CSSM_RETURN p12GenMac_app(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		&ptext,	
	CSSM_ALGORITHMS		alg,	
	unsigned			iterCount,
	const CSSM_DATA		&salt,
	const CSSM_DATA		&pwd,		
	SecNssCoder			&coder,		
	CSSM_DATA			&macData)	
{
	CSSM_RETURN crtn;

	unsigned keySizeInBits;
	CSSM_ALGORITHMS hmacAlg;
	switch(alg) {
		case CSSM_ALGID_SHA1:
			keySizeInBits = 160;
			hmacAlg = CSSM_ALGID_SHA1HMAC;
			break;
		case CSSM_ALGID_MD5:

			keySizeInBits = 128;
			hmacAlg = CSSM_ALGID_MD5HMAC;
			break;
		default:
			return CSSMERR_CSP_INVALID_ALGORITHM;
	}
	CSSM_KEY macKey;
	CSSM_DATA iv = {0, NULL};
	crtn = p12KeyGen_app(cspHand, macKey, false, hmacAlg, alg,
		keySizeInBits, iterCount, salt, pwd, iv);
	if(crtn) {
		return crtn;
	}	

	coder.allocItem(macData, keySizeInBits / 8);
	CSSM_CC_HANDLE ccHand = 0;
	crtn = CSSM_CSP_CreateMacContext(cspHand, hmacAlg, &macKey, &ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateMacContext", crtn);
		return crtn;
	}

	crtn = CSSM_GenerateMac (ccHand, &ptext, 1, &macData);
	if(crtn) {
		cuPrintError("CSSM_GenerateMac", crtn);
	}
	CSSM_DeleteContext(ccHand);
	CSSM_FreeKey(cspHand, NULL, &macKey, CSSM_FALSE);
	return crtn;
}

CSSM_RETURN p12VerifyMac_app(
	const NSS_P12_DecodedPFX 	&pfx,
	CSSM_CSP_HANDLE				cspHand,
	const CSSM_DATA				&pwd,	
	SecNssCoder					&coder)	
{
	if(pfx.macData == NULL) {
		return CSSMERR_CSP_INVALID_SIGNATURE;
	}
	NSS_P12_MacData &macData = *pfx.macData;
	NSS_P7_DigestInfo &digestInfo  = macData.mac;
	CSSM_OID &algOid = digestInfo.digestAlgorithm.algorithm;
	CSSM_ALGORITHMS macAlg;
	if(!cssmOidToAlg(&algOid, &macAlg)) {
		return CSSMERR_CSP_INVALID_ALGORITHM;
	}
	uint32 iterCount = 0;
	CSSM_DATA &citer = macData.iterations;
	if(!p12DataToInt(citer, iterCount)) {
		return CSSMERR_CSP_INVALID_ATTR_ROUNDS;
	}
	if(iterCount == 0) {

		iterCount = 1;
	}

	CSSM_DATA genMac;
	CSSM_RETURN crtn = p12GenMac_app(cspHand, *pfx.authSafe.content.data, 
		macAlg, iterCount, macData.macSalt, pwd, coder, genMac);
	if(crtn) {
		return crtn;
	}
	if(nssCompareCssmData(&genMac, &digestInfo.digest)) {
		return CSSM_OK;
	}
	else {
		return CSSMERR_CSP_VERIFY_FAILED;
	}
}
/*
 * Copyright (c) 2003,2005 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please 
 * obtain a copy of the License at http://www.apple.com/publicsource and 
 * read it before using this file.
 * 
 * This 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.
 */

/*
 * p12Crypto.cpp - PKCS12 Crypto routines. App space reference version.
 *
 * Created 2/28/03 by Doug Mitchell.
 */

#include "p12Crypto.h"
#include "pkcs12Utils.h"
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <Security/cssmapple.h>

/*
 * Free memory via specified plugin's app-level allocator
 */
static void appFreeCssmMemory(
	CSSM_HANDLE	hand,
	void 			*p)
{
	CSSM_API_MEMORY_FUNCS memFuncs;
	CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
	if(crtn) {
		cssmPerror("CSSM_GetAPIMemoryFunctions", crtn);
		/* oh well, leak and continue */
		return;
	}
	memFuncs.free_func(p, memFuncs.AllocRef);
}

#define KEY_LABEL	"p12 key"

/*
 * Given appropriate P12-style parameters, cook up a CSSM_KEY.
 */
CSSM_RETURN p12KeyGen_app(
	CSSM_CSP_HANDLE		cspHand,
	CSSM_KEY			&key,
	bool				isForEncr,	// true: en/decrypt   false: MAC
	CSSM_ALGORITHMS		keyAlg,
	CSSM_ALGORITHMS		pbeHashAlg,	// SHA1, MD5 only
	uint32				keySizeInBits,
	uint32				iterCount,
	const CSSM_DATA		&salt,
	const CSSM_DATA		&pwd,		// unicode, double null terminated
	CSSM_DATA			&iv)		// referent is optional
{
	CSSM_RETURN					crtn;
	CSSM_CC_HANDLE 				ccHand;
	CSSM_DATA					dummyLabel;
	CSSM_ACCESS_CREDENTIALS		creds;
	
	memset(&key, 0, sizeof(CSSM_KEY));
	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));

	/* infer key derivation algorithm */
	CSSM_ALGORITHMS deriveAlg = CSSM_ALGID_NONE;
	if(pbeHashAlg != CSSM_ALGID_SHA1) {
		return CSSMERR_CSP_INVALID_ALGORITHM;
	}
	if(isForEncr) {
		/*
		 * FIXME - if this key is going to be used to wrap/unwrap a 
		 * shrouded key bag, its usage will change accordingly...
		 */
		deriveAlg = CSSM_ALGID_PKCS12_PBE_ENCR;
	}
	else {
		deriveAlg = CSSM_ALGID_PKCS12_PBE_MAC;
	}
	CSSM_CRYPTO_DATA seed;
	seed.Param = pwd;
	seed.Callback = NULL;
	seed.CallerCtx = NULL;
	
	crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
		deriveAlg,
		keyAlg,
		keySizeInBits,
		&creds,
		NULL,			// BaseKey
		iterCount,
		&salt,
		&seed,			// seed
		&ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateDeriveKeyContext", crtn);
		return crtn;
	}
	
	dummyLabel.Length = strlen(KEY_LABEL);
	dummyLabel.Data = (uint8 *)KEY_LABEL;
	
	/* KEYUSE_ANY - this is just an ephemeral session key */
	crtn = CSSM_DeriveKey(ccHand,
		&iv,
		CSSM_KEYUSE_ANY,
		//CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE,
		CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE,
		&dummyLabel,
		NULL,			// cred and acl
		&key);
	CSSM_DeleteContext(ccHand);
	if(crtn) {
		cuPrintError("CSSM_DeriveKey", crtn);
	}
	return crtn;
}

/*
 * Decrypt (typically, an encrypted P7 ContentInfo contents or
 * a P12 ShroudedKeyBag).
 */
CSSM_RETURN p12Decrypt_app(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		&cipherText,
	CSSM_ALGORITHMS		keyAlg,				
	CSSM_ALGORITHMS		encrAlg,
	CSSM_ALGORITHMS		pbeHashAlg,			// SHA1, MD5 only
	uint32				keySizeInBits,
	uint32				blockSizeInBytes,	// for IV
	CSSM_PADDING		padding,			// CSSM_PADDING_PKCS7, etc.
	CSSM_ENCRYPT_MODE	mode,				// CSSM_ALGMODE_CBCPadIV8, etc.
	uint32				iterCount,
	const CSSM_DATA		&salt,
	const CSSM_DATA		&pwd,		// unicode, double null terminated
	SecNssCoder			&coder,		// for mallocing KeyData and plainText
	CSSM_DATA			&plainText)
{
	CSSM_RETURN crtn;
	CSSM_KEY ckey;
	CSSM_CC_HANDLE ccHand = 0;
	
	/* P12 style IV derivation, optional */
	CSSM_DATA iv = {0, NULL};
	CSSM_DATA_PTR ivPtr = NULL;
	if(blockSizeInBytes) {
		coder.allocItem(iv, blockSizeInBytes);
		ivPtr = &iv;
	}
	
	/* P12 style key derivation */
	crtn = p12KeyGen_app(cspHand, ckey, true, keyAlg, pbeHashAlg,
		keySizeInBits, iterCount, salt, pwd, iv);
	if(crtn) {
		return crtn;
	}	
		
	/* CSSM context */
	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
		encrAlg,
		mode,
		NULL,			// access cred
		&ckey,
		ivPtr,			// InitVector, optional
		padding,	
		NULL,			// Params
		&ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateSymmetricContext", crtn);
		return crtn;
	}
	
	/* go - CSP mallocs ptext and rem data */
	CSSM_DATA ourPtext = {0, NULL};
	CSSM_DATA remData = {0, NULL};
	uint32 bytesDecrypted;
	crtn = CSSM_DecryptData(ccHand,
		&cipherText,
		1,
		&ourPtext,
		1,
		&bytesDecrypted,
		&remData);
	if(crtn) {
		cuPrintError("CSSM_EncryptData", crtn);
	}
	else {
		coder.allocCopyItem(ourPtext, plainText);
		plainText.Length = bytesDecrypted;
		
		/* plaintext copied into coder space; free the memory allocated
		 * by the CSP */
		appFreeCssmMemory(cspHand, ourPtext.Data);
	}
	/* an artifact of CSPFUllPLuginSession - this never contains
	 * valid data but sometimes gets mallocds */
	if(remData.Data) {
		appFreeCssmMemory(cspHand, remData.Data);
	}
	CSSM_DeleteContext(ccHand);
	CSSM_FreeKey(cspHand, NULL, &ckey, CSSM_FALSE);
	return crtn;
}

/*
 * Calculate the MAC for a PFX. Caller is either going compare
 * the result against an existing PFX's MAC or drop the result into 
 * a newly created PFX.
 */
CSSM_RETURN p12GenMac_app(
	CSSM_CSP_HANDLE		cspHand,
	const CSSM_DATA		&ptext,	// e.g., NSS_P12_DecodedPFX.derAuthSaafe
	CSSM_ALGORITHMS		alg,	// better be SHA1!
	unsigned			iterCount,
	const CSSM_DATA		&salt,
	const CSSM_DATA		&pwd,		// unicode, double null terminated
	SecNssCoder			&coder,		// for mallocing macData
	CSSM_DATA			&macData)	// RETURNED 
{
	CSSM_RETURN crtn;
	
	/* P12 style key derivation */
	unsigned keySizeInBits;
	CSSM_ALGORITHMS hmacAlg;
	switch(alg) {
		case CSSM_ALGID_SHA1:
			keySizeInBits = 160;
			hmacAlg = CSSM_ALGID_SHA1HMAC;
			break;
		case CSSM_ALGID_MD5:
			/* not even sure if this is legal in p12 world... */
			keySizeInBits = 128;
			hmacAlg = CSSM_ALGID_MD5HMAC;
			break;
		default:
			return CSSMERR_CSP_INVALID_ALGORITHM;
	}
	CSSM_KEY macKey;
	CSSM_DATA iv = {0, NULL};
	crtn = p12KeyGen_app(cspHand, macKey, false, hmacAlg, alg,
		keySizeInBits, iterCount, salt, pwd, iv);
	if(crtn) {
		return crtn;
	}	

	/* prealloc the mac data */
	coder.allocItem(macData, keySizeInBits / 8);
	CSSM_CC_HANDLE ccHand = 0;
	crtn = CSSM_CSP_CreateMacContext(cspHand, hmacAlg, &macKey, &ccHand);
	if(crtn) {
		cuPrintError("CSSM_CSP_CreateMacContext", crtn);
		return crtn;
	}
	
	crtn = CSSM_GenerateMac (ccHand, &ptext, 1, &macData);
	if(crtn) {
		cuPrintError("CSSM_GenerateMac", crtn);
	}
	CSSM_DeleteContext(ccHand);
	CSSM_FreeKey(cspHand, NULL, &macKey, CSSM_FALSE);
	return crtn;
}

/*
 * Verify MAC on an existing PFX.  
 */
CSSM_RETURN p12VerifyMac_app(
	const NSS_P12_DecodedPFX 	&pfx,
	CSSM_CSP_HANDLE				cspHand,
	const CSSM_DATA				&pwd,	// unicode, double null terminated
	SecNssCoder					&coder)	// for temp mallocs
{
	if(pfx.macData == NULL) {
		return CSSMERR_CSP_INVALID_SIGNATURE;
	}
	NSS_P12_MacData &macData = *pfx.macData;
	NSS_P7_DigestInfo &digestInfo  = macData.mac;
	CSSM_OID &algOid = digestInfo.digestAlgorithm.algorithm;
	CSSM_ALGORITHMS macAlg;
	if(!cssmOidToAlg(&algOid, &macAlg)) {
		return CSSMERR_CSP_INVALID_ALGORITHM;
	}
	uint32 iterCount = 0;
	CSSM_DATA &citer = macData.iterations;
	if(!p12DataToInt(citer, iterCount)) {
		return CSSMERR_CSP_INVALID_ATTR_ROUNDS;
	}
	if(iterCount == 0) {
		/* optional, default 1 */
		iterCount = 1;
	}

	/*
	 * In classic fashion, the PKCS12 spec now says:
	 *
	 *      When password integrity mode is used to secure a PFX PDU, 
	 *      an SHA-1 HMAC is computed on the BER-encoding of the contents 
	 *      of the content field of the authSafe field in the PFX PDU.
	 *
	 * So here we go.
	 */
	CSSM_DATA genMac;
	CSSM_RETURN crtn = p12GenMac_app(cspHand, *pfx.authSafe.content.data, 
		macAlg, iterCount, macData.macSalt, pwd, coder, genMac);
	if(crtn) {
		return crtn;
	}
	if(nssCompareCssmData(&genMac, &digestInfo.digest)) {
		return CSSM_OK;
	}
	else {
		return CSSMERR_CSP_VERIFY_FAILED;
	}
}

