
/*
 * SecImportExportCrypto.cpp - low-level crypto routines for wrapping and unwrapping
 *							   keys.
 */

#include "SecImportExport.h"
#include "SecImportExportCrypto.h"
#include "SecImportExportUtils.h"
#include "Keychains.h"
#include "Access.h"
#include "Item.h"
#include "SecKeyPriv.h"
#include "KCEventNotifier.h"
#include <security_cdsa_utilities/cssmacl.h>
#include <security_cdsa_utilities/KeySchema.h>
#include <security_cdsa_utilities/cssmdata.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_utilities/devrandom.h>
#include <security_cdsa_client/securestorage.h>
#include <security_cdsa_client/dlclient.h>
#include <Security/cssmapi.h>

/*
 * Key attrribute names and values.
 *
 * This is where the public key hash goes.
 */
#define SEC_KEY_HASH_ATTR_NAME			"Label"

/*
 * This is where the publicly visible name goes.
 */
#define SEC_KEY_PRINT_NAME_ATTR_NAME	"PrintName"

/*
 * Default values we ultimately assign to the PrintName attr.
 */
#define SEC_PRIVKEY_PRINT_NAME_ATTR_VALUE		"Imported Private Key"
#define SEC_PUBKEY_PRINT_NAME_ATTR_VALUE		"Imported Public Key"
#define SEC_SESSIONKEY_PRINT_NAME_ATTR_VALUE	"Imported Key"

/*
 * Set private key's Label and PrintName attributes. On entry Label
 * is typically a random string to faciliate finding the key in a DL;
 * the PrintName is currently set to the same value by the DL. We
 * replace the Label attr with the public key hash and the PrintName
 * attr with a caller-supplied value.
 */
static CSSM_RETURN impExpSetKeyLabel(
	CSSM_CSP_HANDLE 	cspHand,		// where the key lives
	CSSM_DL_DB_HANDLE 	dlDbHand,		// ditto
	SecKeychainRef		kcRef,			// ditto
	CSSM_KEY_PTR		cssmKey,
	const CSSM_DATA		*existKeyLabel,	// existing label, a random string
	const CSSM_DATA		*newPrintName,
	CssmOwnedData		&newLabel,		// RETURNED as what we set
	SecKeyRef			*secKey)		// RETURNED
{
	CSSM_RETURN						crtn;
	CSSM_DATA						keyDigest = {0, NULL};

	crtn = impExpKeyDigest(cspHand, cssmKey, &keyDigest);
	if(crtn) {
		return crtn;
	}

	/* caller needs this for subsequent DL lookup */
	newLabel.copy(keyDigest);

	/* Find this key as a SecKeychainItem */
	SecItemClass itemClass;
	switch (cssmKey->KeyHeader.KeyClass) {
		case CSSM_KEYCLASS_PRIVATE_KEY:
			itemClass = kSecPrivateKeyItemClass;
			break;
		case CSSM_KEYCLASS_PUBLIC_KEY:
			itemClass = kSecPublicKeyItemClass;
			break;
		case CSSM_KEYCLASS_SESSION_KEY:
			itemClass = kSecSymmetricKeyItemClass;
			break;
		default:
			itemClass = (SecItemClass) 0;
	}
	SecKeychainAttribute kcAttr = {kSecKeyLabel, (UInt32)existKeyLabel->Length, existKeyLabel->Data};
	SecKeychainAttributeList kcAttrList = {1, &kcAttr};
	SecKeychainSearchRef srchRef = NULL;
	OSStatus ortn;
	SecKeychainItemRef itemRef = NULL;

	ortn = SecKeychainSearchCreateFromAttributes(kcRef, itemClass,
		&kcAttrList, &srchRef);
	if(ortn) {
		SecImpExpDbg("SecKeychainSearchCreateFromAttributes error");
		crtn = ortn;
		goto errOut;
	}
	ortn = SecKeychainSearchCopyNext(srchRef, &itemRef);
	if(ortn) {
		SecImpExpDbg("SecKeychainSearchCopyNext error");
		crtn = ortn;
		goto errOut;
	}
	#ifndef	NDEBUG
	ortn = SecKeychainSearchCopyNext(srchRef, &itemRef);
	if(ortn == errSecSuccess) {
		SecImpExpDbg("impExpSetKeyLabel: found second key with same label!");
		crtn = errSecInternalComponent;
		goto errOut;
	}
	#endif	/* NDEBUG */

	/* modify two attributes... */
	SecKeychainAttribute modAttrs[2];
	modAttrs[0].tag    = kSecKeyLabel;
	modAttrs[0].length = (UInt32)keyDigest.Length;
	modAttrs[0].data   = keyDigest.Data;
	modAttrs[1].tag    = kSecKeyPrintName;
	modAttrs[1].length = (UInt32)newPrintName->Length;
	modAttrs[1].data   = newPrintName->Data;
	kcAttrList.count = 2;
	kcAttrList.attr = modAttrs;
	ortn = SecKeychainItemModifyAttributesAndData(itemRef, &kcAttrList,
		0, NULL);
	if(ortn) {
		SecImpExpDbg("SecKeychainItemModifyAttributesAndData error");
		crtn = ortn;
		goto errOut;
	}
	*secKey = (SecKeyRef)itemRef;
errOut:
	if(keyDigest.Data)  {
		/* mallocd by CSP */
		impExpFreeCssmMemory(cspHand, keyDigest.Data);
	}
	if(srchRef) {
		CFRelease(srchRef);
	}
	return crtn;
}

