FEEAsymmetricContext.cpp   [plain text]


/*
 * Copyright (c) 2000-2001 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.
 */


/*
 * FEEAsymmetricContext.cpp - CSPContexts for FEE asymmetric encryption
 *
 * Created March 8 2001 by dmitch.
 */

#ifdef	CRYPTKIT_CSP_ENABLE

#include "FEEAsymmetricContext.h"
#include "FEECSPUtils.h"
#include <security_cryptkit/falloc.h>
#include <CommonCrypto/CommonDigest.h>

/* validate context for FEED and FEEDExp - no unexpected attributes allowed */
static void validateFeedContext(
	const Context &context)
{
	/* Note we cannot distinguish between zero and "not there" */
	uint32 blockSize = context.getInt(CSSM_ATTRIBUTE_BLOCK_SIZE);
	if(blockSize != 0) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_BLOCK_SIZE);
	}
	CSSM_ENCRYPT_MODE cssmMode = context.getInt(CSSM_ATTRIBUTE_MODE);
	if(cssmMode != 0) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_MODE);
	}
	#if 0
	/* we allow this for CMS wrapping */
	CssmData *iv = context.get<CssmData>(CSSM_ATTRIBUTE_INIT_VECTOR);
	if(iv != NULL) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_INIT_VECTOR);
	}
	#endif
	CSSM_PADDING padding = context.getInt(CSSM_ATTRIBUTE_PADDING); 
	if(padding != 0) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_PADDING);
	}
}

/***
 *** FEED - 1:1 FEED - encrypt n bytes of plaintext, get (roughly) n bytes
 *** of ciphertext. Ciphertext is smaller than with FEED, but this is slower.
 ***/
CryptKit::FEEDContext::~FEEDContext()
{
	if(mFeeFeed) {
		feeFEEDFree(mFeeFeed);
		mFeeFeed = NULL;
	}
	if(mPrivKey && mAllocdPrivKey) {
		feePubKeyFree(mPrivKey);
	}
	if(mPubKey && mAllocdPubKey) {
		feePubKeyFree(mPubKey);
	}
	mPrivKey = NULL;
	mPubKey  = NULL;
	mInitFlag = false;
}

// called by CSPFullPluginSession; reusable
void CryptKit::FEEDContext::init(
	const Context &context, 
	bool encoding)
{
	if(mInitFlag && !opStarted()) {
		/* reusing - e.g. query followed by encrypt */
		return;
	}
	
	/* 
	 * Fetch FEE keys from context. This is an unusual algorithm - it requires
	 * two keys, one public and one private. The public key MUST be stored in
	 * the context with attribute type CSSM_ATTRIBUTE_PUBLIC_KEY, and the private 
	 * key with CSSM_ATTRIBUTE_KEY.
	 *
	 * For now, we require CSSM_KEYUSE_ANY for FEE keys used for this algorithm.
	 * Otherwise we'd have to allow both KEYUSE_ENCRYPT and KEYUSE_DECRYPT for
	 * both keys, and that would require some algorithm-specific hack in 
	 * cspValidateKeyUsageBits() which I really don't want to do.
	 */
	if(mPrivKey == NULL) {
		assert(!opStarted());
		mPrivKey = contextToFeeKey(context,
			session(),
			CSSM_ATTRIBUTE_KEY,
			CSSM_KEYCLASS_PRIVATE_KEY,
			CSSM_KEYUSE_ANY,
			mAllocdPrivKey);
	}
	else {
		assert(opStarted());	
	}
	if(mPubKey == NULL) {
		assert(!opStarted());
		mPubKey = contextToFeeKey(context,
			session(),
			CSSM_ATTRIBUTE_PUBLIC_KEY,
			CSSM_KEYCLASS_PUBLIC_KEY,
			CSSM_KEYUSE_ANY,
			mAllocdPubKey);
	}
	else {
		assert(opStarted());	
	}
	
	/* validate context - no other attributes allowed */
	validateFeedContext(context);

	if(mFeeFeed != NULL) {
		/* not reusable */
		assert(opStarted());
		feeFEEDFree(mFeeFeed);
		mFeeFeed = NULL;
	}
	
	/* OK, looks good. Cook up a feeFEED object. */
	mFeeFeed = feeFEEDNewWithPubKey(mPrivKey,
		mPubKey,
		encoding ? 1 : 0,
		feeRandCallback,
		&session());
	if(mFeeFeed == NULL) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY);
	}
	
	/* finally, have BlockCryptor set up its stuff. */
	unsigned plainBlockSize  = feeFEEDPlainBlockSize(mFeeFeed);
	unsigned cipherBlockSize = feeFEEDCipherBlockSize(mFeeFeed);
	setup(encoding ? plainBlockSize  : cipherBlockSize, // blockSizeIn
		  encoding ? cipherBlockSize : plainBlockSize,	// blockSizeOut
		  false,										// pkcsPad
		  true,											// needsFinal
		  BCM_ECB,
		  NULL);										// IV
	mInitFlag = true;
}

