/****************************************************************************
*																			*
*							Key Exchange Routines							*
*						Copyright Peter Gutmann 1993-2019					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "crypt.h"
  #include "asn1.h"
  #include "asn1_ext.h"
  #include "misc_rw.h"
  #include "pgp_rw.h"
  #include "mech.h"
#else
  #include "crypt.h"
  #include "enc_dec/asn1.h"
  #include "enc_dec/asn1_ext.h"
  #include "enc_dec/misc_rw.h"
  #include "enc_dec/pgp_rw.h"
  #include "mechs/mech.h"
#endif /* Compiler-specific includes */

#ifdef USE_INT_CMS

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

/* Try and determine the format of the encrypted data */

CHECK_RETVAL_ENUM( CRYPT_FORMAT ) STDC_NONNULL_ARG( ( 1 ) ) \
static CRYPT_FORMAT_TYPE getFormatType( IN_BUFFER( dataLength ) const void *data, 
										IN_LENGTH_SHORT_MIN( MIN_CRYPT_OBJECTSIZE ) \
											const int dataLength )
	{
	STREAM stream;
	long value;
	int status;

	assert( isReadPtrDynamic( data, dataLength ) );

	REQUIRES_EXT( isShortIntegerRangeMin( dataLength, MIN_CRYPT_OBJECTSIZE ), 
				  CRYPT_FORMAT_NONE );

	sMemConnect( &stream, data, min( 16, dataLength ) );

	/* Figure out what we've got.  PKCS #7/CMS/SMIME keyTrans begins:

		keyTransRecipientInfo ::= SEQUENCE {
			version		INTEGER (0|2),

	   while a kek begins:

		kekRecipientInfo ::= [3] IMPLICIT SEQUENCE {
			version		INTEGER (0),

	   which allows us to determine which type of object we have.  Note that 
	   we use sPeek() rather than peekTag() because we want to continue
	   processing (or at least checking for) PGP data if it's not ASN.1 */
	if( sPeek( &stream ) == BER_SEQUENCE )
		{
		CRYPT_FORMAT_TYPE formatType;

		readSequence( &stream, NULL );
		status = readShortInteger( &stream, &value );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( &stream );
			return( CRYPT_FORMAT_NONE );
			}
		switch( value )
			{
			case KEYTRANS_VERSION:
				formatType = CRYPT_FORMAT_CMS;
				break;

			case KEYTRANS_EX_VERSION:
				formatType = CRYPT_FORMAT_CRYPTLIB;
				break;

			default:
				formatType = CRYPT_FORMAT_NONE;
			}
		sMemDisconnect( &stream );

		return( formatType );
		}
	if( sPeek( &stream ) == MAKE_CTAG( CTAG_RI_PASSWORD ) )
		{
		readConstructed( &stream, NULL, CTAG_RI_PASSWORD );
		status = readShortInteger( &stream, &value );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( &stream );
			return( CRYPT_FORMAT_NONE );
			}
		sMemDisconnect( &stream );

		return( ( value == PWRI_VERSION ) ? \
				CRYPT_FORMAT_CRYPTLIB : CRYPT_FORMAT_NONE );
		}

#ifdef USE_PGP
	/* It's not ASN.1 data, check for PGP data */
	status = pgpReadPacketHeader( &stream, NULL, &value, 30, 8192 );
	if( cryptStatusOK( status ) && value > 30 && value < 8192 )
		{
		sMemDisconnect( &stream );
		return( CRYPT_FORMAT_PGP );
		}
#endif /* USE_PGP */

	sMemDisconnect( &stream );

	return( CRYPT_FORMAT_NONE );
	}

/* Check the key wrap key being used to import/export a session key */

CHECK_RETVAL \
static int checkWrapKey( IN_HANDLE int importKey, 
						 IN_BOOL const BOOLEAN isPKC,
						 IN_BOOL const BOOLEAN isImport )
	{
	REQUIRES( isHandleRangeValid( importKey ) );
	REQUIRES( isBooleanValue( isPKC ) );
	REQUIRES( isBooleanValue( isImport ) );

	/* In the case of PKCs, the DLP algorithms have specialised data-
	   formatting requirements and can't normally be directly accessed via 
	   external messages, and PKC operations in general may be restricted to 
	   internal-only access if they have certificates that restrict their 
	   use associated with them.  However doing the check via an internal 
	   message is safe at this point since we've already checked the 
	   context's external accessibility when we got the algorithm info */
	if( isPKC )
		{
		return( krnlSendMessage( importKey, IMESSAGE_CHECK, NULL,
								 isImport ? MESSAGE_CHECK_PKC_DECRYPT : \
											MESSAGE_CHECK_PKC_ENCRYPT ) );
		}

	return( krnlSendMessage( importKey, MESSAGE_CHECK, NULL, 
							 MESSAGE_CHECK_CRYPT ) );
	}

