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

#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.  Note that this is
   content-sniffing rather than an absolute check, so all it does is tell
   the caller to try processing it as CMS data or PGP data, with the actual
   processing being applied determining whether it's valid or not */

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;
#ifdef USE_PGP
	int ctb, dummy;
#endif /* USE_PGP */
	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 );
		}

	/* It's not ASN.1 data, check for PGP data.  Since this is just content-
	   sniffing to check whether it looks like PGP data we just perform a 
	   basic check that it looks about right for PGP keyex data */
#ifdef USE_PGP
	status = pgpReadPacketHeader( &stream, &ctb, &dummy, 30, 8192 );
	if( cryptStatusOK( status ) && \
		( pgpGetPacketType( ctb ) != PGP_PACKET_PKE && \
		  pgpGetPacketType( ctb ) != PGP_PACKET_SKE ) )
		status = CRYPT_ERROR_BADDATA;
	if( cryptStatusOK( status ) )
		{
		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 ) );
		}

	/* For the non-PKC algorithms, we can use a standard external message */
	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 = CRYPT_MODE_NONE, 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 );
				if( cryptStatusError( status ) )
					return( CRYPT_ERROR_PARAM3 );
				}
			else
				{
				if( !checkAlgoID( sessionKeyAlgo, sessionKeyMode ) )
					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 then 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 );
	}

/****************************************************************************
*																			*
*								Unwrap a Session Key						*
*																			*
****************************************************************************/

/* Unwrap an encrypted key */

C_CHECK_RETVAL C_NONNULL_ARG( ( 1 ) ) \
C_RET cryptUnwrapKeyEx( 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( CRYPT_ERROR_PARAM3 );

	/* 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 doesn't do conventional-key wrap (it derives a key from a 
		   password but doesn't do key wrap) so we can only continue if 
		   we've been given a PKC context */
		if( !isPkcAlgo( importAlgo ) )
			return( CRYPT_ERROR_PARAM3 );
		
		/* 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_PARAM3 : CRYPT_ERROR_PARAM4;
			}
		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 )
		{
		/* 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 cryptUnwrapKey( C_IN void C_PTR encryptedKey,
					  C_IN int encryptedKeyLength,
					  C_IN CRYPT_CONTEXT importKey,
					  C_IN CRYPT_CONTEXT sessionKeyContext )
	{
	return( cryptUnwrapKeyEx( encryptedKey, encryptedKeyLength, importKey,
							  sessionKeyContext, NULL ) );
	}

/* Pre-3.4.9 versions of the functions.  Since these are marked as 
   deprecated in general we need to disable the warnings for this module */

#if defined( __clang__ ) && ( __clang_major__ >= 8 )
  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined( __GNUC__ ) && ( __GNUC__ >= 4 )
  #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined( _MSC_VER ) && ( _MSC_VER >= 1300 )
  #pragma warning ( disable : 4995 ) 
#endif /* VC++ */

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 )
	{
	return( cryptUnwrapKeyEx( encryptedKey, encryptedKeyLength, importKey,
							  sessionKeyContext, returnedContext ) );
	}

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( cryptUnwrapKeyEx( encryptedKey, encryptedKeyLength, importKey,
							  sessionKeyContext, NULL ) );
	}

/****************************************************************************
*																			*
*								Wrap a Session Key							*
*																			*
****************************************************************************/

/* Wrap an encrypted key */

C_CHECK_RETVAL C_NONNULL_ARG( ( 3 ) ) \
C_RET cryptWrapKeyEx( 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( !isIntegerRangeMin( encryptedKeyMaxLength, \
								MIN_CRYPT_OBJECTSIZE ) )
			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( CRYPT_ERROR_PARAM5 );

	/* 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 session key */
	if( formatType == CRYPT_FORMAT_PGP )
		{
		/* PGP doesn't do conventional-key wrap (it derives a key from a 
		   password but doesn't do key wrap) so we can only continue if 
		   we've been given a PKC context */
		if( !isPkcAlgo( exportAlgo ) )
			return( CRYPT_ERROR_PARAM5 );
		}
	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.  The reason for the 
	   min() part of the expression is that iCryptExportKey/ImportKey() gets 
	   suspicious of very large buffer sizes.  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 cryptWrapKey( 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 = cryptWrapKeyEx( encryptedKey, encryptedKeyMaxLength,
							 encryptedKeyLength, CRYPT_FORMAT_CRYPTLIB,
							 exportKey, sessionKeyContext );
	return( ( status == CRYPT_ERROR_PARAM5 ) ? CRYPT_ERROR_PARAM4 : \
			( status == CRYPT_ERROR_PARAM6 ) ? CRYPT_ERROR_PARAM5 : status );
	}

/* Pre-3.4.9 versions of the functions */

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 )
	{
	return( cryptWrapKeyEx( encryptedKey, encryptedKeyMaxLength,
							encryptedKeyLength, formatType, exportKey,
							sessionKeyContext ) );
	}

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 = cryptWrapKeyEx( 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 */
	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 ) );
		}

	/* Once we get to this point, where we're doing a PKC export, we need to 
	   have a valid session key context */
	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 and data format being used before 
		   enabling it */
		if( exportAlgo == CRYPT_ALGO_RSA && \
			keyexFormat == CRYPT_PKCFORMAT_OAEP && \
			formatType != CRYPT_FORMAT_PGP )
			{
			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 cryptUnwrapKeyEx( 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 cryptUnwrapKey( 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 cryptWrapKeyEx( 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 cryptWrapKey( 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 );
	}

/* Pre-3.4.9 versions of the functions.  Since these are marked as 
   deprecated in general we need to disable the warnings for this module */

#if defined( __clang__ ) && ( __clang_major__ >= 8 )
  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined( __GNUC__ ) && ( __GNUC__ >= 4 )
  #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined( _MSC_VER ) && ( _MSC_VER >= 1300 )
  #pragma warning ( disable : 4995 ) 
#endif /* VC++ */

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 )
	{
	return( cryptUnwrapKeyEx( encryptedKey, encryptedKeyLength, importKey,
							  sessionKeyContext, returnedContext ) );
	}

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( cryptUnwrapKeyEx( encryptedKey, encryptedKeyLength, importKey,
							  sessionKeyContext, NULL ) );
	}

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