/*
 * Import a raw key. This can be used as a lightweight "guess" evaluator
 * if a handle to the raw CSP is passed in (with no keychain), or as
 * the real thing which does full keychain import.
 */
OSStatus impExpImportRawKey(
	CFDataRef						inData,
	SecExternalFormat				externForm,
	SecExternalItemType				itemType,
	CSSM_ALGORITHMS					keyAlg,
	SecKeychainRef					importKeychain, // optional
	CSSM_CSP_HANDLE					cspHand,		// required
	SecItemImportExportFlags		flags,
	const SecKeyImportExportParameters	*keyParams,		// optional
	const char						*printName,		// optional
	CFMutableArrayRef				outArray)		// optional, append here
{
	CSSM_RETURN			crtn;
	CSSM_KEY			wrappedKey;
	CSSM_KEYHEADER		&hdr = wrappedKey.KeyHeader;
	CSSM_CSP_HANDLE		rawCspHand = 0;
	CSSM_KEY_SIZE		keySize;
	CSSM_KEYBLOB_FORMAT format;
	CSSM_KEYCLASS		keyClass;

	/* First convert external format and types to CSSM style. */
	crtn = impExpKeyForm(externForm, itemType, keyAlg, &format, &keyClass);

	/* cook up key to be null-unwrapped */
	memset(&wrappedKey, 0, sizeof(CSSM_KEY));
	wrappedKey.KeyData.Length = CFDataGetLength(inData);
	wrappedKey.KeyData.Data   = (uint8 *)CFDataGetBytePtr(inData);

	hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
	/* CspId don't care */
	hdr.BlobType = CSSM_KEYBLOB_RAW;
	hdr.Format = format;
	hdr.AlgorithmId = keyAlg;
	hdr.KeyClass = keyClass;
	/* LogicalKeySizeInBits calculated below */
	/* attr and usage are for the incoming unwrapped key... */
	hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
	hdr.KeyUsage = CSSM_KEYUSE_ANY;

	/*
	 * Get key size in bits from raw CSP. Doing this right now is a good
	 * optimization for the "guessing" case; getting the key size from the
	 * raw CSP involves a full decode on an alg- and format-specific manner.
	 * If we've been given the wrong params, we'll fail right here without
	 * the complication of a full UnwrapKey op.
	 */
	rawCspHand = cuCspStartup(CSSM_TRUE);
	if(rawCspHand == 0) {
		return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
	}
	crtn = CSSM_QueryKeySizeInBits(rawCspHand, CSSM_INVALID_HANDLE, &wrappedKey, &keySize);
	cuCspDetachUnload(rawCspHand, CSSM_TRUE);
	if(crtn) {
		SecImpExpDbg("CSSM_QueryKeySizeInBits error");
		return crtn;
	}
	hdr.LogicalKeySizeInBits = keySize.LogicalKeySizeInBits;

	impExpKeyUnwrapParams unwrapParams;
	memset(&unwrapParams, 0, sizeof(unwrapParams));
	unwrapParams.encrAlg		= CSSM_ALGID_NONE;
	unwrapParams.encrMode		= CSSM_ALGMODE_NONE;
	unwrapParams.unwrappingKey  = NULL;
	unwrapParams.encrPad		= CSSM_PADDING_NONE;

	return impExpImportKeyCommon(
		&wrappedKey,
		importKeychain,
		cspHand,
		flags,
		keyParams,
		&unwrapParams,
		printName,
		outArray);
}

