deriveKey.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.
 */


/*
 	File:		deriveKey.cpp
	
 	Contains:	CSSM_DeriveKey functions

 	Copyright:	(C) 2000 by Apple Computer, Inc., all rights reserved

 	Written by:	Doug Mitchell <dmitch@apple.com>	
*/

#include <HMACSHA1.h>
#include <pbkdf2.h>
#include <pbkdDigest.h>
#include <pkcs12Derive.h>
#include "AppleCSPSession.h"
#include "AppleCSPUtils.h"
#include "AppleCSPContext.h"
#include "cspdebugging.h"
#include <security_cdsa_utilities/context.h>
#include <DH_exchange.h>
#include "FEEAsymmetricContext.h"

/* minimum legal values */
#define PBKDF2_MIN_SALT			8		/* bytes */
#define PBKDF2_MIN_ITER_CNT		1000	/* iteration count */

#define ALLOW_ZERO_PASSWORD		1 

void AppleCSPSession::DeriveKey_PBKDF2(
	const Context &context,
	const CssmData &Param,
	CSSM_DATA *keyData)
{
	/* validate algorithm-specific arguments */

	/* Param must point to a CSSM_PKCS5_PBKDF2_PARAMS */
	if(Param.Length != sizeof(CSSM_PKCS5_PBKDF2_PARAMS)) {
		errorLog0("DeriveKey_PBKDF2: Param wrong size\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
	}
	const CSSM_PKCS5_PBKDF2_PARAMS *pbkdf2Params = 
		reinterpret_cast<const CSSM_PKCS5_PBKDF2_PARAMS *>(Param.Data);
	if(pbkdf2Params == NULL) {
		errorLog0("DeriveKey_PBKDF2: null Param.Data\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
	}
	
	/* Get passphrase from either baseKey or from CSSM_PKCS5_PBKDF2_PARAMS */
	CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
	CSSM_SIZE	passphraseLen = 0;
	uint8 	*passphrase = NULL;
	if(passKey != NULL) {
		AppleCSPContext::symmetricKeyBits(context, *this,
			CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE, 
			passphrase, passphraseLen);
	}
	else {
		passphraseLen = pbkdf2Params->Passphrase.Length;
		passphrase = pbkdf2Params->Passphrase.Data;
	}
	
	#if 	!ALLOW_ZERO_PASSWORD
	/* passphrase required */
	if(passphrase == NULL) {
		errorLog0("DeriveKey_PBKDF2: null Passphrase\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
	}
	if(passphraseLen == 0) {		
		/* FIXME - enforce minimum length? */
		errorLog0("DeriveKey_PBKDF2: zero length passphrase\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
	}
	#endif	/* ALLOW_ZERO_PASSWORD */
	
	if(pbkdf2Params->PseudoRandomFunction != 
			CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1) {
		errorLog0("DeriveKey_PBKDF2: invalid PRF\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
	}
	
	/* salt, from context, required */
	CssmData salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT,
		CSSMERR_CSP_MISSING_ATTR_SALT);
	if((salt.Data == NULL) || (salt.Length < PBKDF2_MIN_SALT)){
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SALT);
	}
	
	/* iteration count, from context, required */
	uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT,
		CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT);
	if(iterCount < PBKDF2_MIN_ITER_CNT) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_ITERATION_COUNT);
	}
	
	/* 
	 * allocate a temp buffer, length 
	 *    = MAX (hLen, saltLen + 4) + 2 * hLen
	 *    = MAX (kSHA1DigestSize, saltLen + 4) + 2 * kSHA1DigestSize
	 */
	uint32 tempLen = salt.Length + 4;
	if(tempLen < kSHA1DigestSize) {
		tempLen = kSHA1DigestSize;
	}
	tempLen += (2 * kSHA1DigestSize);
	CSSM_DATA tempData = {0, NULL};
	setUpData(tempData, tempLen, privAllocator);
	
	/* go */
	pbkdf2 (hmacsha1, 
		kSHA1DigestSize,
		passphrase, passphraseLen,
		salt.Data, salt.Length,
		iterCount,
		keyData->Data, keyData->Length,
		tempData.Data);
	freeData(&tempData, privAllocator, false);
}

/*
 * PKCS5 v1.5 key derivation. Also used for traditional openssl key 
 * derivation, which is mighty similar to PKCS5 v1.5, with the addition
 * of the ability to generate more than (keysize + ivsize) bytes. 
 */