// called by BlockCryptor
void CryptKit::FEEDContext::encryptBlock(
	const void		*plainText,			// length implied (one block)
	size_t			plainTextLen,
	void			*cipherText,	
	size_t			&cipherTextLen,		// in/out, throws on overflow
	bool			final)
{
	feeReturn frtn;
	unsigned actMoved;
	
	assert(mFeeFeed != NULL);
	frtn = feeFEEDEncryptBlock(mFeeFeed,
		(unsigned char *)plainText,
		plainTextLen,
		(unsigned char *)cipherText,
		&actMoved,
		final ? 1 : 0);
	if(frtn) {
		throwCryptKit(frtn, "feeFEEDEncryptBlock");
	}
	if(actMoved > cipherTextLen) {
		/* Overflow already occurred! */
		CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
	}
	cipherTextLen = actMoved;
}

void CryptKit::FEEDContext::decryptBlock(
	const void		*cipherText,		// length implied (one cipher block)
	size_t			cipherTextLen,
	void			*plainText,	
	size_t			&plainTextLen,		// in/out, throws on overflow
	bool			final)
{
	feeReturn frtn;
	unsigned actMoved;
	
	assert(mFeeFeed != NULL);
	frtn = feeFEEDDecryptBlock(mFeeFeed,
		(unsigned char *)cipherText,
		inBlockSize(),
		(unsigned char *)plainText,
		&actMoved,
		final ? 1 : 0);
	if(frtn) {
		throwCryptKit(frtn, "feeFEEDDecryptBlock");
	}
	if(actMoved > plainTextLen) {
		/* Overflow already occurred! */
		CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
	}
	plainTextLen = actMoved;
}

/*
 * Additional query size support, necessary because we don't conform to 
 * BlockCryptor's standard one-to-one block scheme
 */
 
#define BUFFER_DEBUG	0
#if		BUFFER_DEBUG
#define bprintf(s)		printf s
#else
#define bprintf(s)
#endif

size_t CryptKit::FEEDContext::inputSize(
	size_t 			outSize)			// input for given output size
{
	/*
	 * We've been assured that this is NOT called for the final() op...
	 */
	unsigned inSize;
	if(encoding()) {
		inSize = feeFEEDPlainTextSize(mFeeFeed, outSize, 0);
	}
	else {
		inSize = feeFEEDCipherTextSize(mFeeFeed, outSize, 0);
	}
	
	/* account for possible pending buffered input */
	if(inSize >= inBufSize()) {
		inSize -= inBufSize();
	}
	
	/* round up to next block size, then lop off one...anything from
	 * blockSize*n to (blockSize*n)-1 has same effect */
	unsigned inBlocks = ((inSize + inBlockSize()) / inBlockSize());
	inSize = (inBlocks * inBlockSize()) - 1;
	bprintf(("--- FEEDContext::inputSize  inSize 0x%x outSize 0x%x\n",
		inSize, outSize));
	return inSize;
}