using namespace KeychainCore;

/*
 * Post notification of a "new key added" event.
 * If you know of another way to do this, other than a dlclient-based lookup of the
 * existing key in order to get a KeychainCore::Item, by all means have at it.
 */
OSStatus impExpKeyNotify(
	SecKeychainRef	importKeychain,
	const CssmData	&keyLabel,		// stored with this, we use it to do a lookup
	const CSSM_KEY	&cssmKey)		// unwrapped key in CSSM format
{
	/*
	 * Look up key in the DLDB by label, key class, algorithm, and key size.
	 */
	CSSM_DB_RECORDTYPE recordType;
	const CSSM_KEYHEADER &hdr = cssmKey.KeyHeader;

	switch(hdr.KeyClass) {
		case CSSM_KEYCLASS_PUBLIC_KEY:
			recordType = CSSM_DL_DB_RECORD_PUBLIC_KEY;
			break;
		case CSSM_KEYCLASS_PRIVATE_KEY:
			recordType = CSSM_DL_DB_RECORD_PRIVATE_KEY;
			break;
		case CSSM_KEYCLASS_SESSION_KEY:
			recordType = CSSM_DL_DB_RECORD_SYMMETRIC_KEY;
			break;
		default:
			return errSecParam;
	}
	assert(importKeychain != NULL);
	Keychain keychain = KeychainImpl::required(importKeychain);

	SSDbImpl* impl = dynamic_cast<CssmClient::SSDbImpl *>(&(*keychain->database()));
	if (impl == NULL) // did we go bad?
	{
		CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
	}

	CssmClient::SSDb ssDb(impl);

	CssmClient::DbAttributes dbAttributes;
	CssmClient::DbUniqueRecord uniqueId;
	CssmClient::SSDbCursor dbCursor(ssDb, 3);		// three attributes
	dbCursor->recordType(recordType);
	dbCursor->add(CSSM_DB_EQUAL, KeySchema::Label, keyLabel);
	dbCursor->add(CSSM_DB_EQUAL, KeySchema::KeyType, hdr.AlgorithmId);
	dbCursor->add(CSSM_DB_EQUAL, KeySchema::KeySizeInBits, hdr.LogicalKeySizeInBits);
	CssmClient::Key key;
	if (!dbCursor->nextKey(&dbAttributes, key, uniqueId)) {
		SecImpExpDbg("impExpKeyNotify: key not found");
		return errSecItemNotFound;
	}

	/*
	 * Get a Keychain-style Item, post notification.
	 */
	Item keyItem = keychain->item(recordType, uniqueId);
	keychain->postEvent(kSecAddEvent, keyItem);

	return errSecSuccess;
}

/*
 * Size of random label string in ASCII chars to facilitate DL lookup.
 */
#define SEC_RANDOM_LABEL_LEN		16

#define SEC_KEYATTR_RETURN_MASK		\
	(CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_RETURN_NONE)

/*
 * Common code to unwrap a key, used for raw keys (which do a NULL unwrap) and
 * wrapped keys.
 */
