/****************************************************************************
*																			*
*					cryptlib PKCS #15 Get Public/Private Key				*
*						Copyright Peter Gutmann 1996-2025					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "crypt.h"
  #include "asn1.h"
  #include "asn1_ext.h"
  #include "keyset.h"
  #include "pkcs15.h"
#else
  #include "crypt.h"
  #include "enc_dec/asn1.h"
  #include "enc_dec/asn1_ext.h"
  #include "keyset/keyset.h"
  #include "keyset/pkcs15.h"
#endif /* Compiler-specific includes */

#ifdef USE_PKCS15

/* OID information used to read a PKCS #15 keyset */

static const OID_INFO dataOIDinfo[] = {
	{ OID_CMS_DATA, CRYPT_OK },
	{ NULL, 0 }, { NULL, 0 }
	};

/****************************************************************************
*																			*
*								Utility Functions							*
*																			*
****************************************************************************/

/* Translate the PKCS #15 usage flags into cryptlib permitted actions.  The
   PKCS #11 use of the 'derive' flag to mean 'allow key agreement' is a bit
   of a kludge, we map it to allowing key-agreement export and import if 
   it's a key-agreement algorithm, if there are further constraints then 
   they'll be handled by the attached certificate.  The PKCS #15 
   nonRepudiation flag doesn't have any definition so we can't do anything 
   with it, although we may need to translate it to allowing signing and/or 
   verification if implementations appear that expect it to be used this 
   way */