size_t CryptKit::FEEDContext::outputSize(
	bool 			final, 
	size_t 			inSize) 			// output for given input size
{
	size_t rtn;
	if(encoding()) {
		rtn = feeFEEDCipherTextSize(mFeeFeed, inSize + inBufSize(), final ? 1 : 0);
	}
	else {
		rtn = feeFEEDPlainTextSize(mFeeFeed, inSize + inBufSize(), final ? 1 : 0);
	}
	bprintf(("--- FEEDContext::outputSize inSize 0x%x outSize 0x%x final %d\n",
		inSize, rtn, final));
	return rtn;
}

void CryptKit::FEEDContext::minimumProgress(
	size_t 			&in, 
	size_t 			&out) 				// minimum progress chunks
{
	if(encoding()) {
		/*
		 * -- in  := one block plaintext
		 * -- out := current cipher size for one block plaintext
		 */
		in  = inBlockSize();
		out = feeFEEDCipherBufSize(mFeeFeed, 0);
	}
	else {
		/*
		 * -- in  := current cipher size for one block plaintext
		 * -- out := one block plaintext
		 */
		in  = feeFEEDCipherBufSize(mFeeFeed, 0); 
		out = outBlockSize();
	}
	
	/* 
	 * Either case - input adjusted for pending. Note inBufSize can be up to one 
	 * input block size, leaving the temp result zero here....
	 */
	assert(in >= inBufSize());
	in -= inBufSize();
	
	/* if it is zero, bump it up so caller can make something happen */
	if(in == 0) {
		in++;
	}
	bprintf(("--- FEEDContext::minProgres inSize 0x%x outSize 0x%x\n",
		in, out));
}

/***
 *** FEEDExp - 2:1 FEED - encrypt n bytes of plaintext, get (roughly) 2n bytes
 *** of ciphertext. Ciphertext is larger than with FEED, but this is faster.
 ***/
CryptKit::FEEDExpContext::~FEEDExpContext()
{
	if(mFeeFeedExp) {
		feeFEEDExpFree(mFeeFeedExp);
		mFeeFeedExp = NULL;
	}
	if(mFeeKey && mAllocdFeeKey) {
		feePubKeyFree(mFeeKey);
	}
	mFeeKey = NULL;
	mInitFlag = false;
}

// called by CSPFullPluginSession; reusable
void CryptKit::FEEDExpContext::init(
	const Context &context, 
	bool encoding)
{
	if(mInitFlag && !opStarted()) {
		/* reusing - e.g. query followed by encrypt */
		return;
	}
	
	/* fetch FEE key from context */
	CSSM_KEYCLASS 	keyClass;
	CSSM_KEYUSE		keyUse;
	
	if(encoding) {
		/* encrypting to public key */
		keyClass = CSSM_KEYCLASS_PUBLIC_KEY;
		keyUse   = CSSM_KEYUSE_ENCRYPT;
	}
	else {
		/* decrypting with private key */
		keyClass = CSSM_KEYCLASS_PRIVATE_KEY;
		keyUse   = CSSM_KEYUSE_DECRYPT;
	}
	if(mFeeKey == NULL) {
		assert(!opStarted());
		mFeeKey = contextToFeeKey(context,
			session(),
			CSSM_ATTRIBUTE_KEY,
			keyClass,
			keyUse,
			mAllocdFeeKey);
	}
	else {
		assert(opStarted());
	}
	
	/* validate context - no other attributes allowed */
	validateFeedContext(context);

	/* OK, looks good. Cook up a feeFEEDExp object. */
	if(mFeeFeedExp != NULL) {
		/* not reusable */
		assert(opStarted());
		feeFEEDExpFree(mFeeFeedExp);
		mFeeFeedExp = NULL;
	}
	mFeeFeedExp = feeFEEDExpNewWithPubKey(mFeeKey,
		feeRandCallback,
		&session());
	if(mFeeFeedExp == NULL) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY);
	}
	
	/* finally, have BlockCryptor set up its stuff. */
	unsigned plainBlockSize = feeFEEDExpPlainBlockSize(mFeeFeedExp);
	unsigned cipherBlockSize = feeFEEDExpCipherBlockSize(mFeeFeedExp);
	setup(encoding ? plainBlockSize  : cipherBlockSize, // blockSizeIn
		  encoding ? cipherBlockSize : plainBlockSize,	// blockSizeOut
		  false,										// pkcs5Pad
		  true,											// needsFinal
		  BCM_ECB,
		  NULL);											// IV
	mInitFlag = true;
}