OSStatus impExpImportKeyCommon(
	const CSSM_KEY					*wrappedKey,
	SecKeychainRef					importKeychain, // optional
	CSSM_CSP_HANDLE					cspHand,		// required, if importKeychain is
													// present, must be from there
	SecItemImportExportFlags		flags,
	const SecKeyImportExportParameters	*keyParams, // optional
	const impExpKeyUnwrapParams		*unwrapParams,
	const char						*printName,		// optional
	CFMutableArrayRef				outArray)		// optional, append here
{
	CSSM_CC_HANDLE		ccHand = 0;
	CSSM_RETURN			crtn;
	CSSM_DATA			labelData;
	CSSM_KEY			unwrappedKey;
	CSSM_DL_DB_HANDLE   dlDbHandle;
	CSSM_DL_DB_HANDLE   *dlDbPtr = NULL;
	OSStatus			ortn;
	CSSM_ACCESS_CREDENTIALS	nullCreds;
	uint8				randLabel[SEC_RANDOM_LABEL_LEN + 1];
	CSSM_KEYUSE			keyUsage = 0;			// default
	CSSM_KEYATTR_FLAGS  keyAttributes = 0;		// default
	const CSSM_KEYHEADER &hdr = wrappedKey->KeyHeader;
	CSSM_DATA			descrData = {0, NULL};
	ResourceControlContext rcc;
	Security::KeychainCore::Access::Maker maker;
	ResourceControlContext *rccPtr = NULL;
	SecAccessRef		accessRef = keyParams ? keyParams->accessRef : NULL;
	CssmAutoData		keyLabel(Allocator::standard());
	SecKeyRef			secKeyRef = NULL;
	bool				usedSecKeyCreate = false;

	assert(unwrapParams != NULL);
	assert(cspHand != 0);

	if(importKeychain) {
		ortn = SecKeychainGetDLDBHandle(importKeychain, &dlDbHandle);
		if(ortn) {
			return ortn;
		}
		dlDbPtr = &dlDbHandle;
	}

	memset(&unwrappedKey, 0, sizeof(CSSM_KEY));
	memset(&nullCreds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));

	/* context for unwrap */
	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
			unwrapParams->encrAlg,
			unwrapParams->encrMode,
			&nullCreds,
			unwrapParams->unwrappingKey,
			unwrapParams->iv.Data ? &unwrapParams->iv : NULL,
			unwrapParams->encrPad,
			0,				// Params
			&ccHand);
	if(crtn) {
		goto errOut;
	}
	if(dlDbPtr) {
		/* Importing to a keychain - add DLDB to context */
		crtn = impExpAddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_DL_DB_HANDLE,
			sizeof(CSSM_ATTRIBUTE_DL_DB_HANDLE),
			dlDbPtr);
		if(crtn) {
			SecImpExpDbg("impExpImportKeyCommon: CSSM_UpdateContextAttributes error");
			goto errOut;
		}
	}

	if((hdr.KeyClass != CSSM_KEYCLASS_SESSION_KEY) && (dlDbPtr != NULL)) {
		/* Generate random 16-char label to facilitate DL lookup */
		char *randAscii = (char *)randLabel;
		uint8 randBinary[SEC_RANDOM_LABEL_LEN / 2];
		unsigned randBinaryLen = SEC_RANDOM_LABEL_LEN / 2;
		DevRandomGenerator rng;

		rng.random(randBinary, randBinaryLen);
		for(unsigned i=0; i<randBinaryLen; i++) {
			sprintf(randAscii, "%02X", randBinary[i]);
			randAscii += 2;
		}
		labelData.Data = randLabel;
		labelData.Length = SEC_RANDOM_LABEL_LEN;
		/* actual keyLabel value set later */
	}
	else {
		labelData.Data = (uint8 *)SEC_SESSIONKEY_PRINT_NAME_ATTR_VALUE;
		labelData.Length = strlen(SEC_SESSIONKEY_PRINT_NAME_ATTR_VALUE);
		keyLabel.copy(labelData);
	}

	/*
	 * key attr flags and usage. First the defaults.
	 */
	if(keyParams) {
		keyUsage = keyParams->keyUsage;
		keyAttributes = keyParams->keyAttributes;
	}
	if(keyUsage == 0) {
		/* default */
		keyUsage = CSSM_KEYUSE_ANY;
	}
	if(keyAttributes == 0) {
		/* default */
		keyAttributes = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE;
		if(dlDbPtr) {
			keyAttributes |= CSSM_KEYATTR_PERMANENT;
		}
		if(hdr.KeyClass == CSSM_KEYCLASS_PRIVATE_KEY) {
			keyAttributes |= (CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_EXTRACTABLE);
		}
	}
	else {
		/* caller-supplied; ensure we're generating a reference key */
		keyAttributes &= ~SEC_KEYATTR_RETURN_MASK;
		keyAttributes |= CSSM_KEYATTR_RETURN_REF;
	}

	if( (dlDbPtr != NULL) &&							// not permanent, no ACL
	    (hdr.KeyClass == CSSM_KEYCLASS_PRIVATE_KEY) &&	// ACLs only for private key
		( (keyParams == NULL) ||						// NULL --> default ACL
		  !(keyParams->flags & kSecKeyNoAccessControl)  // explicity request no ACL
		)
	  ) {
		/*
		 * Prepare to set up either a default ACL or one provided by caller via
		 * keyParams->accessRef.
		 */
		memset(&rcc, 0, sizeof(rcc));
		maker.initialOwner(rcc);
		rccPtr = &rcc;
	}

	/*
	 * Additional optional parameters: block size, rounds,
	 * effectiveKeySize.
	 * WARNING: block size and rounds, used for RC5, have not been tested.
	 * OpenSSL, as of Panther ship, did not support RC5 encryption.
	 */
	if(unwrapParams->effectiveKeySizeInBits != 0) {
		assert(unwrapParams->unwrappingKey->KeyHeader.AlgorithmId ==
			CSSM_ALGID_RC2);
		SecImpExpDbg("impExpImportKeyCommon: setting effectiveKeySizeInBits to %lu",
			(unsigned long)unwrapParams->effectiveKeySizeInBits);
		crtn = impExpAddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_EFFECTIVE_BITS,
			sizeof(uint32),
			(void *)((size_t) unwrapParams->effectiveKeySizeInBits));
		if(crtn) {
			SecImpExpDbg("impExpImportKeyCommon: CSSM_UpdateContextAttributes error");
			goto errOut;
		}
	}

	if(unwrapParams->rounds != 0) {
		assert(unwrapParams->unwrappingKey->KeyHeader.AlgorithmId ==
			CSSM_ALGID_RC5);
		SecImpExpDbg("impExpImportKeyCommon: setting rounds to %lu",
			(unsigned long)unwrapParams->rounds);
		crtn = impExpAddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_ROUNDS,
			sizeof(uint32),
			(void *)((size_t)unwrapParams->rounds));
		if(crtn) {
			SecImpExpDbg("impExpImportKeyCommon: CSSM_UpdateContextAttributes error");
			goto errOut;
		}
	}

	if(unwrapParams->blockSizeInBits != 0) {
		/* Our RC5 implementation has a fixed block size */
		if(unwrapParams->blockSizeInBits != 64) {
			SecImpExpDbg("WARNING impExpImportKeyCommon: setting block size to %lu",
				(unsigned long)unwrapParams->blockSizeInBits);
			/*
			 * With the current CSP this will actually be ignored
			 */
			crtn = impExpAddContextAttribute(ccHand,
				CSSM_ATTRIBUTE_BLOCK_SIZE,
				sizeof(uint32),
				(void *)((size_t)unwrapParams->blockSizeInBits));
			if(crtn) {
				SecImpExpDbg("impExpImportKeyCommon: CSSM_UpdateContextAttributes error");
				goto errOut;
			}
		}
	}

	/* Here we go */
	crtn = CSSM_UnwrapKey(ccHand,
		NULL,				// public key
		(const CSSM_WRAP_KEY *)wrappedKey,
		keyUsage,
		keyAttributes,
		&labelData,
		rccPtr,				// CredAndAclEntry
		&unwrappedKey,
		&descrData);		// required
	if(crtn != CSSM_OK) {
		SecImpExpDbg("CSSM_UnwrapKey failure");
		if(crtn == CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA) {
			/* report in a keychain-friendly way */
			crtn = errSecDuplicateItem;
		}
		goto errOut;
	}

	/* Private and public keys: update Label as public key hash */
	if((hdr.KeyClass != CSSM_KEYCLASS_SESSION_KEY) && (dlDbPtr != NULL)) {
		CSSM_DATA newPrintName;
		if(printName) {
			/* caller specified */
			newPrintName.Data = (uint8 *)printName;
		}
		else {
			/* use defaults */
			if(hdr.KeyClass == CSSM_KEYCLASS_PRIVATE_KEY) {
				newPrintName.Data = (uint8 *)SEC_PRIVKEY_PRINT_NAME_ATTR_VALUE;
			}
			else {
				newPrintName.Data = (uint8 *)SEC_PUBKEY_PRINT_NAME_ATTR_VALUE;
			}
		}
		newPrintName.Length = strlen((char *)newPrintName.Data);
		#if old_way
		crtn = impExpSetKeyLabel(cspHand, *dlDbPtr, &unwrappedKey,
			&labelData, &newPrintName, keyLabel);
		#else
		crtn = impExpSetKeyLabel(cspHand, *dlDbPtr, importKeychain,
			&unwrappedKey, &labelData, &newPrintName, keyLabel, &secKeyRef);
		#endif
		if(crtn) {
			goto errOut;
		}
	}

	/* Private key: adjust ACL as appropriate */
	if(rccPtr != NULL) {
		SecPointer<KeychainCore::Access> theAccess(accessRef ?
			KeychainCore::Access::required(accessRef) :
			new KeychainCore::Access("Imported Private Key"));
		try {
			CssmClient::KeyAclBearer bearer(cspHand, unwrappedKey, Allocator::standard());
			theAccess->setAccess(bearer, maker);
		}
		catch (const CssmError &e) {
			/* not implemented means we're talking to the raw CSP which does
			 * not implement ACLs */
			if(e.error != CSSMERR_CSP_FUNCTION_NOT_IMPLEMENTED) {
				crtn = e.error;
			}
		}
		catch(...) {
			SecImpExpDbg("keyImport: exception on setAccess\n");
			crtn = errSecAuthFailed;	/* ??? */
		}
	}

	/*
	 * If importKeychain is non-NULL we've already added the key to the keychain.
	 * If importKeychain is NULL, and outArray is non-NULL, we have to use the
	 * half-baked SecKeyCreateWithCSSMKey to give the caller *something*.
	 */
	if(outArray) {
		if(secKeyRef == NULL) {
			assert(importKeychain == NULL);
			ortn = SecKeyCreateWithCSSMKey(&unwrappedKey, &secKeyRef);
			if(ortn) {
				SecImpExpDbg("SecKeyCreateWithCSSMKey failure");
				crtn = ortn;
				goto errOut;
			}
			/* don't CSSM_FreeKey() this key */
			usedSecKeyCreate = true;
		}
		CFArrayAppendValue(outArray, secKeyRef);
	}

	if(importKeychain) {
		impExpKeyNotify(importKeychain, keyLabel.get(), unwrappedKey);
	}

