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


//
// wrapKey.cpp - wrap/unwrap key functions for AppleCSPSession
//

/*
 * Currently the Security Server wraps public keys when they're stored, so we have
 * to allow this. We might not want to do this in the real world. 
 */
#define ALLOW_PUB_KEY_WRAP		1

#include "AppleCSPSession.h"
#include "AppleCSPUtils.h"
#include "AppleCSPKeys.h"
#include "pkcs8.h"
#include "cspdebugging.h"

/*
 * Wrap key function. Used for two things:
 *
 *  -- Encrypt and encode a private or session key for export to
 *     a foreign system or program. Any type of keys may be used
 *     for the  unwrapped key and the wrapping (encrypting) key,
 *     as long as this CSP understands those keys. The context
 *     must be of  class ALGCLASS_SYMMETRIC or ALGCLASS_ASYMMETRIC,
 *     matching the wrapping key. 
 *
 *	   In the absence of an explicit CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT
 *     attribute, private keys will be PKCS8 wrapped; session keys will be 
 *     PKCS7 wrapped. Both input keys may be in raw or reference 
 *     format. Wrapped key will have BlobType CSSM_KEYBLOB_WRAPPED.
 * 
 *  -- Convert a reference key to a RAW key (with no encrypting).
 *     This is called a NULL wrap; no wrapping key need be present in
 *     the context, but the context must be of class 
 *	   ALGCLASS_SYMMETRIC and algorithm ALGID_NONE. 
 *
 * There are serious inconsistencies in the specification of wrap 
 * algorithms to be used in the various CDSA specs (c914.pdf, 
 * CSP Behavior spec) and between those specs and the PKCS standards
 * PKCS7, PKCS8, RFC2630). Here is what this module implements:
 *
 * On a wrap key op, the caller can add a CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT
 * attribute to the context to specify the wrapping algorithm to be used.
 * If it's there, that's what we use if appropriate for the incoming key
 * types. Otherwise we figure out a reasonable default from the incoming
 * key types. The wrapped key always has the appropriate KeyHeader.Format
 * field set indicating how it was wrapped. Defaults are shows below. 
 *
 * The format CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM is used to indicate
 * a modified CMS-style wrapping which is similar to that specified in
 * RFC2630, with some modification. 
 *
 * Default wrapping if none specified based on ther unwrapped key as 
 * follows:
 *
 * UnwrappedKey			Wrap format
 * ------------			-----------
 * Symmetric 			PKCS7
 * Public				APPLE_CUSTOM
 * FEE private			APPLE_CUSTOM
 * Other private		PKCS8
 */
 