// called by BlockCryptor
void CryptKit::FEEDExpContext::encryptBlock(
	const void		*plainText,			// length implied (one block)
	size_t			plainTextLen,
	void			*cipherText,	
	size_t			&cipherTextLen,		// in/out, throws on overflow
	bool			final)
{
	feeReturn frtn;
	unsigned actMoved;
	
	assert(mFeeFeedExp != NULL);
	frtn = feeFEEDExpEncryptBlock(mFeeFeedExp,
		(unsigned char *)plainText,
		plainTextLen,
		(unsigned char *)cipherText,
		&actMoved,
		final ? 1 : 0);
	if(frtn) {
		throwCryptKit(frtn, "feeFEEDExpEncryptBlock");
	}
	if(actMoved > cipherTextLen) {
		/* Overflow already occurred! */
		CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
	}
	cipherTextLen = actMoved;
}

void CryptKit::FEEDExpContext::decryptBlock(
	const void		*cipherText,		// length implied (one cipher block)
	size_t			cipherTextLen,
	void			*plainText,	
	size_t			&plainTextLen,		// in/out, throws on overflow
	bool			final)
{
	feeReturn frtn;
	unsigned actMoved;
	
	assert(mFeeFeedExp != NULL);
	frtn = feeFEEDExpDecryptBlock(mFeeFeedExp,
		(unsigned char *)cipherText,
		inBlockSize(),
		(unsigned char *)plainText,
		&actMoved,
		final ? 1 : 0);
	if(frtn) {
		throwCryptKit(frtn, "feeFEEDExpDecryptBlock");
	}
	if(actMoved > plainTextLen) {
		/* Overflow already occurred! */
		CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
	}
	plainTextLen = actMoved;
}

/* convert uint32 to big-endian 4 bytes */
static void int32ToBytes(
	uint32_t i,
	unsigned char *b)
{
	for(int dex=3; dex>=0; dex--) {
		b[dex] = i;
		i >>= 8;
	}
}

/* 
 * X9.63 key derivation with optional SharedInfo passed as 
 * context attribute CSSM_ATTRIBUTE_SALT.
 */
static feeReturn ecdhKdf(
	const Context &context, 
	const unsigned char *Z,		/* shared secret, i.e., output of ECDH */
	unsigned ZLen,
	CSSM_DATA *K)				/* output RETURNED in K->Data, length K->Length bytes */
{
	/* SharedInfo via salt, from context, optional */
	const unsigned char *sharedInfo = NULL;
	CSSM_SIZE sharedInfoLen = 0;
	
	CssmData *salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT);
	if(salt != NULL) {
		sharedInfo = (const unsigned char *)salt->Data;
		sharedInfoLen = salt->Length;
	}
	
	unsigned char *outp = K->Data;
	CSSM_SIZE bytesToGo = K->Length;
	CC_SHA1_CTX sha1;
	uint32_t counter = 1;
	uint8 counterBytes[4];
	unsigned char digOut[CC_SHA1_DIGEST_LENGTH];
	
	do {
		/* K[i] = Hash(Z || Counter || SharedInfo) */
		CC_SHA1_Init(&sha1);
		CC_SHA1_Update(&sha1, Z, ZLen);
		int32ToBytes(counter, counterBytes);
		CC_SHA1_Update(&sha1, counterBytes, 4);
		if(sharedInfoLen) {
			CC_SHA1_Update(&sha1, sharedInfo, sharedInfoLen);
		}
		CC_SHA1_Final(digOut, &sha1);
		
		/* digest --> output */
		unsigned toMove = CC_SHA1_DIGEST_LENGTH;
		if(toMove > bytesToGo) {
			toMove = bytesToGo;
		}
		memmove(outp, digOut, toMove);
		
		counter++;
		outp += toMove;
		bytesToGo -= toMove;
		
	} while(bytesToGo);
	
	return FR_Success;
}