CHECK_RETVAL STDC_NONNULL_ARG( ( 3 ) ) \
static int getPermittedActions( IN_FLAGS( PKCS15_USAGE ) const int usageFlags,
								IN_ALGO const CRYPT_ALGO_TYPE cryptAlgo,
								OUT_FLAGS_Z( ACTION_PERM ) int *usage )
	{
	int actionFlags = ACTION_PERM_NONE_ALL;

	REQUIRES( isFlagRangeZ( usageFlags, PKCS15_USAGE ) );
	REQUIRES( isPkcAlgo( cryptAlgo ) );

	/* Clear return value */
	*usage = ACTION_PERM_NONE;

	if( usageFlags & ( PKCS15_USAGE_ENCRYPT | PKCS15_USAGE_WRAP ) )
		actionFlags |= MK_ACTION_PERM( MESSAGE_CTX_ENCRYPT, ACTION_PERM_ALL );
	if( usageFlags & ( PKCS15_USAGE_DECRYPT | PKCS15_USAGE_UNWRAP ) )
		actionFlags |= MK_ACTION_PERM( MESSAGE_CTX_DECRYPT, ACTION_PERM_ALL );
	if( usageFlags & PKCS15_USAGE_SIGN )
		actionFlags |= MK_ACTION_PERM( MESSAGE_CTX_SIGN, ACTION_PERM_ALL );
	if( usageFlags & PKCS15_USAGE_VERIFY )
		actionFlags |= MK_ACTION_PERM( MESSAGE_CTX_SIGCHECK, ACTION_PERM_ALL );
	if( isKeyexAlgo( cryptAlgo ) && ( usageFlags & PKCS15_USAGE_DERIVE ) )
		actionFlags |= MK_ACTION_PERM( MESSAGE_CTX_ENCRYPT, ACTION_PERM_ALL ) | \
					   MK_ACTION_PERM( MESSAGE_CTX_DECRYPT, ACTION_PERM_ALL );
	if( cryptAlgo == CRYPT_ALGO_RSA )
		{
		/* If there are any restrictions on the key usage then we have to 
		   make it internal-only because of RSA's signature/encryption 
		   duality */
		if( !( ( usageFlags & ( PKCS15_USAGE_ENCRYPT | PKCS15_USAGE_WRAP | \
								PKCS15_USAGE_DECRYPT | PKCS15_USAGE_UNWRAP ) ) && \
			   ( usageFlags & ( PKCS15_USAGE_SIGN | PKCS15_USAGE_VERIFY ) ) ) )
			actionFlags = MK_ACTION_PERM_NONE_EXTERNAL( actionFlags );
		}
	else
		{
		/* Because of the special-case data formatting requirements for DLP
		   algorithms we make the usage internal-only */
		actionFlags = MK_ACTION_PERM_NONE_EXTERNAL( actionFlags );
		}
	if( actionFlags <= ACTION_PERM_NONE_ALL )
		return( CRYPT_ERROR_PERMISSION );
	*usage = actionFlags;

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Read Public Key Components						*
*																			*
****************************************************************************/

/* Read public-key components from a PKCS #15 object entry */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 4, 8, 9, 10, 11, 12 ) ) \
int readPublicKeyComponents( IN_PTR const PKCS15_INFO *pkcs15infoPtr,
							 IN_HANDLE const CRYPT_KEYSET iCryptKeysetCallback,
							 IN_KEYID const CRYPT_KEYID_TYPE keyIDtype,
							 IN_BUFFER( keyIDlength ) const void *keyID, 
							 IN_LENGTH_KEYID const int keyIDlength,
							 IN_BOOL const BOOLEAN publicComponentsOnly,
							 IN_HANDLE const CRYPT_DEVICE iDeviceObject, 
							 OUT_HANDLE_OPT CRYPT_CONTEXT *iCryptContextPtr,
							 OUT_HANDLE_OPT CRYPT_CERTIFICATE *iDataCertPtr,
							 OUT_FLAGS_Z( ACTION_PERM ) int *pubkeyActionFlags, 
							 OUT_FLAGS_Z( ACTION_PERM ) int *privkeyActionFlags, 
							 INOUT_PTR ERROR_INFO *errorInfo )
	{
	CRYPT_CONTEXT iCryptContext;
	CRYPT_CERTIFICATE iDataCert = CRYPT_ERROR;
	STREAM stream;
	int pkcAlgo, status;

	assert( isReadPtr( pkcs15infoPtr, sizeof( PKCS15_INFO ) ) );
	assert( isReadPtrDynamic( keyID, keyIDlength ) );
	assert( isWritePtr( iCryptContextPtr, sizeof( CRYPT_CONTEXT ) ) );
	assert( isWritePtr( iDataCertPtr, sizeof( CRYPT_CERTIFICATE ) ) );
	assert( isWritePtr( pubkeyActionFlags, sizeof( int ) ) );
	assert( isWritePtr( privkeyActionFlags, sizeof( int ) ) );

	REQUIRES( isHandleRangeValid( iCryptKeysetCallback ) );
	REQUIRES( keyIDtype == CRYPT_KEYID_NAME || \
			  keyIDtype == CRYPT_KEYID_URI || \
			  keyIDtype == CRYPT_IKEYID_KEYID || \
			  keyIDtype == CRYPT_IKEYID_PGPKEYID || \
			  keyIDtype == CRYPT_IKEYID_ISSUERID );
	REQUIRES( keyIDlength >= MIN_NAME_LENGTH && \
			  keyIDlength < MAX_ATTRIBUTE_SIZE );
	REQUIRES( isBooleanValue( publicComponentsOnly ) );
#if defined( CONFIG_CRYPTO_HW1 ) || defined( CONFIG_CRYPTO_HW2 )
	REQUIRES( iDeviceObject == SYSTEM_OBJECT_HANDLE || \
			  iDeviceObject == CRYPTO_OBJECT_HANDLE || \
			  isHandleRangeValid( iDeviceObject ) );
#else
	REQUIRES( iDeviceObject == SYSTEM_OBJECT_HANDLE || \
			  isHandleRangeValid( iDeviceObject ) );
#endif /* CONFIG_CRYPTO_HW1 || CONFIG_CRYPTO_HW2 */
	REQUIRES( errorInfo != NULL );

	/* Clear return values */
	*iCryptContextPtr = CRYPT_ERROR;
	*iDataCertPtr = CRYPT_ERROR;
	*pubkeyActionFlags = *privkeyActionFlags = ACTION_PERM_NONE;

	/* If we're creating a public-key context we create the certificate or 
	   PKC context normally, if we're creating a private-key context we 
	   create a data-only certificate (if there's certificate information 
	   present) and a partial PKC context ready to accept the private key 
	   components.  If there's a certificate present then we take all of the 
	   information that we need from the certificate, otherwise we use the 
	   public-key data */
#ifdef USE_CERTIFICATES
	if( pkcs15infoPtr->certData != NULL )
		{
		ERROR_INFO localErrorInfo;

		/* There's a certificate present, import it and reconstruct the
		   public-key information from it if we're creating a partial PKC 
		   context */
		clearErrorInfo( &localErrorInfo );
		status = iCryptImportCertIndirect( &iCryptContext,
								iCryptKeysetCallback, keyIDtype, keyID,
								keyIDlength, publicComponentsOnly ? \
									KEYMGMT_FLAG_NONE : \
									KEYMGMT_FLAG_DATAONLY_CERT,
								&localErrorInfo );
		if( cryptStatusError( status ) )
			{
			retExtErr( status, 
					   ( status, errorInfo, &localErrorInfo, 
						 "Couldn't recreate certificate from PKCS #15 "
						 "certificate data" ) );
			}
		if( !publicComponentsOnly )
			{
			DYNBUF pubKeyDB;

			/* We got the certificate, now create the public part of the 
			   context from the certificate's encoded public-key 
			   components */
			iDataCert = iCryptContext;
			status = dynCreate( &pubKeyDB, iDataCert, 
								CRYPT_IATTRIBUTE_SPKI );
			if( cryptStatusError( status ) )
				{
				krnlSendNotifier( iDataCert, IMESSAGE_DECREFCOUNT );
				return( status );
				}
			sMemConnect( &stream, dynData( pubKeyDB ),
						 dynLength( pubKeyDB ) );
			status = iCryptReadSubjectPublicKey( &stream, &iCryptContext,
												 iDeviceObject, TRUE );
			sMemDisconnect( &stream );
			dynDestroy( &pubKeyDB );
			if( cryptStatusError( status ) )
				{
				krnlSendNotifier( iDataCert, IMESSAGE_DECREFCOUNT );
				retExt( status, 
						( status, errorInfo, 
						  "Couldn't recreate public key from "
						  "certificate" ) );
				}
			}
		}
	else
#endif /* USE_CERTIFICATES */
		{
		const int pubKeyStartOffset = pkcs15infoPtr->pubKeyOffset;
		const int pubKeyTotalSize = pkcs15infoPtr->pubKeyDataSize;

		/* There's no certificate present, create the public-key context
		   directly */
		REQUIRES( !checkOverflowSub( pubKeyTotalSize, pubKeyStartOffset ) );
		REQUIRES( boundsCheck( pubKeyStartOffset, 
							   pubKeyTotalSize - pubKeyStartOffset,
							   pubKeyTotalSize ) );
		sMemConnect( &stream, 
					 ( BYTE * ) pkcs15infoPtr->pubKeyData + pubKeyStartOffset,
					 pubKeyTotalSize - pubKeyStartOffset );
		status = iCryptReadSubjectPublicKey( &stream, &iCryptContext,
											 iDeviceObject, 
											 publicComponentsOnly ? \
												FALSE : TRUE );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			{
			retExt( status, 
					( status, errorInfo, 
					  "Couldn't recreate public key from stored public key "
					  "data" ) );
			}
		}

	/* Get the permitted usage flags for each object type that we'll be
	   instantiating.  If there's a public key present we apply its usage
	   flags to whichever PKC context we create, even if it's done indirectly
	   via the certificate import.  Since the private key can also perform 
	   the actions of the public key we set its action flags to the union of 
	   the two */
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
							  &pkcAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusOK( status ) && pkcs15infoPtr->pubKeyData != NULL )
		{
		status = getPermittedActions( pkcs15infoPtr->pubKeyUsage, pkcAlgo,
									  pubkeyActionFlags );
		}
	if( cryptStatusOK( status ) && !publicComponentsOnly )
		{
		status = getPermittedActions( pkcs15infoPtr->privKeyUsage, pkcAlgo,
									  privkeyActionFlags );
		}
	if( cryptStatusError( status ) )
		{
		krnlSendNotifier( iCryptContext, IMESSAGE_DECREFCOUNT );
		if( iDataCert != CRYPT_ERROR )
			krnlSendNotifier( iDataCert, IMESSAGE_DECREFCOUNT );
		retExt( status, 
				( status, errorInfo, 
				  "Public/private key usage flags don't allow any type of "
				  "key usage" ) );
		}

	/* Return the newly-created objects to the caller */
	*iCryptContextPtr = iCryptContext;
	*iDataCertPtr = iDataCert;

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						Read Private Key Components							*
*																			*
****************************************************************************/