void AppleCSPSession::WrapKey(
		CSSM_CC_HANDLE CCHandle,
		const Context &Context,
        const AccessCredentials &AccessCred,
        const CssmKey &UnwrappedKey,
        const CssmData *DescriptiveData,
        CssmKey &WrappedKey,
		CSSM_PRIVILEGE Privilege)
{
	CssmKey::Header 		&wrappedHdr   = WrappedKey.header();
	bool 					isNullWrap = false;
	CssmKey					*wrappingKey = NULL;
	CSSM_KEYBLOB_FORMAT		wrapFormat;
	
	switch(UnwrappedKey.keyClass()) {
		case CSSM_KEYCLASS_PUBLIC_KEY:
		case CSSM_KEYCLASS_PRIVATE_KEY:
		case CSSM_KEYCLASS_SESSION_KEY:
			break;
		default:
			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
	}

	/* wrapping key only required for non-NULL wrap */
	wrappingKey = Context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
	if(wrappingKey == NULL) {
		if((Context.algorithm() == CSSM_ALGID_NONE) &&
		   (Context.type() == CSSM_ALGCLASS_SYMMETRIC)) {
				// NULL wrap, OK
				isNullWrap = true;
		}
		else {
			errorLog0("WrapKey: missing wrapping key\n");
			CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_KEY);
		}
	}
	
	/*
	 * Validate misc. params as best we can
	 */
	if(isNullWrap) {
		wrapFormat = CSSM_KEYBLOB_WRAPPED_FORMAT_NONE;
	}
	else {
		/*
		 * Can only wrap session and private keys. 
		 */
		#if		!ALLOW_PUB_KEY_WRAP
		if(UnwrappedKey.keyClass() == CSSM_KEYCLASS_PUBLIC_KEY) {
			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
		}
		#endif	/* ALLOW_PUB_KEY_WRAP */
		cspValidateIntendedKeyUsage(&wrappingKey->KeyHeader, CSSM_KEYUSE_WRAP);
		cspVerifyKeyTimes(wrappingKey->KeyHeader);
		
		/*
		 * make sure wrapping key type matches context
		 */
		CSSM_CONTEXT_TYPE wrapType;
		switch(wrappingKey->KeyHeader.KeyClass) {
			case CSSM_KEYCLASS_PUBLIC_KEY:
			case CSSM_KEYCLASS_PRIVATE_KEY:
				wrapType = CSSM_ALGCLASS_ASYMMETRIC;
				break;
			case CSSM_KEYCLASS_SESSION_KEY:
				wrapType = CSSM_ALGCLASS_SYMMETRIC;
				break;
			default:
				errorLog0("WrapKey: bad class of wrappingKey\n");
				CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY);
		}
		if(wrapType != Context.type()) {
			errorLog0("WrapKey: mismatch wrappingKey/contextType\n");
			CssmError::throwMe(CSSMERR_CSP_INVALID_CONTEXT);
		}
		if(Context.algorithm() == CSSM_ALGID_NONE) {
			errorLog0("WrapKey: null wrap alg, non-null key\n");
			CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
		}

		/*
		 * Get optional wrap format, set default per incoming keys
		 * Note: no such atrribute ==> 0 ==> FORMAT_NONE, which we
		 * take to mean "use the default".
		 */
		wrapFormat = Context.getInt(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT);
		if(wrapFormat == CSSM_KEYBLOB_WRAPPED_FORMAT_NONE) {
			/* figure out a default based on unwrapped key */
			switch(UnwrappedKey.keyClass()) {
				case CSSM_KEYCLASS_SESSION_KEY:
					wrapFormat = CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7;
					break;
				case CSSM_KEYCLASS_PUBLIC_KEY:
					wrapFormat = CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM; 
					break;
				case CSSM_KEYCLASS_PRIVATE_KEY:
					switch(UnwrappedKey.algorithm()) {
						case CSSM_ALGID_FEE:
							wrapFormat = CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM; 
							break;
						default:
							wrapFormat = CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8; 
							break;
					}
					break;
				default:
					/* NOT REACHED - checked above */
					break;
			}
		}		/* no format present or FORMAT_NONE */
	}
	
	/* make sure we have a valid format here */
	switch(wrapFormat) {
		case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7:
			if(UnwrappedKey.keyClass() != CSSM_KEYCLASS_SESSION_KEY) {
				/* this wrapping style only for symmetric keys */
				CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
			}
			break;
		case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8:
		case CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL:
			if(UnwrappedKey.keyClass() != CSSM_KEYCLASS_PRIVATE_KEY) {
				/* these wrapping styles only for private keys */
				CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
			}
			break;
		case CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM:
			/* no restrictions (well AES can't be the wrap alg but that will 
			 * be caught later */
			break;
		case CSSM_KEYBLOB_WRAPPED_FORMAT_NONE:
			if(isNullWrap) {
				/* only time this is OK */
				break;
			}
			/* else fall thru */
		default:
			dprintf1("KeyWrap: invalid wrapFormat (%d)\n", (int)wrapFormat);
			CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_WRAPPED_KEY_FORMAT);
	}
	/* get the blob to be wrappped */
	CssmData rawBlob;
	bool allocdRawBlob = false;
	CSSM_KEYBLOB_FORMAT rawFormat;
	
	/* 
	 * Outgoing same as incoming unless a partial key is completed during 
	 * generateKeyBlob()
	 */
	const CssmKey::Header &unwrappedHdr = UnwrappedKey.header();
	CSSM_KEYATTR_FLAGS unwrappedKeyAttrFlags = unwrappedHdr.KeyAttr;
	
	switch(UnwrappedKey.blobType()) {
		case CSSM_KEYBLOB_RAW:
			/* trivial case */
			rawBlob = CssmData::overlay(UnwrappedKey.KeyData);
			rawFormat = UnwrappedKey.blobFormat();
			break;
		case CSSM_KEYBLOB_REFERENCE:
			/* get binary key, then get blob from it */
			{
				BinaryKey &binKey = lookupRefKey(UnwrappedKey);
				
				/*
				 * Subsequent tests for extractability: don't trust the 
				 * caller's header; use the one in the BinaryKey.
				 */
				CSSM_KEYATTR_FLAGS keyAttr = binKey.mKeyHeader.KeyAttr;
				if(!(keyAttr & CSSM_KEYATTR_EXTRACTABLE)) {
					/* this key not extractable in any form */
					CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK);
				}
				
				/*
				 * Null wrap - prevent caller from obtaining 
				 * clear bits if CSSM_KEYATTR_SENSITIVE
				 */
				if(isNullWrap && (keyAttr & CSSM_KEYATTR_SENSITIVE)) {
					CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK);
				}

				/*
				 * Special case for PKCS8 and openssl: need to get blob of a specific
				 * algorithm-dependent format. Caller can override our 
				 * preference with a 
				 * CSSM_ATTRIBUTE_{PRIVATE,PUBLIC,SESSION}_KEY_FORMAT 
				 * context attribute. 
				 */
				rawFormat = requestedKeyFormat(Context, UnwrappedKey);
				if(rawFormat == CSSM_KEYBLOB_RAW_FORMAT_NONE) {
					CSSM_ALGORITHMS keyAlg = binKey.mKeyHeader.AlgorithmId;
					switch(wrapFormat) {
						case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8:
							rawFormat = pkcs8RawKeyFormat(keyAlg);
							break;
						case CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL:
							rawFormat = opensslRawKeyFormat(keyAlg);
						default:
							/* punt and take default for key type */
							break;
					}
				}

				/* optional parameter-bearing key */
				CssmKey *paramKey = Context.get<CssmKey>(CSSM_ATTRIBUTE_PARAM_KEY);
				binKey.generateKeyBlob(privAllocator,
					rawBlob,
					rawFormat,
					*this,
					paramKey,
					unwrappedKeyAttrFlags);
			}
			allocdRawBlob = true;		// remember - we need to free
			break;
			
		default:
			errorLog0("WrapKey: bad unwrappedKey BlobType\n");
			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
	}

	/*
	 * Prepare outgoing header.
	 */
	setKeyHeader(wrappedHdr,
		plugin.myGuid(),
		unwrappedHdr.algorithm(),		// same as incoming 
		unwrappedHdr.keyClass(),		// same as incoming
		unwrappedKeyAttrFlags,
		unwrappedHdr.KeyUsage);
	wrappedHdr.LogicalKeySizeInBits = unwrappedHdr.LogicalKeySizeInBits;
	wrappedHdr.WrapAlgorithmId = Context.algorithm(); 	// true for null 
														// and non-Null 
	wrappedHdr.StartDate = unwrappedHdr.StartDate;
	wrappedHdr.EndDate = unwrappedHdr.EndDate;
	wrappedHdr.Format = wrapFormat;
	if(isNullWrap) {
		wrappedHdr.BlobType = CSSM_KEYBLOB_RAW;
	}
	else {
		wrappedHdr.BlobType = CSSM_KEYBLOB_WRAPPED;
	}
	
	/* 
	 * special case - break out here for custom Apple CMS  
	 */
	if(!isNullWrap && (wrapFormat == CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM)) {
		try {
			WrapKeyCms(CCHandle,
				Context,
				AccessCred,
				UnwrappedKey,
				rawBlob,
				allocdRawBlob,
				DescriptiveData,
				WrappedKey,
				Privilege);
		}
		catch(...) {
			if(allocdRawBlob) {
				freeCssmData(rawBlob, privAllocator);
			}
			throw;
		}
		if(allocdRawBlob) {
			freeCssmData(rawBlob, privAllocator);
		}
		return;
	}

	
	/*
	 * Generate wrapped blob. Careful, we need to conditionally free
	 * rawBlob on error.
	 */
	CssmData encryptedBlob;
	CssmData remData;
	WrappedKey.KeyData.Data = NULL;		// ignore possible incoming KeyData
	WrappedKey.KeyData.Length = 0;
	
	try {
		if(isNullWrap) {
			/* copy raw blob to caller's wrappedKey */
			copyCssmData(rawBlob, 
				CssmData::overlay(WrappedKey.KeyData), 
				normAllocator);
			wrappedHdr.Format   = rawFormat; 
		}
		else {
			/* encrypt rawBlob using caller's context, then encode to
			 * WrappedKey.KeyData */
			uint32 bytesEncrypted;
			EncryptData(CCHandle,
				Context,
				&rawBlob,			// ClearBufs[]
				1,					// ClearBufCount
				&encryptedBlob,		// CipherBufs[],
				1,					// CipherBufCount,
				bytesEncrypted,
				remData,
				Privilege);
	
			// I'm not 100% sure about this....
			assert(remData.Length == 0);
			encryptedBlob.Length = bytesEncrypted;
			WrappedKey.KeyData = encryptedBlob;
			wrappedHdr.BlobType = CSSM_KEYBLOB_WRAPPED;
			// OK to be zero or not present 
			wrappedHdr.WrapMode = Context.getInt(
				CSSM_ATTRIBUTE_MODE);
		}
	}
	catch (...) {
		errorLog0("WrapKey: EncryptData() threw exception\n");
		if(allocdRawBlob) {
			freeCssmData(rawBlob, privAllocator);
		}
		freeCssmData(remData,normAllocator);
		throw;
	}
	if(allocdRawBlob) {
		freeCssmData(rawBlob, privAllocator);
	}
	freeCssmData(remData, normAllocator);
}