/* Check that the context data is encodable using the chosen format */

CHECK_RETVAL \
static int checkContextsEncodable( IN_HANDLE const CRYPT_HANDLE exportKey,
								   IN_ALGO const CRYPT_ALGO_TYPE exportAlgo,
								   IN_HANDLE const CRYPT_CONTEXT sessionKeyContext,
								   IN_ENUM( CRYPT_FORMAT ) \
									const CRYPT_FORMAT_TYPE formatType )
	{
	const BOOLEAN exportIsPKC = isPkcAlgo( exportAlgo ) ? TRUE : FALSE;
	BOOLEAN sessionIsMAC = FALSE;
	int sessionKeyAlgo, sessionKeyMode DUMMY_INIT, status;

	REQUIRES( isHandleRangeValid( exportKey ) );
	REQUIRES( isEnumRange( exportAlgo, CRYPT_ALGO ) );
	REQUIRES( isHandleRangeValid( sessionKeyContext ) );
	REQUIRES( isEnumRangeExternal( formatType, CRYPT_FORMAT ) );

	/* Get any required context information */
	status = krnlSendMessage( sessionKeyContext, MESSAGE_GETATTRIBUTE,
							  &sessionKeyAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( CRYPT_ERROR_PARAM3 );
	if( isMacAlgo( sessionKeyAlgo ) )
		sessionIsMAC = TRUE;
	else
		{
		status = krnlSendMessage( sessionKeyContext, MESSAGE_GETATTRIBUTE,
								  &sessionKeyMode, CRYPT_CTXINFO_MODE );
		if( cryptStatusError( status ) )
			return( CRYPT_ERROR_PARAM3 );
		}

	switch( formatType )
		{
		case CRYPT_FORMAT_CRYPTLIB:
		case CRYPT_FORMAT_CMS:
		case CRYPT_FORMAT_SMIME:
			/* Check that the export algorithm is encodable */
			if( exportIsPKC )
				{
				if( cryptStatusError( sizeofAlgoID( exportAlgo ) ) )
					return( CRYPT_ERROR_PARAM1 );
				}
			else
				{
				ALGOID_PARAMS algoIDparams;
				int exportMode;	/* int vs.enum */

				/* If it's a conventional key export, the key wrap mechanism 
				   requires the use of CBC mode for the wrapping */
				status = krnlSendMessage( exportKey, MESSAGE_GETATTRIBUTE, 
										  &exportMode, CRYPT_CTXINFO_MODE );
				if( cryptStatusError( status ) || exportMode != CRYPT_MODE_CBC )
					return( CRYPT_ERROR_PARAM1 );
				initAlgoIDparamsCrypt( &algoIDparams, exportMode, 0 );
				if( cryptStatusError( \
						sizeofAlgoIDex( exportAlgo, &algoIDparams ) ) )
					return( CRYPT_ERROR_PARAM1 );
				}

			/* Check that the session-key algorithm is encodable */
			if( sessionIsMAC )
				status = sizeofAlgoID( sessionKeyAlgo );
			else
				status = checkAlgoID( sessionKeyAlgo, sessionKeyMode );
			if( cryptStatusError( status ) )
				return( CRYPT_ERROR_PARAM3 );

			return( CRYPT_OK );

#ifdef USE_PGP
		case CRYPT_FORMAT_PGP:
			{
			int dummy;

			/* Check that the export algorithm is encodable */
			if( cryptStatusError( \
					cryptlibToPgpAlgo( exportAlgo, &dummy ) ) )
				return( CRYPT_ERROR_PARAM1 );

			/* Check that the session-key algorithm is encodable */
			if( exportIsPKC )
				{
				if( sessionKeyMode != CRYPT_MODE_CFB || \
					cryptStatusError( \
						cryptlibToPgpAlgo( sessionKeyAlgo, &dummy ) ) )
					return( CRYPT_ERROR_PARAM3 );
				}
			else
				{
				int exportMode;	/* int vs.enum */

				/* If it's a conventional key export there's no key wrap as 
				   in CMS (the session-key context isn't used), so the 
				   "export context" mode must be CFB */
				status = krnlSendMessage( exportKey, MESSAGE_GETATTRIBUTE, 
										  &exportMode, CRYPT_CTXINFO_MODE );
				if( cryptStatusError( status ) || \
					exportMode != CRYPT_MODE_CFB )
					return( CRYPT_ERROR_PARAM1 );
				}

			return( CRYPT_OK );
			}
#endif /* USE_PGP */
		}
	
	/* It's an invalid/unknown format, we can't check the encodability of 
	   the context data */
	return( CRYPT_ERROR_PARAM4 );
	}

/****************************************************************************
*																			*
*								Import a Session Key						*
*																			*
****************************************************************************/

/* Import an extended encrypted key, either a cryptlib key or a CMS key */

C_CHECK_RETVAL C_NONNULL_ARG( ( 1 ) ) \
C_RET cryptImportKeyEx( C_IN void C_PTR encryptedKey,
						C_IN int encryptedKeyLength,
						C_IN CRYPT_CONTEXT importKey,
						C_IN CRYPT_CONTEXT sessionKeyContext,
						C_OUT_OPT CRYPT_CONTEXT C_PTR returnedContext )
	{
	CRYPT_CONTEXT iReturnedContext DUMMY_INIT;
	CRYPT_FORMAT_TYPE formatType;
	CRYPT_ALGO_TYPE importAlgo;
	ERROR_INFO localErrorInfo;
	int owner = CRYPT_ERROR, originalOwner = CRYPT_ERROR, status;

	/* Perform basic error checking */
	if( !isShortIntegerRangeMin( encryptedKeyLength, MIN_CRYPT_OBJECTSIZE ) )
		return( CRYPT_ERROR_PARAM2 );
	if( !isReadPtrDynamic( encryptedKey, encryptedKeyLength ) )
		return( CRYPT_ERROR_PARAM1 );
	if( ( formatType = \
			getFormatType( encryptedKey, \
						   encryptedKeyLength ) ) == CRYPT_FORMAT_NONE )
		return( CRYPT_ERROR_BADDATA );
	if( !isHandleRangeValid( importKey ) )
		return( CRYPT_ERROR_PARAM3 );
	if( sessionKeyContext != CRYPT_UNUSED && \
		!isHandleRangeValid( sessionKeyContext ) )
		return( CRYPT_ERROR_PARAM4 );

	/* Make sure that the context is valid and get the algorithm being 
	   used */
	status = krnlSendMessage( importKey, MESSAGE_GETATTRIBUTE,
							  &importAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( status );

	/* Check the importing key */
	status = checkWrapKey( importKey, isPkcAlgo( importAlgo ) ? TRUE : FALSE, 
						   TRUE );
	if( cryptStatusError( status ) )
		return( cryptArgError( status ) ? CRYPT_ERROR_PARAM3 : status );

	/* Check the session key */
	if( formatType == CRYPT_FORMAT_PGP )
		{
		/* PGP stores the session key information with the encrypted key
		   data, so the user can't provide a context */
		if( sessionKeyContext != CRYPT_UNUSED )
			return( CRYPT_ERROR_PARAM4 );
		if( !isWritePtr( returnedContext, sizeof( CRYPT_CONTEXT ) ) )
			return( CRYPT_ERROR_PARAM5 );
		*returnedContext = CRYPT_ERROR;
		}
	else
		{
		int sessionKeyAlgo;	/* int vs.enum */

		if( !isHandleRangeValid( sessionKeyContext ) )
			return( CRYPT_ERROR_PARAM4 );
		status = krnlSendMessage( sessionKeyContext, MESSAGE_GETATTRIBUTE,
								  &sessionKeyAlgo, CRYPT_CTXINFO_ALGO );
		if( cryptStatusOK( status ) )
			{
			status = krnlSendMessage( sessionKeyContext, MESSAGE_CHECK, NULL,
									  isMacAlgo( sessionKeyAlgo ) ? \
										MESSAGE_CHECK_MAC_READY : \
										MESSAGE_CHECK_CRYPT_READY );
			}
		if( cryptStatusError( status ) )
			return( cryptArgError( status ) ? CRYPT_ERROR_PARAM4 : status );
		if( returnedContext != NULL )
			return( CRYPT_ERROR_PARAM5 );
		}

	/* If the importing key is owned, bind the session key context to the same
	   owner before we load a key into it.  We also need to save the original
	   owner so that we can undo the binding later if things fail */
	status = krnlSendMessage( importKey, MESSAGE_GETATTRIBUTE, &owner,
							  CRYPT_PROPERTY_OWNER );
	if( cryptStatusOK( status ) && sessionKeyContext != CRYPT_UNUSED )
		{
		/* The importing key is owned, set the imported key's owner */
		status = krnlSendMessage( sessionKeyContext, MESSAGE_GETATTRIBUTE,
								  &originalOwner, CRYPT_PROPERTY_OWNER );
		if( cryptStatusOK( status ) )
			{
			status = krnlSendMessage( sessionKeyContext, MESSAGE_SETATTRIBUTE, 
									  &owner, CRYPT_PROPERTY_OWNER );
			if( cryptStatusError( status ) )
				return( status );
			}
		else
			{
			/* Don't try and reset the session key ownership on error */
			originalOwner = CRYPT_ERROR;
			}
		}

	/* Import it as appropriate.  Since there's nothing to return the error 
	   information through we don't do anything with it */
	clearErrorInfo( &localErrorInfo );
	status = iCryptImportKey( encryptedKey, encryptedKeyLength, formatType,
							  importKey, sessionKeyContext, 
							  ( formatType == CRYPT_FORMAT_PGP ) ? \
								&iReturnedContext : NULL, &localErrorInfo );
	if( cryptStatusError( status ) )
		{
		/* The import failed, return the session key context to its
		   original owner.  If this fails there's not much that we can do
		   to recover so we don't do anything with the return value */
		if( originalOwner != CRYPT_ERROR )
			{
			( void ) krnlSendMessage( sessionKeyContext, 
									  MESSAGE_SETATTRIBUTE,
									  &originalOwner, CRYPT_PROPERTY_OWNER );
			}
		if( cryptArgError( status ) )
			{
			/* If we get an argument error from the lower-level code, map the
			   parameter number to the function argument number */
			status = ( status == CRYPT_ARGERROR_NUM1 ) ? \
					 CRYPT_ERROR_PARAM4 : CRYPT_ERROR_PARAM3;
			}
		return( status );
		}

#ifdef USE_PGP
	/* If it's a PGP key import then the session key was recreated from 
	   information stored with the wrapped key so we have to make it 
	   externally visible before it can be used by the caller */
	if( formatType == CRYPT_FORMAT_PGP && isPkcAlgo( importAlgo ) )
		{
		/* If the importing key is owned, set the imported key's owner */
		if( owner != CRYPT_ERROR )
			{
			status = krnlSendMessage( iReturnedContext, 
									  IMESSAGE_SETATTRIBUTE, 
									  &owner, CRYPT_PROPERTY_OWNER );
			if( cryptStatusError( status ) )
				{
				krnlSendNotifier( iReturnedContext, IMESSAGE_DECREFCOUNT );
				return( status );
				}
			}

		/* Make the newly-created context externally visible */
		status = krnlSendMessage( iReturnedContext, IMESSAGE_SETATTRIBUTE, 
								  MESSAGE_VALUE_FALSE, 
								  CRYPT_IATTRIBUTE_INTERNAL );
		if( cryptStatusError( status ) )
			{
			krnlSendNotifier( iReturnedContext, IMESSAGE_DECREFCOUNT );
			return( status );
			}
		*returnedContext = iReturnedContext;
		}
#endif /* USE_PGP */
	
	return( CRYPT_OK );
	}

C_CHECK_RETVAL C_NONNULL_ARG( ( 1 ) ) \
C_RET cryptImportKey( C_IN void C_PTR encryptedKey,
					  C_IN int encryptedKeyLength,
					  C_IN CRYPT_CONTEXT importKey,
					  C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	return( cryptImportKeyEx( encryptedKey, encryptedKeyLength, importKey,
							  sessionKeyContext, NULL ) );
	}

/****************************************************************************
*																			*
*								Export a Session Key						*
*																			*
****************************************************************************/

/* Export an extended encrypted key, either a cryptlib key or a CMS key */

C_CHECK_RETVAL C_NONNULL_ARG( ( 3 ) ) \
C_RET cryptExportKeyEx( C_OUT_OPT void C_PTR encryptedKey,
						C_IN int encryptedKeyMaxLength,
						C_OUT int C_PTR encryptedKeyLength,
						C_IN CRYPT_FORMAT_TYPE formatType,
						C_IN CRYPT_HANDLE exportKey,
						C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	CRYPT_ALGO_TYPE exportAlgo;
	ERROR_INFO localErrorInfo;
	int sessionKeyAlgo, status;	/* int vs.enum */

	/* Perform basic error checking */
	if( encryptedKey != NULL )
		{
		if( encryptedKeyMaxLength < MIN_CRYPT_OBJECTSIZE || \
			encryptedKeyMaxLength >= MAX_BUFFER_SIZE )
			return( CRYPT_ERROR_PARAM2 );
		if( !isWritePtrDynamic( encryptedKey, encryptedKeyMaxLength ) )
			return( CRYPT_ERROR_PARAM1 );
		memset( encryptedKey, 0, MIN_CRYPT_OBJECTSIZE );
		}
	else
		{
		if( encryptedKeyMaxLength != 0 )
			return( CRYPT_ERROR_PARAM2 );
		}
	if( !isWritePtr( encryptedKeyLength, sizeof( int ) ) )
		return( CRYPT_ERROR_PARAM3 );
	*encryptedKeyLength = 0;
	if( formatType != CRYPT_FORMAT_CRYPTLIB && \
		formatType != CRYPT_FORMAT_CMS && \
		formatType != CRYPT_FORMAT_SMIME && \
		formatType != CRYPT_FORMAT_PGP )
		return( CRYPT_ERROR_PARAM4 );
	if( !isHandleRangeValid( exportKey ) )
		return( CRYPT_ERROR_PARAM5 );
	if( !isHandleRangeValid( sessionKeyContext ) )
		return( CRYPT_ERROR_PARAM6 );

	/* Make sure that the context is valid and get the algorithm being 
	   used */
	status = krnlSendMessage( exportKey, MESSAGE_GETATTRIBUTE,
							  &exportAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( status );

	/* Check the exporting key */
	status = checkWrapKey( exportKey, isPkcAlgo( exportAlgo ) ? TRUE : FALSE, 
						   FALSE );
	if( cryptStatusError( status ) )
		return( cryptArgError( status ) ? CRYPT_ERROR_PARAM5 : status );
	status = checkContextsEncodable( exportKey, exportAlgo, 
									 sessionKeyContext, formatType );
	if( cryptStatusError( status ) )
		{
		return( ( status == CRYPT_ERROR_PARAM1 ) ? CRYPT_ERROR_PARAM5 : \
				( status == CRYPT_ERROR_PARAM3 ) ? CRYPT_ERROR_PARAM6 : \
				CRYPT_ERROR_PARAM4 );
		}

	/* Check the exported key */
	status = krnlSendMessage( sessionKeyContext, MESSAGE_GETATTRIBUTE,
							  &sessionKeyAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( CRYPT_ERROR_PARAM6 );
	status = krnlSendMessage( sessionKeyContext, MESSAGE_CHECK, NULL,
							  isMacAlgo( sessionKeyAlgo ) ? \
								MESSAGE_CHECK_MAC : MESSAGE_CHECK_CRYPT );
	if( cryptStatusError( status ) )
		return( cryptArgError( status ) ? CRYPT_ERROR_PARAM6 : status );

	/* Export the key via the shared export function.  Since there's nothing 
	   to return the error information through we don't do anything with 
	   it */
	clearErrorInfo( &localErrorInfo );
	status = iCryptExportKey( encryptedKey, 
							  min( encryptedKeyMaxLength, 
								   MAX_INTLENGTH_SHORT - 1 ),
							  encryptedKeyLength, formatType,
							  sessionKeyContext, exportKey, 
							  &localErrorInfo );
	if( cryptArgError( status ) )
		{
		/* If we get an argument error from the lower-level code, map the
		   parameter number to the function argument number */
		status = ( status == CRYPT_ARGERROR_NUM1 ) ? \
				 CRYPT_ERROR_PARAM6 : CRYPT_ERROR_PARAM5;
		}
	return( status );
	}

C_CHECK_RETVAL C_NONNULL_ARG( ( 3 ) ) \
C_RET cryptExportKey( C_OUT_OPT void C_PTR encryptedKey,
					  C_IN int encryptedKeyMaxLength,
					  C_OUT int C_PTR encryptedKeyLength,
					  C_IN CRYPT_HANDLE exportKey,
					  C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	int status;

	status = cryptExportKeyEx( encryptedKey, encryptedKeyMaxLength,
							   encryptedKeyLength, CRYPT_FORMAT_CRYPTLIB,
							   exportKey, sessionKeyContext );
	return( ( status == CRYPT_ERROR_PARAM5 ) ? CRYPT_ERROR_PARAM4 : \
			( status == CRYPT_ERROR_PARAM6 ) ? CRYPT_ERROR_PARAM5 : status );
	}

/****************************************************************************
*																			*
*						Internal Import/Export Functions					*
*																			*
****************************************************************************/

/* Internal versions of the above.  These skip a lot of the explicit 
   checking done by the external versions (e.g. "Is this value really a 
   handle to a valid PKC context?") since they're only called by cryptlib 
   internal functions rather than being passed untrusted user data */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 7 ) ) \
int iCryptImportKey( IN_BUFFER( encryptedKeyLength ) \
						const void *encryptedKey, 
					 IN_LENGTH_SHORT_MIN( MIN_CRYPT_OBJECTSIZE ) \
						const int encryptedKeyLength,
					 IN_ENUM( CRYPT_FORMAT ) \
						const CRYPT_FORMAT_TYPE formatType,
					 IN_HANDLE const CRYPT_CONTEXT iImportKey,
					 IN_HANDLE_OPT const CRYPT_CONTEXT iSessionKeyContext,
					 OUT_OPT_HANDLE_OPT CRYPT_CONTEXT *iReturnedContext,
					 INOUT_PTR ERROR_INFO *errorInfo )
	{
	const KEYEX_TYPE keyexType = \
			( formatType == CRYPT_FORMAT_AUTO || \
			  formatType == CRYPT_FORMAT_CRYPTLIB ) ? KEYEX_CRYPTLIB : \
			( formatType == CRYPT_FORMAT_PGP ) ? KEYEX_PGP : KEYEX_CMS;
	int importAlgo,	status;	/* int vs.enum */

	assert( isReadPtrDynamic( encryptedKey, encryptedKeyLength ) );
	assert( ( formatType == CRYPT_FORMAT_PGP && \
			  isWritePtr( iReturnedContext, sizeof( CRYPT_CONTEXT ) ) ) || \
			( formatType != CRYPT_FORMAT_PGP && \
			  iReturnedContext == NULL ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	REQUIRES( isShortIntegerRangeMin( encryptedKeyLength, 
									  MIN_CRYPT_OBJECTSIZE ) );
	REQUIRES( isEnumRange( formatType, CRYPT_FORMAT ) );
	REQUIRES( isHandleRangeValid( iImportKey ) );
	REQUIRES( ( formatType == CRYPT_FORMAT_PGP && \
				iSessionKeyContext == CRYPT_UNUSED ) || \
			  ( formatType != CRYPT_FORMAT_PGP && \
				isHandleRangeValid( iSessionKeyContext ) ) );
	REQUIRES( ( formatType == CRYPT_FORMAT_PGP && \
				iReturnedContext != NULL ) || \
			  ( formatType != CRYPT_FORMAT_PGP && \
				iReturnedContext == NULL ) );

	/* Clear return values */
	if( iReturnedContext != NULL )
		*iReturnedContext = CRYPT_ERROR;

	/* Import it as appropriate.  We don't handle key agreement at this
	   level since it's a protocol-specific mechanism used by SSH and SSL,
	   which are internal-only formats */
	status = krnlSendMessage( iImportKey, IMESSAGE_GETATTRIBUTE, &importAlgo,
							  CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( status );
	if( isConvAlgo( importAlgo ) )
		{
		if( !isHandleRangeValid( iSessionKeyContext ) )
			return( CRYPT_ARGERROR_NUM2 );
		return( importConventionalKey( encryptedKey, encryptedKeyLength,
									   iSessionKeyContext, iImportKey,
									   keyexType, errorInfo ) );
		}
	return( importPublicKey( encryptedKey, encryptedKeyLength,
							 iSessionKeyContext, iImportKey, 
							 iReturnedContext, keyexType, errorInfo ) );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 3, 7 ) ) \
int iCryptExportKey( OUT_BUFFER_OPT( encryptedKeyMaxLength, \
									 *encryptedKeyLength ) \
						void *encryptedKey, 
					 IN_LENGTH_SHORT_Z const int encryptedKeyMaxLength,
					 OUT_LENGTH_BOUNDED_SHORT_Z( encryptedKeyMaxLength ) \
						int *encryptedKeyLength,
					 IN_ENUM( CRYPT_FORMAT ) \
						const CRYPT_FORMAT_TYPE formatType,
					 IN_HANDLE_OPT const CRYPT_CONTEXT iSessionKeyContext,
					 IN_HANDLE const CRYPT_CONTEXT iExportKey,
					 INOUT_PTR ERROR_INFO *errorInfo )
	{
	KEYEX_TYPE keyexType = \
			( formatType == CRYPT_FORMAT_CRYPTLIB ) ? KEYEX_CRYPTLIB : \
			( formatType == CRYPT_FORMAT_PGP ) ? KEYEX_PGP : KEYEX_CMS;
	DYNBUF auxDB;
	const int encKeyMaxLength = ( encryptedKey == NULL ) ? \
								0 : encryptedKeyMaxLength;
#ifdef USE_OAEP
	int keyexFormat;
#endif /* USE_OAEP */
	int exportAlgo, status;	/* int vs.enum */

	assert( ( encryptedKey == NULL && encryptedKeyMaxLength == 0 ) || \
			( encryptedKeyMaxLength >= MIN_CRYPT_OBJECTSIZE && \
			  isWritePtrDynamic( encryptedKey, encryptedKeyMaxLength ) ) );
	assert( isWritePtr( encryptedKeyLength, sizeof( int ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	REQUIRES( ( encryptedKey == NULL && encryptedKeyMaxLength == 0 ) || \
			  isShortIntegerRangeMin( encryptedKeyMaxLength, \
									  MIN_CRYPT_OBJECTSIZE ) );
	REQUIRES( isEnumRange( formatType, CRYPT_FORMAT ) );
	REQUIRES( ( formatType == CRYPT_FORMAT_PGP && \
				iSessionKeyContext == CRYPT_UNUSED ) || \
			  isHandleRangeValid( iSessionKeyContext ) );
	REQUIRES( isHandleRangeValid( iExportKey ) );

	ANALYSER_HINT( encryptedKeyLength != NULL );

	/* Clear return value */
	*encryptedKeyLength = 0;

	/* Perform simplified error checking */
	status = krnlSendMessage( iExportKey, IMESSAGE_GETATTRIBUTE, &exportAlgo,
							  CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( cryptArgError( status ) ? CRYPT_ARGERROR_NUM2 : status );

	/* If it's a non-PKC export, pass the call down to the low-level export
	   function */
	if( !isPkcAlgo( exportAlgo ) )
		{
		return( exportConventionalKey( encryptedKey, encKeyMaxLength, 
									   encryptedKeyLength, iSessionKeyContext, 
									   iExportKey, keyexType, errorInfo ) );
		}

	REQUIRES( isHandleRangeValid( iSessionKeyContext ) );

	/* Get the keyex format type to use */
#ifdef USE_OAEP
	status = krnlSendMessage( DEFAULTUSER_OBJECT_HANDLE, 
							  IMESSAGE_GETATTRIBUTE, &keyexFormat, 
							  CRYPT_OPTION_PKC_FORMAT );
	if( cryptStatusError( status ) )
		return( status );
	if( keyexFormat != CRYPT_PKCFORMAT_DEFAULT )
		{
		/* We're using a nonstandard keyex format, make sure that it's 
		   compatible with the algorithm being used before enabling it */
		if( exportAlgo == CRYPT_ALGO_RSA && \
			keyexFormat == CRYPT_PKCFORMAT_OAEP )
			{
			keyexType = ( keyexType == KEYEX_CRYPTLIB ) ? \
						KEYEX_CRYPTLIB_OAEP : KEYEX_CMS_OAEP;
			}
		}
#endif /* USE_OAEP */

	/* If it's a non-CMS/SMIME PKC export, pass the call down to the low-
	   level export function */
	if( keyexType == KEYEX_CRYPTLIB || keyexType == KEYEX_CRYPTLIB_OAEP || \
		keyexType == KEYEX_PGP )
		{
		return( exportPublicKey( encryptedKey, encKeyMaxLength, 
								 encryptedKeyLength, iSessionKeyContext, 
								 iExportKey, NULL, 0, keyexType, 
								 errorInfo ) );
		}

	REQUIRES( keyexType == KEYEX_CMS || keyexType == KEYEX_CMS_OAEP );

	/* We're exporting a key in CMS format we need to obtain recipient 
	   information from the certificate associated with the export context.
	   First we lock the certificate for our exclusive use and in case it's 
	   a certificate chain select the first certificate in the chain */
	status = krnlSendMessage( iExportKey, IMESSAGE_SETATTRIBUTE,
							  MESSAGE_VALUE_TRUE, CRYPT_IATTRIBUTE_LOCKED );
	if( cryptStatusError( status ) )
		return( CRYPT_ERROR_PARAM5 );
	status = krnlSendMessage( iExportKey, IMESSAGE_SETATTRIBUTE,
							  MESSAGE_VALUE_CURSORFIRST, 
							  CRYPT_CERTINFO_CURRENT_CERTIFICATE );
	if( cryptStatusError( status ) )
		{
		/* Unlock the chain before we exit.  If this fails there's not much 
		   that we can do to recover so we don't do anything with the return 
		   value */
		( void ) krnlSendMessage( iExportKey, IMESSAGE_SETATTRIBUTE,
								  MESSAGE_VALUE_FALSE, 
								  CRYPT_IATTRIBUTE_LOCKED );
		return( CRYPT_ERROR_PARAM5 );
		}

	/* Next we get the recipient information from the certificate into a 
	   dynbuf */
	status = dynCreate( &auxDB, iExportKey,
						CRYPT_IATTRIBUTE_ISSUERANDSERIALNUMBER );
	if( cryptStatusError( status ) )
		{
		( void ) krnlSendMessage( iExportKey, IMESSAGE_SETATTRIBUTE,
								  MESSAGE_VALUE_FALSE, 
								  CRYPT_IATTRIBUTE_LOCKED );
		return( CRYPT_ERROR_PARAM5 );
		}

	/* We're ready to export the key alongside the key ID as auxiliary 
	   data */
	status = exportPublicKey( encryptedKey, encKeyMaxLength, 
							  encryptedKeyLength, iSessionKeyContext, 
							  iExportKey, dynData( auxDB ), 
							  dynLength( auxDB ), keyexType, errorInfo );

	/* Clean up.  If the unlock fails there's not much that we can do to 
	   recover so we don't do anything with the return value */
	( void ) krnlSendMessage( iExportKey, IMESSAGE_SETATTRIBUTE, 
							  MESSAGE_VALUE_FALSE, CRYPT_IATTRIBUTE_LOCKED );
	dynDestroy( &auxDB );

	return( status );
	}

#else

/****************************************************************************
*																			*
*						Stub Functions for non-CMS/PGP Use					*
*																			*
****************************************************************************/

C_RET cryptImportKeyEx( C_IN void C_PTR encryptedKey,
						C_IN int encryptedKeyLength,
						C_IN CRYPT_CONTEXT importKey,
						C_IN CRYPT_CONTEXT sessionKeyContext,
						C_OUT CRYPT_CONTEXT C_PTR returnedContext )
	{
	UNUSED_ARG( encryptedKey );
	UNUSED_ARG( returnedContext );

	return( CRYPT_ERROR_NOTAVAIL );
	}

C_RET cryptImportKey( C_IN void C_PTR encryptedKey,
					  C_IN int encryptedKeyLength,
					  C_IN CRYPT_CONTEXT importKey,
					  C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	UNUSED_ARG( encryptedKey );

	return( CRYPT_ERROR_NOTAVAIL );
	}

C_RET cryptExportKeyEx( C_OUT_OPT void C_PTR encryptedKey,
						C_IN int encryptedKeyMaxLength,
						C_OUT int C_PTR encryptedKeyLength,
						C_IN CRYPT_FORMAT_TYPE formatType,
						C_IN CRYPT_HANDLE exportKey,
						C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	UNUSED_ARG( encryptedKey );
	UNUSED_ARG( encryptedKeyLength );

	return( CRYPT_ERROR_NOTAVAIL );
	}

C_RET cryptExportKey( C_OUT_OPT void C_PTR encryptedKey,
					  C_IN int encryptedKeyMaxLength,
					  C_OUT int C_PTR encryptedKeyLength,
					  C_IN CRYPT_HANDLE exportKey,
					  C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	UNUSED_ARG( encryptedKey );
	UNUSED_ARG( encryptedKeyLength );

	return( CRYPT_ERROR_NOTAVAIL );
	}
#endif /* USE_INT_CMS */