errOut:
	if(ccHand != 0) {
		CSSM_DeleteContext(ccHand);
	}
	if(secKeyRef) {
		CFRelease(secKeyRef);
	}
	if((unwrappedKey.KeyData.Data != NULL) && !usedSecKeyCreate) {
		/* skip this free if we used SecKeyCreateWithCSSMKey() */
		CSSM_FreeKey(cspHand, NULL, &unwrappedKey, CSSM_FALSE);
	}
	return crtn;
}

/*
 * Common code to wrap a key for export.
 */
CSSM_RETURN impExpExportKeyCommon(
	CSSM_CSP_HANDLE		cspHand,		// for all three keys
	SecKeyRef			secKey,
	CSSM_KEY_PTR		wrappingKey,
	CSSM_KEY_PTR		wrappedKey,		// RETURNED
	CSSM_ALGORITHMS		wrapAlg,
	CSSM_ENCRYPT_MODE   wrapMode,
	CSSM_PADDING		wrapPad,
	CSSM_KEYBLOB_FORMAT	wrapFormat,		// NONE, PKCS7, PKCS8, OPENSSL
	CSSM_ATTRIBUTE_TYPE blobAttrType,	// optional raw key format attr
	CSSM_KEYBLOB_FORMAT blobForm,		// ditto
	const CSSM_DATA		*descData,		// optional descriptive data
	const CSSM_DATA		*iv)
{
	OSStatus ortn;
	CSSM_RETURN crtn;

	const CSSM_KEY *unwrappedKey;
	ortn = SecKeyGetCSSMKey(secKey, &unwrappedKey);
	if(ortn) {
		SecImpExpDbg("impExpExportKeyCommon SecKeyGetCSSMKey error");
		return ortn;
	}
	else if(!(unwrappedKey->KeyHeader.KeyAttr & CSSM_KEYATTR_EXTRACTABLE)) {
		SecImpExpDbg("impExpExportKeyCommon: CSSM key is non-extractable");
		return errSecDataNotAvailable;
	}

	/*
	 * Creds are needed for wrapping private and session keys.
	 */
	CSSM_ACCESS_CREDENTIALS	nullCreds;
	memset(&nullCreds, 0, sizeof(nullCreds));
	const CSSM_ACCESS_CREDENTIALS *creds = &nullCreds;		// default

	CSSM_KEYCLASS keyClass = unwrappedKey->KeyHeader.KeyClass;
	if(keyClass == CSSM_KEYCLASS_PRIVATE_KEY || keyClass == CSSM_KEYCLASS_SESSION_KEY) {
		ortn = SecKeyGetCredentials(secKey,
			CSSM_ACL_AUTHORIZATION_DECRYPT,
			kSecCredentialTypeDefault,
			&creds);
		if(ortn) {
			SecImpExpDbg("impExpExportKeyCommon SecKeyGetCredentials error");
			return ortn;
		}
	}

	CSSM_CC_HANDLE ccHand;
	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
		wrapAlg,
		wrapMode,
		&nullCreds,			// creds for wrapping key, never a private key here
		wrappingKey,
		iv,
		wrapPad,
		0,					// Params
		&ccHand);
	if(ortn) {
		SecImpExpDbg("impExpExportKeyCommon CSSM_CSP_CreateSymmetricContext error");
		return crtn;
	}

	/* a couple of optional caller-specified attributes */
	if(wrapFormat != CSSM_KEYBLOB_WRAPPED_FORMAT_NONE) {
		crtn = impExpAddContextAttribute(ccHand,
			CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT,
			sizeof(uint32),
			(void *)((size_t)wrapFormat));
		if(crtn) {
			SecImpExpDbg("impExpExportKeyCommon AddContextAttribute error (1)");
			CSSM_DeleteContext(ccHand);
			return crtn;
		}
	}

	if(blobAttrType != CSSM_ATTRIBUTE_NONE) {
		crtn = impExpAddContextAttribute(ccHand,
			blobAttrType,
			sizeof(uint32),
			(void *)((size_t)blobForm));
		if(crtn) {
			SecImpExpDbg("impExpExportKeyCommon AddContextAttribute error");
			return crtn;
		}
	}

	CSSM_DATA dData = {0, 0};
	if(descData) {
		dData = *descData;
	}

	crtn = CSSM_WrapKey(ccHand,
		creds,
		unwrappedKey,
		&dData,
		wrappedKey);
	CSSM_DeleteContext(ccHand);
	switch(crtn) {
		case CSSM_OK:
			break;
		case CSSMERR_CSP_INVALID_KEYATTR_MASK:
		{
			/*
			 * This is what comes back when we try to wrap an unextractable
			 * key, or when we null wrap a sensitive key. Give the caller
			 * some useful info.
			 */
			CSSM_KEYATTR_FLAGS attr = unwrappedKey->KeyHeader.KeyAttr;
			if(!(attr & CSSM_KEYATTR_EXTRACTABLE)) {
				SecImpExpDbg("impExpExportKeyCommon !EXTRACTABLE");
				return errSecDataNotAvailable;
			}
			if((attr & CSSM_KEYATTR_SENSITIVE) && (wrappingKey == NULL)) {
				SecImpExpDbg("impExpExportKeyCommon !SENSITIVE, NULL wrap");
				return errSecPassphraseRequired;
			}

		}
		default:
			SecImpExpDbg("impExpExportKeyCommon CSSM_WrapKey error");
	}
	return crtn;
}