/*
 * Unwrap key function. Used for:
 *
 * -- Given key of BlobType CSSM_KEYBLOB_WRAPPED, decode and decrypt
 *    it, yielding a key in either raw or reference format. Unwrapping
 *    key may be either raw or reference. The context must match
 *    the unwrapping key (ALGCLASS_SYMMETRIC  or ALGCLASS_ASYMMETRIC).
 *
 *	  Private keys are assumed to be PKCS8 encoded; session keys  
 *    are assumed to be PKCS7 encoded. 
 *
 * -- Convert a Raw key to a reference key (with no decrypting).
 *    This is called a NULL unwrap; no unwrapping key need be present in
 *    the context, but the context must be of class 
 *    ALGCLASS_SYMMETRIC and algorithm ALGID_NONE.
 */ 
void AppleCSPSession::UnwrapKey(
		CSSM_CC_HANDLE CCHandle,
		const Context &Context,
		const CssmKey *PublicKey,
		const CssmKey &WrappedKey,
		uint32 KeyUsage,
		uint32 KeyAttr,
		const CssmData *KeyLabel,
		const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
		CssmKey &UnwrappedKey,
		CssmData &DescriptiveData,
		CSSM_PRIVILEGE Privilege)
{
	bool 					isNullUnwrap = false;
	CssmKey					*unwrappingKey = NULL;
	cspKeyType				keyType;				// CKT_Public, etc. 
	CSSM_KEYBLOB_FORMAT		wrapFormat = WrappedKey.blobFormat();
	
	/* obtain unwrapping key if present */
	unwrappingKey = Context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
	if(unwrappingKey == NULL) {
		if((Context.algorithm() == CSSM_ALGID_NONE) &&
		   (Context.type() == CSSM_ALGCLASS_SYMMETRIC)) {
				// NULL unwrap, OK
				isNullUnwrap = true;
		}
		else {
			errorLog0("UnwrapKey: missing wrapping key\n");
			CssmError::throwMe(CSSMERR_CSP_MISSING_ATTR_KEY);
		}
	}

	/* 
	 * validate unwrappingKey 
	 */
	if(!isNullUnwrap) {
		/* make sure unwrapping key type matches context */
		CSSM_CONTEXT_TYPE unwrapType;
		switch(unwrappingKey->KeyHeader.KeyClass) {
			case CSSM_KEYCLASS_PUBLIC_KEY:
			case CSSM_KEYCLASS_PRIVATE_KEY:
				unwrapType = CSSM_ALGCLASS_ASYMMETRIC;
				break;
			case CSSM_KEYCLASS_SESSION_KEY:
				unwrapType = CSSM_ALGCLASS_SYMMETRIC;
				break;
			default:
				errorLog0("UnwrapKey: bad class of wrappingKey\n");
				CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_KEY);
		}
		if(unwrapType != Context.type()) {
			errorLog0("UnwrapKey: mismatch unwrappingKey/contextType\n");
			CssmError::throwMe(CSSMERR_CSP_INVALID_CONTEXT);
		}
		if(Context.algorithm() == CSSM_ALGID_NONE) {
			errorLog0("UnwrapKey: null wrap alg, non-null key\n");
			CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
		}
		cspValidateIntendedKeyUsage(&unwrappingKey->KeyHeader, CSSM_KEYUSE_UNWRAP);
		cspVerifyKeyTimes(unwrappingKey->KeyHeader);
	}

	/* validate WrappedKey */
	switch(WrappedKey.keyClass()) {
		case CSSM_KEYCLASS_PUBLIC_KEY:
			#if 	!ALLOW_PUB_KEY_WRAP
			if(!isNullUnwrap) {
				errorLog0("UnwrapKey: unwrap of public key illegal\n");
				CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
			}
			#endif	/* ALLOW_PUB_KEY_WRAP */
			keyType = CKT_Public;
			break;
		case CSSM_KEYCLASS_PRIVATE_KEY:
			keyType = CKT_Private;
			break;
		case CSSM_KEYCLASS_SESSION_KEY:
			keyType = CKT_Session;
			break;
		default:
			CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
	}
	if(isNullUnwrap) {
		if(WrappedKey.blobType() != CSSM_KEYBLOB_RAW) {
			errorLog0("UnwrapKey: expected raw blobType\n");
			CssmError::throwMe(CSSMERR_CSP_KEY_BLOB_TYPE_INCORRECT);
		}
	}
	else {
		if(WrappedKey.blobType() != CSSM_KEYBLOB_WRAPPED) {
			errorLog0("UnwrapKey: expected wrapped blobType\n");
			CssmError::throwMe(CSSMERR_CSP_KEY_BLOB_TYPE_INCORRECT);
		}
	}

	/* validate requested storage and usage */
	cspKeyStorage keyStorage = cspParseKeyAttr(keyType, KeyAttr);
	switch(keyStorage) {
		case CKS_Ref:
		case CKS_Data:
			break;		// OK
		default:
			CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK);
	}
	cspValidateKeyUsageBits(keyType,  KeyUsage);

	/* prepare outgoing header */
	CssmKey::Header &unwrappedHdr = UnwrappedKey.header();
	const CssmKey::Header &wrappedHdr   = WrappedKey.header();
	setKeyHeader(unwrappedHdr,
		plugin.myGuid(),
		wrappedHdr.algorithm(),		// same as incoming 
		wrappedHdr.keyClass(),		// same as incoming
		KeyAttr & ~KEY_ATTR_RETURN_MASK,
		KeyUsage);
	unwrappedHdr.LogicalKeySizeInBits = wrappedHdr.LogicalKeySizeInBits;
	unwrappedHdr.StartDate = wrappedHdr.StartDate;
	unwrappedHdr.EndDate = wrappedHdr.EndDate;
	UnwrappedKey.KeyData.Data = NULL;	// ignore possible incoming KeyData
	UnwrappedKey.KeyData.Length = 0;
	
	/* validate wrappedKey format */
	if(!isNullUnwrap) {
		switch(wrapFormat) {
			case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7:
				if(WrappedKey.keyClass() != CSSM_KEYCLASS_SESSION_KEY) {
					/* this unwrapping style only for symmetric keys */
					CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
				}
				break;
			case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8:
			case CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL:
				if(WrappedKey.keyClass() != CSSM_KEYCLASS_PRIVATE_KEY) {
					/* these unwrapping styles only for private keys */
					CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
				}
				break;
			case CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM:
				UnwrapKeyCms(CCHandle,
						Context,
						WrappedKey,
						CredAndAclEntry,
						UnwrappedKey,
						DescriptiveData,
						Privilege,
						keyStorage);
				return;
			default:
				CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_WRAPPED_KEY_FORMAT);
		}
	}

	/* Get key blob, decoding and decrypting if necessary */
	CssmData decodedBlob;
	CssmData remData;
	try {
		if(isNullUnwrap) {
			/* simple copy of raw blob */
			copyData(WrappedKey.KeyData, 
				UnwrappedKey.KeyData, 
				normAllocator);
			unwrappedHdr.BlobType = CSSM_KEYBLOB_RAW;
			unwrappedHdr.Format   = wrapFormat; 
		}
		else {
			decodedBlob = CssmData::overlay(WrappedKey.KeyData);
			uint32 bytesDecrypted;		
			CssmData *unwrapData = 	
				CssmData::overlay(&UnwrappedKey.KeyData);
				
			DecryptData(CCHandle,
				Context,
				&decodedBlob,		// CipherBufs[],
				1,					// CipherBufCount,
				unwrapData,			// ClearBufs[]
				1,					// ClearBufCount
				bytesDecrypted,
				remData,
				Privilege);
	
			// I'm not 100% sure about this....
			assert(remData.Length == 0);
			UnwrappedKey.KeyData.Length = bytesDecrypted;
			unwrappedHdr.BlobType = CSSM_KEYBLOB_RAW;
			
			/* 
			 * Figure out various header fields from resulting blob
			 */
			switch(wrapFormat) {
				case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7:
					unwrappedHdr.Format = 
						CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
					if(unwrappedHdr.LogicalKeySizeInBits == 0) {
						unwrappedHdr.LogicalKeySizeInBits =
							bytesDecrypted * 8;
					}
					/* app has to infer/know algorithm */
					break;
				case CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS8:
					pkcs8InferKeyHeader(UnwrappedKey);
					break;
				case CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSL:
					/* 
					 * App told us key algorithm (in WrappedKey).
					 * Infer format and key size. 
					 */
					opensslInferKeyHeader(UnwrappedKey);
					break;
			}
		}
	}
	catch (...) {
		errorLog0("UnwrapKey: DecryptData() threw exception\n");
		freeCssmData(remData, normAllocator);
		throw;
	}
	freeCssmData(remData, normAllocator);

	/* 
	 * One more thing: cook up a BinaryKey if caller wants a 
	 * reference key.
	 */
	if(keyStorage == CKS_Ref) {
		/*
		 * We have a key in raw format; convert to BinaryKey.
		 */
		BinaryKey *binKey = NULL;
		CSPKeyInfoProvider *provider = infoProvider(UnwrappedKey);
		/* optional parameter-bearing key */
		CssmKey *paramKey = Context.get<CssmKey>(CSSM_ATTRIBUTE_PARAM_KEY);
		provider->CssmKeyToBinary(paramKey, UnwrappedKey.KeyHeader.KeyAttr, &binKey);
		addRefKey(*binKey, UnwrappedKey);
		delete provider;
	}
}