/*
 * Elliptic curve Diffie-Hellman key exchange. The public key is 
 * specified in one of two ways - a raw X9.62 format public key 
 * string in Param, or a CSSM_KEY in the Context. 
 * Requested size, in keyData->Length, must be the same size as
 * the keys' modulus. Data is returned in keyData->Data, which is 
 * allocated by the caller.
 * Optionally performs X9.63 key derivation if algId == 
 * CSSM_ALGID_ECDH_X963_KDF, with the optional SharedInfo passed
 * as optional context attribute CSSM_ATTRIBUTE_SALT.
 */
void CryptKit::DeriveKey_ECDH (
	const Context &context,
	CSSM_ALGORITHMS algId,		
	const CssmData &Param,			// other's public key. may be empty
	CSSM_DATA *keyData,				// mallocd by caller
									// we fill in keyData->Length bytes
	AppleCSPSession &session)
{
	bool mallocdPrivKey;
	size_t privSize;
	
	/* private ECDH key from context - required */
	feePubKey privKey = contextToFeeKey(context, session, CSSM_ATTRIBUTE_KEY,
		CSSM_KEYCLASS_PRIVATE_KEY, CSSM_KEYUSE_DERIVE, mallocdPrivKey);
	if(privKey == NULL) {
		CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_KEY);
	}
	privSize = (feePubKeyBitsize(privKey) + 7) / 8;
	if((algId == CSSM_ALGID_ECDH) & (privSize != keyData->Length)) {
		/* exact match required here */
		CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
	}
	
	/*
	 * Public key ("their" key) can come from two places:
	 * -- in the context as a CSSM_ATTRIBUTE_PUBLIC_KEY. This is how 
	 *    public keys in X509 format must be used in this function.
	 * -- in the incoming Param, the raw unformatted (ANSI X9.62) form 
	 */
	bool mallocdPubKey = false;
	feePubKey pubKey = NULL;
	if(Param.Data == NULL) {
		/* this throws if no key present */
		pubKey = contextToFeeKey(context, session, CSSM_ATTRIBUTE_PUBLIC_KEY,
			CSSM_KEYCLASS_PUBLIC_KEY, CSSM_KEYUSE_DERIVE, mallocdPubKey);
	}
	if((pubKey == NULL) && (Param.Data == NULL)) {
		errorLog0("DeriveKey_ECDH: no pub_key\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
	}
	unsigned char *output = NULL;
	unsigned outputLen = 0;
	feeReturn frtn = feePubKeyECDH(privKey, pubKey, 
		(const unsigned char *)Param.Data, (unsigned)Param.Length,
		&output, &outputLen);
	if(frtn) {
		goto errOut;
	}
	switch(algId) {
		case CSSM_ALGID_ECDH:
			/*
			 * Raw ECDH - requested length must match the generated size
			 * exactly. If so, return the result unmodified.
			 */
			if(outputLen != keyData->Length) {
				errorLog0("DeriveKey_ECDH: length mismatch\n");
				frtn = FR_Internal;
				break;
			}
			memmove(keyData->Data, output, outputLen);
			break;
		case CSSM_ALGID_ECDH_X963_KDF:
			/* Further processing... */
			frtn = ecdhKdf(context, output, outputLen, keyData);
			break;
		default:
			/* shouldn't be here */
			frtn = FR_Internal;
			break;
	}

errOut:
	if(mallocdPrivKey) {
		feePubKeyFree(privKey);
	}
	if(mallocdPubKey) {
		feePubKeyFree(pubKey);
	}
	if(output != NULL) {
		ffree(output);
	}
	if(frtn) {
		throwCryptKit(frtn, NULL);
	}
}

#endif	/* CRYPTKIT_CSP_ENABLE */