void AppleCSPSession::DeriveKey_PKCS5_V1_5(
	const Context &context,
	CSSM_ALGORITHMS algId,
	const CssmData &Param,			// IV optional, mallocd by app to indicate
									//   size 
	CSSM_DATA *keyData)				// mallocd by caller to indicate size
{
	CSSM_DATA pwd = {0, NULL};
	
	/* password from either Seed.Param or from base key */
	CssmCryptoData *cryptData = 
		context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
	if((cryptData != NULL) && (cryptData->Param.Length != 0)) {
		pwd = cryptData->Param;
	}
	else {
		/* Get secure passphrase from base key */
		CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
		if (passKey != NULL) {
			AppleCSPContext::symmetricKeyBits(context, *this,
				CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE, 
				pwd.Data, pwd.Length);
		}
	}

	if(pwd.Data == NULL) {
		errorLog0("DeriveKey_PKCS5_V1_5: null Passphrase\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
	}
	if(pwd.Length == 0) {		
		errorLog0("DeriveKey_PKCS5_V1_5: zero length passphrase\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
	}

	CSSM_ALGORITHMS hashAlg;
	unsigned digestLen;
	bool opensslAlg = false;
	switch(algId) {
		case CSSM_ALGID_PKCS5_PBKDF1_MD5:
			hashAlg = CSSM_ALGID_MD5;
			digestLen = kMD5DigestSize;
			break;
		case CSSM_ALGID_PKCS5_PBKDF1_MD2:
			hashAlg = CSSM_ALGID_MD2;
			digestLen = kMD2DigestSize;
			break;
		case CSSM_ALGID_PKCS5_PBKDF1_SHA1:
			hashAlg = CSSM_ALGID_SHA1;
			digestLen = kSHA1DigestSize;
			break;
		case CSSM_ALGID_PBE_OPENSSL_MD5:
			hashAlg = CSSM_ALGID_MD5;
			digestLen = kMD5DigestSize;
			opensslAlg = true;
			break;			
		default:
			/* should not have been called */
			assert(0);
			CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
	}

	/* IV optional */
	CSSM_DATA iv = Param;
	
	/* total requested length can't exceed digest size for struct PKCS5 v1.5*/
	if(!opensslAlg && ((keyData->Length + iv.Length) > digestLen)) {
		errorLog0("DeriveKey_PKCS5_V1_5: requested length larger than digest\n");
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY_LENGTH);
	}
	
	/* salt, from context, required */
	CssmData salt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT,
		CSSMERR_CSP_MISSING_ATTR_SALT);
	if(salt.Data == NULL) {
		CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SALT);
	}
	
	/* iteration count, from context, required */
	uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT,
		CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT);
		
	/* 
	 * Apply the underlying hash function Hash for c iterations to
	 *  the concatenation of the password P and the salt S, then 
	 * extract the first dkLen octets to produce a derived key DK:
	 *
	 *		T1 = Hash (P || S) ,
	 *		T2 = Hash (T1) ,
	 *		...
	 *		Tc = Hash (Tc-1) ,
	 *		DK = Tc<0..dkLen-1> .
	 */
	DigestCtx ctx;
	uint8 *keyDataP		= keyData->Data;
	uint32 keyBytesToGo = keyData->Length;
	uint8 *ivDataP		= iv.Data;
	uint32 ivBytesToGo  = iv.Length;
	bool looping		= false;		// true for additional bytes for openssl
	unsigned char digestOut[kMaxDigestSize];
	
	for(;;) {
		/* this loop guaranteed to only run once if !opensslAlg */
		DigestCtxInit(&ctx, hashAlg);
		
		if(looping) {
			/* openssl addition: re-digest the digest here */
			DigestCtxUpdate(&ctx, digestOut, digestLen);
		}
		
		/* digest password then salt */
		DigestCtxUpdate(&ctx, pwd.Data, pwd.Length);
		DigestCtxUpdate(&ctx, salt.Data, salt.Length);

		DigestCtxFinal(&ctx, digestOut);
		
		/* now iterCount-1 more iterations */
		for(unsigned dex=1; dex<iterCount; dex++) {
			DigestCtxInit(&ctx, hashAlg);
			DigestCtxUpdate(&ctx, digestOut, digestLen);
			DigestCtxFinal(&ctx, digestOut);
		}
		
		/* first n bytes to the key */
		uint32 bytesAvail = digestLen;
		uint32 toMove = (keyBytesToGo > bytesAvail) ? bytesAvail : keyBytesToGo;
		memmove(keyDataP, digestOut, toMove);
		uint8 *remainder = digestOut + toMove;
		bytesAvail   -= toMove;
		keyDataP     += toMove;
		keyBytesToGo -= toMove;
		
		/* then optionally some to IV */
		if(ivBytesToGo && bytesAvail) {
			toMove = (ivBytesToGo > bytesAvail) ? bytesAvail : ivBytesToGo;
			memmove(ivDataP, remainder, toMove);
			ivDataP     += toMove;
			ivBytesToGo -= toMove;
		}
		if((keyBytesToGo == 0) && (ivBytesToGo == 0)) {
			/* guaranteed true for PKCS5 v1.5 */
			break;
		}
		
		assert(opensslAlg == true);
		looping = true;
	}
	DigestCtxFree(&ctx);
}

/*
 * Member function initially declared for CSPAbstractPluginSession;
 * we're overriding the null version in CSPFullPluginSession.
 *
 * We'll generate any type of key (for now). 
 */