/* Import the session key (either a direct session key or a generic-secret
   key used to derive the decryption and MAC contexts) using the supplied
   password */

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 4, 6, 7 ) ) \
static int importSessionKey( IN_HANDLE const CRYPT_CONTEXT iSessionKey,
							 IN_BUFFER( encryptedKeyDataSize ) \
								const void *encryptedKeyData,
							 IN_LENGTH_SHORT const int encryptedKeyDataSize,
							 IN_BUFFER( passwordLength ) const void *password, 
							 IN_LENGTH_NAME const int passwordLength,
							 const QUERY_INFO *queryInfo,
							 INOUT_PTR ERROR_INFO *errorInfo )
	{
	CRYPT_CONTEXT iKeyWrapContext;
	MESSAGE_CREATEOBJECT_INFO createInfo;
	MESSAGE_DATA msgData;
	int mode, status;

	assert( isReadPtrDynamic( encryptedKeyData, encryptedKeyDataSize ) );
	assert( isReadPtrDynamic( password, passwordLength ) );
	assert( isReadPtr( queryInfo, sizeof( QUERY_INFO ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	REQUIRES( isHandleRangeValid( iSessionKey ) );
	REQUIRES( isShortIntegerRangeMin( encryptedKeyDataSize, 16 ) );
	REQUIRES( passwordLength >= MIN_NAME_LENGTH && \
			  passwordLength < MAX_ATTRIBUTE_SIZE );

	/* Create the context used to import the session key and derive the user
	   password into it */
	setMessageCreateObjectInfo( &createInfo, queryInfo->cryptAlgo );
	status = krnlSendMessage( CRYPTO_OBJECT_HANDLE, 
							  IMESSAGE_DEV_CREATEOBJECT, &createInfo, 
							  OBJECT_TYPE_CONTEXT );
	if( cryptStatusError( status ) )
		return( status );
	iKeyWrapContext = createInfo.cryptHandle;
	mode = queryInfo->cryptMode;	/* int vs.enum */
	status = krnlSendMessage( iKeyWrapContext, IMESSAGE_SETATTRIBUTE, 
							  &mode, CRYPT_CTXINFO_MODE );
	if( cryptStatusOK( status ) && \
		queryInfo->keySetupAlgo != CRYPT_ALGO_NONE )
		{
		const int algorithm = queryInfo->keySetupAlgo;	/* int vs.enum */
		status = krnlSendMessage( iKeyWrapContext, IMESSAGE_SETATTRIBUTE,
								  ( MESSAGE_CAST ) &algorithm, 
								  CRYPT_CTXINFO_KEYING_ALGO );
		}
	if( cryptStatusOK( status ) )
		{
		status = krnlSendMessage( iKeyWrapContext, IMESSAGE_SETATTRIBUTE,
								  ( MESSAGE_CAST ) &queryInfo->keySetupIterations,
								  CRYPT_CTXINFO_KEYING_ITERATIONS );
		}
	if( cryptStatusOK( status ) && queryInfo->keySize > 0 )
		{
		status = krnlSendMessage( iKeyWrapContext, IMESSAGE_SETATTRIBUTE,
								  ( MESSAGE_CAST ) &queryInfo->keySize,
								  CRYPT_CTXINFO_KEYSIZE );
		}
	if( cryptStatusOK( status ) )
		{
		setMessageData( &msgData, ( MESSAGE_CAST ) queryInfo->salt, 
						queryInfo->saltLength );
		status = krnlSendMessage( iKeyWrapContext, IMESSAGE_SETATTRIBUTE_S, 
								  &msgData, CRYPT_CTXINFO_KEYING_SALT );
		}
	if( cryptStatusOK( status ) )
		{
		setMessageData( &msgData, ( MESSAGE_CAST ) password, passwordLength );
		status = krnlSendMessage( iKeyWrapContext, IMESSAGE_SETATTRIBUTE_S, 
								  &msgData, CRYPT_CTXINFO_KEYING_VALUE );
		}
	if( cryptStatusError( status ) )
		{
		krnlSendNotifier( iKeyWrapContext, IMESSAGE_DECREFCOUNT );

		/* If there's an error in the parameters supplied via the query 
		   information (which were stored with the exported key) we'll get 
		   an argument or attribute error when we try to set the attribute 
		   so we translate it into an error code which is appropriate for 
		   the situation */
		status = cryptArgError( status ) ? CRYPT_ERROR_BADDATA : status;
		retExt( status,
				( status, errorInfo,
				  "Couldn't initialise context used to import session "
				  "key" ) );
		}

	/* Import the session key, either an actual session key context for PKCS 
	   #15 v1.1 using direct-protected content or a generic-secret context
	   for PKCS #15 v1.2 using direct-protected-ext content */
	status = iCryptImportKey( encryptedKeyData, encryptedKeyDataSize, 
							  CRYPT_FORMAT_CRYPTLIB, iKeyWrapContext,
							  iSessionKey, NULL, errorInfo );
	krnlSendNotifier( iKeyWrapContext, IMESSAGE_DECREFCOUNT );
	if( cryptStatusError( status ) )
		{
		/* Translate any arg/attribute errors as before */
		return( cryptArgError( status ) ? CRYPT_ERROR_BADDATA : status );
		}

	return( CRYPT_OK );
	}

/* Verify the integrity of the encrypted key components and derive the final 
   decryption context from the generic intermediate context that's used to 
   derive the MAC and final decryption contexts.  This replaces the 
   intermediate context with the final decryption context */

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1, 2, 4, 6, 7 ) ) \
static int verifyEncKey( INOUT_HANDLE CRYPT_CONTEXT *iCryptContext,
						 IN_BUFFER( encryptedContentLength ) \
							const void *encryptedContent, 
						 IN_LENGTH_SHORT const int encryptedContentLength, 
						 IN_BUFFER( macValueLength ) \
							const void *macValue, 
						 IN_LENGTH_HASH const int macValueLength, 
						 const QUERY_INFO *queryInfo,
						 OUT_PTR ERROR_INFO *errorInfo )
	{
	const CRYPT_CONTEXT iOriginalCryptContext = *iCryptContext;
	CRYPT_CONTEXT iDerivedCryptContext, iMacContext;
	MESSAGE_DATA msgData;
	int status;

	assert( isWritePtr( iCryptContext, sizeof( CRYPT_CONTEXT ) ) );
	assert( isReadPtrDynamic( encryptedContent, encryptedContentLength ) );
	assert( isReadPtrDynamic( macValue, macValueLength ) );
	assert( isReadPtr( queryInfo, sizeof( QUERY_INFO ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	REQUIRES( isHandleRangeValid( iOriginalCryptContext ) );
	REQUIRES( isShortIntegerRangeNZ( encryptedContentLength ) );
	REQUIRES( macValueLength >= MIN_HASHSIZE && \
			  macValueLength <= CRYPT_MAX_HASHSIZE );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	/* We're using authenticated encryption so we have to apply an 
	   intermediate step that transforms the generic-secret context into 
	   distinct decryption and MAC contexts.  This also MACs the encryption 
	   metadata that precedes the data payload.
	   
	   Note that 3.4.0, the first version to use AuthEnc from the PKCS #15
	   v1.2 draft, omitted the metadata-MAC step, this version dates from 
	   2010 and is now extinct so we don't try and work around it by running 
	   two MACs, one with and one without the metadata MAC step.  3.4.0 also 
	   used a 128-bit MAC key instead of a 160-bit one for the at the time 
	   hardcoded universal hash SHA-1 due to a specification ambiguity, a 
	   workaround was present from 3.4.1 until 3.4.5 but was removed in 
	   3.4.6, 11 years later */
	status = getGenericSecretParams( iOriginalCryptContext, 
									 &iDerivedCryptContext, &iMacContext, 
									 queryInfo );
	if( cryptStatusError( status ) )
		{
		retExt( status, 
				( status, errorInfo, 
				  "Couldn't recreate encryption and MAC keys needed to "
				  "unwrap the private key" ) );
		}

	/* Verify the integrity of the encrypted private key before trying to 
	   process it */
	status = krnlSendMessage( iMacContext, IMESSAGE_CTX_HASH,
							  ( MESSAGE_CAST ) encryptedContent, 
							  encryptedContentLength );
	if( cryptStatusOK( status ) )
		status = krnlSendMessage( iMacContext, IMESSAGE_CTX_HASH, "", 0 );
	if( cryptStatusOK( status ) )
		{
		setMessageData( &msgData, ( MESSAGE_CAST ) macValue, 
						macValueLength );
		status = krnlSendMessage( iMacContext, IMESSAGE_COMPARE, 
								  &msgData, MESSAGE_COMPARE_HASH );
		if( cryptStatusError( status ) )
			{
			/* A failed MAC check is reported as a CRYPT_ERROR comparison 
			   result so we have to convert it to a more appropriate status 
			   code */
			status = CRYPT_ERROR_SIGNATURE;
			}
		}
	krnlSendNotifier( iMacContext, IMESSAGE_DECREFCOUNT );
	if( cryptStatusError( status ) )
		{
		krnlSendNotifier( iDerivedCryptContext, IMESSAGE_DECREFCOUNT );

		/* We convert any failure status encountered at this point into a 
		   generic signature-check failure, which makes explicit what's 
		   going on */
		retExt( CRYPT_ERROR_SIGNATURE, 
				( CRYPT_ERROR_SIGNATURE, errorInfo, 
				  "Private-key integrity check failed" ) );
		}

	/* Replace the original encryption context with the derived one */
	krnlSendNotifier( iOriginalCryptContext, IMESSAGE_DECREFCOUNT );
	*iCryptContext = iDerivedCryptContext;

	return( CRYPT_OK );
	}

/* Read private-key components from a PKCS #15 object entry */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 6 ) ) \
int readPrivateKeyComponents( IN_PTR const PKCS15_INFO *pkcs15infoPtr,
							  IN_HANDLE const CRYPT_CONTEXT iPrivKeyContext,
							  IN_BUFFER_OPT( passwordLength ) \
									const void *password, 
							  IN_LENGTH_NAME_Z const int passwordLength, 
							  IN_BOOL const BOOLEAN isStorageObject, 
							  INOUT_PTR ERROR_INFO *errorInfo )
	{
	CRYPT_CONTEXT iCryptContext;
	MECHANISM_WRAP_INFO mechanismInfo;
	MESSAGE_DATA msgData;
	QUERY_INFO queryInfo DUMMY_INIT_STRUCT, contentQueryInfo;
	STREAM stream;
	ERROR_INFO localErrorInfo;
	BYTE macValue[ CRYPT_MAX_HASHSIZE + 8 ];
	BOOLEAN isAuthEnc = FALSE;
	const int privKeyStartOffset = pkcs15infoPtr->privKeyOffset;
	const int privKeyTotalSize = pkcs15infoPtr->privKeyDataSize;
	void *encryptedKey, *encryptedContent DUMMY_INIT_PTR;
	CFI_CHECK_TYPE CFI_CHECK_VALUE = CFI_CHECK_INIT;
	int encryptedContentLength DUMMY_INIT, macValueLength DUMMY_INIT;
	int checksum, tag, status;

	assert( isReadPtr( pkcs15infoPtr, sizeof( PKCS15_INFO ) ) );
	assert( ( isStorageObject && \
			  password == NULL && passwordLength == 0 ) || \
			( !isStorageObject && \
			  isReadPtrDynamic( password, passwordLength ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	REQUIRES( isHandleRangeValid( iPrivKeyContext ) );
	REQUIRES( ( isStorageObject && \
				password == NULL && passwordLength == 0 ) || \
			  ( !isStorageObject && \
				passwordLength >= MIN_NAME_LENGTH && \
				passwordLength < MAX_ATTRIBUTE_SIZE ) );
	REQUIRES( isBooleanValue( isStorageObject ) );
	REQUIRES( errorInfo != NULL );

	/* Skip the outer wrapper, version number, and header for the SET OF 
	   EncryptionInfo, and query the exported key information to determine 
	   the parameters required to reconstruct the decryption key */
	REQUIRES( !checkOverflowSub( privKeyTotalSize, privKeyStartOffset ) );
	REQUIRES( boundsCheck( privKeyStartOffset, 
						   privKeyTotalSize - privKeyStartOffset,
						   privKeyTotalSize ) );
	sMemConnect( &stream,
				 ( BYTE * ) pkcs15infoPtr->privKeyData + privKeyStartOffset,
				 privKeyTotalSize - privKeyStartOffset );
	status = tag = peekTag( &stream );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		return( status );
		}
	if( isStorageObject )
		{
		BYTE storageID[ KEYID_SIZE + 8 ];
		int length;

		/* If this is a PKCS #15 storage object then it'll contain only 
		   private-key metadata with the content being merely a reference
		   to external hardware, so we just read the storage object
		   reference and save it to the dummy context */
		if( tag != BER_SEQUENCE )
			{
			sMemDisconnect( &stream );
			retExt( CRYPT_ERROR_BADDATA, 
					( CRYPT_ERROR_BADDATA, errorInfo, 
					  "Expected device storage ID, not item type %02X",
					  tag ) );
			}
		readSequence( &stream, NULL );
		status = readOctetString( &stream, storageID, &length, 
								  KEYID_SIZE, KEYID_SIZE );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			return( status );
		setMessageData( &msgData, storageID, KEYID_SIZE );
		return( krnlSendMessage( iPrivKeyContext, IMESSAGE_SETATTRIBUTE_S,
								 &msgData, CRYPT_IATTRIBUTE_DEVICESTORAGEID ) );
		}
	if( tag == MAKE_CTAG( CTAG_OV_DIRECTPROTECTED_EXT ) )
		isAuthEnc = TRUE;
	else
		{
		if( tag != MAKE_CTAG( CTAG_OV_DIRECTPROTECTED ) )
			{
			sMemDisconnect( &stream );
			retExt( CRYPT_ERROR_NOTAVAIL, 
					( CRYPT_ERROR_NOTAVAIL, errorInfo, 
					  "Unrecognised private-key protection type %02X", 
					  tag ) );
			}
		}
	readConstructed( &stream, NULL, 
					 isAuthEnc ? CTAG_OV_DIRECTPROTECTED_EXT : \
								 CTAG_OV_DIRECTPROTECTED );
	readShortInteger( &stream, NULL );
	status = readSet( &stream, NULL );
	if( cryptStatusOK( status ) )
		status = queryAsn1Object( &stream, &queryInfo, QUERYOBJECT_KEYEX );
	if( cryptStatusOK( status ) && \
		queryInfo.type != CRYPT_OBJECT_ENCRYPTED_KEY )
		status = CRYPT_ERROR_BADDATA;
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		zeroise( &queryInfo, sizeof( QUERY_INFO ) );
		retExt( status, 
				( status, errorInfo, 
				  "Invalid encrypted private key data header" ) );
		}
	status = sMemGetDataBlock( &stream, &encryptedKey, queryInfo.size );
	if( cryptStatusOK( status ) )
		status = readUniversal( &stream );	/* Skip the exported key */
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		zeroise( &queryInfo, sizeof( QUERY_INFO ) );
		return( status );
		}
	CFI_CHECK_UPDATE( "queryAsn1Object" );

	/* Read the header for the encrypted key and make sure that all of the
	   encrypted key data (and the trailing MAC value if we're using 
	   authenticated encryption) is present in the stream */
	status = readCMSencrHeader( &stream, dataOIDinfo, 
				FAILSAFE_ARRAYSIZE( dataOIDinfo, OID_INFO ), NULL,
				&iCryptContext, &contentQueryInfo, isAuthEnc ? \
					READCMS_FLAG_AUTHENC | READCMS_FLAG_DEFINITELENGTH : \
					READCMS_FLAG_DEFINITELENGTH );
	if( cryptStatusOK( status ) )
		{
		encryptedContentLength = contentQueryInfo.size;
		if( !isShortIntegerRangeMin( encryptedContentLength, 
									 MIN_P15_OBJECT_SIZE ) )
			status = CRYPT_ERROR_BADDATA;
		if( cryptStatusOK( status ) )
			{
			status = sMemGetDataBlock( &stream, &encryptedContent, 
									   encryptedContentLength );
			}
		if( cryptStatusOK( status ) )
			{
			status = sSkip( &stream, encryptedContentLength, 
							MAX_INTLENGTH_SHORT );
			}
		}
	if( cryptStatusOK( status ) && isAuthEnc )
		{
		/* If we're using authenticated encryption then the encrypted key 
		   data is followed by a MAC value */
		status = readOctetString( &stream, macValue, &macValueLength, 
								  MIN_HASHSIZE, CRYPT_MAX_HASHSIZE );
		}
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		{
		zeroise( &queryInfo, sizeof( QUERY_INFO ) );
		zeroise( &contentQueryInfo, sizeof( QUERY_INFO ) );
		retExt( status, 
				( status, errorInfo, 
				  "Invalid encrypted private key data" ) );
		}
	ANALYSER_HINT( encryptedContent != NULL );
	checksum = checksumData( encryptedContent, encryptedContentLength );
	CFI_CHECK_UPDATE( "readCMSencrHeader" );

	/* Import the session key using the user password */
	clearErrorInfo( &localErrorInfo );
	status = importSessionKey( iCryptContext, encryptedKey, queryInfo.size, 
							   password, passwordLength, &queryInfo, 
							   &localErrorInfo );
	zeroise( &queryInfo, sizeof( QUERY_INFO ) );
	if( cryptStatusError( status ) )
		{
		krnlSendNotifier( iCryptContext, IMESSAGE_DECREFCOUNT );
		zeroise( &contentQueryInfo, sizeof( QUERY_INFO ) );
		retExtErr( status, 
				   ( status, errorInfo, &localErrorInfo,
					 "Couldn't import the session key used to protect "
					 "the private key" ) );
		}
	CFI_CHECK_UPDATE( "importSessionKey" );

	/* If we're using authenticated encryption then we have to use an 
	   intermediate step that transforms the generic-secret context into 
	   distinct decryption and MAC contexts.  Once we've got the MAC key
	   set up, we use it to MAC the encrypted private key data before
	   continuing */
	if( isAuthEnc )
		{
		status = verifyEncKey( &iCryptContext, encryptedContent, 
							   encryptedContentLength, macValue, 
							   macValueLength, &contentQueryInfo, 
							   errorInfo );
		zeroise( &contentQueryInfo, sizeof( QUERY_INFO ) );
		if( cryptStatusError( status ) )
			return( status );
		}
	else
		zeroise( &contentQueryInfo, sizeof( QUERY_INFO ) );
	CFI_CHECK_UPDATE( "verifyEncKey" );

	/* Import the encrypted key into the PKC context */
	setMechanismWrapInfo( &mechanismInfo, ( MESSAGE_CAST ) encryptedContent,
						  encryptedContentLength, NULL, 0, iPrivKeyContext,
						  iCryptContext );
	status = krnlSendMessage( MECHANISM_OBJECT_HANDLE, IMESSAGE_DEV_IMPORT,
							  &mechanismInfo, 
							  pkcs15infoPtr->isPrivKeyExt ? \
									MECHANISM_PRIVATEKEYWRAP_EXT : \
									MECHANISM_PRIVATEKEYWRAP );
	clearMechanismInfo( &mechanismInfo );
	krnlSendNotifier( iCryptContext, IMESSAGE_DECREFCOUNT );
	if( cryptStatusError( status ) )
		{
		/* We can end up here due to a whole range of possible low-level 
		   problems, to make things easier on the caller we provide a 
		   somewhat more detailed breakdown of possible causes */
		switch( status )
			{
			case CRYPT_ERROR_WRONGKEY:
				retExt( status,
						( status, errorInfo, 
						  "Couldn't unwrap private key, probably due to "
						  "incorrect decryption key being used" ) );

			case CRYPT_ERROR_BADDATA:
				retExt( status,
						( status, errorInfo, 
						  "Private key data corrupted or invalid" ) );

			case CRYPT_ERROR_SIGNATURE:
				retExt( status,
						( status, errorInfo, 
						  "Public/private key data integrity check "
						  "failed" ) );

			case CRYPT_ERROR_INVALID:
				retExt( status,
						( status, errorInfo, 
						  "Private key components failed validity "
						  "check" ) );

			default:
				retExt( status,
						( status, errorInfo, 
						  "Couldn't unwrap/import private key" ) );
			}
		}
	if( checksumData( encryptedContent, 
					  encryptedContentLength ) != checksum )
		{
		/* The encrypted private-key data was corrupted between the MAC 
		   check and when it was decrypted and loaded into the context, we 
		   can't trust the key */
		DEBUG_DIAG(( "Encrypted private-key data memory corruption detected" ));
		return( CRYPT_ERROR_FAILED );
		}
	CFI_CHECK_UPDATE( "IMESSAGE_DEV_IMPORT" );

	ENSURES( CFI_CHECK_SEQUENCE_5( "queryAsn1Object", "readCMSencrHeader", 
								   "importSessionKey", "verifyEncKey", 
								   "IMESSAGE_DEV_IMPORT" ) );

	return( CRYPT_OK );
	}
#endif /* USE_PKCS15 */