void AppleCSPSession::DeriveKey(
	CSSM_CC_HANDLE CCHandle,
	const Context &context,
	CssmData &Param,
	uint32 KeyUsage,
	uint32 KeyAttr,
	const CssmData *KeyLabel,
	const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
	CssmKey &DerivedKey)
{
	/* validate input args, common to all algorithms */
	switch(context.algorithm()) {
		case CSSM_ALGID_PKCS5_PBKDF2:
		case CSSM_ALGID_DH:
		case CSSM_ALGID_PKCS12_PBE_ENCR:
		case CSSM_ALGID_PKCS12_PBE_MAC:
		case CSSM_ALGID_PKCS5_PBKDF1_MD5:
		case CSSM_ALGID_PKCS5_PBKDF1_MD2:
		case CSSM_ALGID_PKCS5_PBKDF1_SHA1:
		case CSSM_ALGID_PBE_OPENSSL_MD5:
		case CSSM_ALGID_OPENSSH1:
		#if CRYPTKIT_CSP_ENABLE
		case CSSM_ALGID_ECDH:
		case CSSM_ALGID_ECDH_X963_KDF:
		#endif
			break;
		/* maybe more here, later */
		default:
			CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
	}
	DerivedKey.KeyData.Data = NULL;
	DerivedKey.KeyData.Length = 0;
	cspKeyStorage keyStorage = cspParseKeyAttr(CKT_Session, KeyAttr);
	cspValidateKeyUsageBits(CKT_Session, KeyUsage);
	
	/* outgoing key type, required (though any algorithm is OK) */
	uint32 keyType = context.getInt(CSSM_ATTRIBUTE_KEY_TYPE,
		CSSMERR_CSP_MISSING_ATTR_KEY_TYPE);

	/* outgoing key size, required - any nonzero value is OK */
	uint32 reqKeySize = context.getInt(
		CSSM_ATTRIBUTE_KEY_LENGTH, 
		CSSMERR_CSP_MISSING_ATTR_KEY_LENGTH);

	/* cook up a place to put the key data */
	uint32 keySizeInBytes = (reqKeySize + 7) / 8;
	SymmetricBinaryKey *binKey = NULL;
	CSSM_DATA_PTR keyData = NULL;
	
	switch(keyStorage) {
		case CKS_None:
			/* no way */
			CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK);
		case CKS_Ref:
			/* cook up a symmetric binary key */
			binKey = new SymmetricBinaryKey(reqKeySize);
			keyData = &binKey->mKeyData;
			break;
		case CKS_Data:
			/* key bytes --> caller's cssmKey */
			keyData = &DerivedKey.KeyData;
			setUpData(*keyData, keySizeInBytes, 
				normAllocator);
			break;
	}

	/* break off to algorithm-specific code, whose job it is
	 * to fill in keyData->Data with keyData->Length bytes */
	switch(context.algorithm()) {
		case CSSM_ALGID_PKCS5_PBKDF2:
			DeriveKey_PBKDF2(context,
				Param,
				keyData);
			break;
		case CSSM_ALGID_DH:
			DeriveKey_DH(context,
				Param,
				keyData,
				*this);
			break;
		case CSSM_ALGID_PKCS12_PBE_ENCR:
		case CSSM_ALGID_PKCS12_PBE_MAC:
			DeriveKey_PKCS12(context,
				*this,
				Param,
				keyData);
			break;
		case CSSM_ALGID_PKCS5_PBKDF1_MD5:
		case CSSM_ALGID_PKCS5_PBKDF1_MD2:
		case CSSM_ALGID_PKCS5_PBKDF1_SHA1:
		case CSSM_ALGID_PBE_OPENSSL_MD5:
			DeriveKey_PKCS5_V1_5(context,
				context.algorithm(),
				Param,
				keyData);
			break;
		case CSSM_ALGID_OPENSSH1:
			DeriveKey_OpenSSH1(context,
				context.algorithm(),
				Param,
				keyData);
			break;
		#if CRYPTKIT_CSP_ENABLE
		case CSSM_ALGID_ECDH:
		case CSSM_ALGID_ECDH_X963_KDF:
			CryptKit::DeriveKey_ECDH(context,
				context.algorithm(),
				Param,
				keyData,
				*this);
			break;
		#endif
		/* maybe more here, later */
		default:
			assert(0);
	}
	
	/* set up outgoing header */
	KeyAttr &= ~KEY_ATTR_RETURN_MASK;
	CSSM_KEYHEADER &hdr = DerivedKey.KeyHeader;
	setKeyHeader(hdr,
		plugin.myGuid(),
		keyType,
		CSSM_KEYCLASS_SESSION_KEY, 
		KeyAttr, 
		KeyUsage);
	/* handle derived size < requested size, legal for Diffie-Hellman */
	hdr.LogicalKeySizeInBits = keyData->Length * 8;
	
	if(keyStorage == CKS_Ref) {
		/* store and convert to ref key */
		addRefKey(*binKey, DerivedKey);
	}
	else {
		/* Raw data */
		hdr.BlobType = CSSM_KEYBLOB_RAW;
		hdr.Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; 
	}
}