/****************************************************************************
*																			*
*						Key Exchange Read/Write Routines					*
*						Copyright Peter Gutmann 1992-2006					*
*																			*
****************************************************************************/

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

/* Context-specific tags for the KEK record */

enum { CTAG_KK_DA };

/* Context-specific tags for the KeyTrans record */

enum { CTAG_KT_SKI };

/* Context-specific tags for the KeyAgree/Fortezza record */

enum { CTAG_KA_ORIG, CTAG_KA_UKM };

/****************************************************************************
*																			*
*					Conventionally-Encrypted Key Routines					*
*																			*
****************************************************************************/

/* The OID for the PKCS #5 v2.0 key derivation function and the parameterised
   PWRI key wrap algorithm */

#define OID_PBKDF2	MKOID( "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x05\x0C" )
#define OID_PWRIKEK	MKOID( "\x06\x0B\x2A\x86\x48\x86\xF7\x0D\x01\x09\x10\x03\x09" )

/* Read/write a PBKDF2 key derivation record:

	SEQUENCE {
		algorithm					AlgorithmIdentifier (pkcs-5 12),
		params SEQUENCE {
			salt					OCTET STRING,
			iterationCount			INTEGER (1..MAX),
			}
		} */

static int readKeyDerivationInfo( STREAM *stream, QUERY_INFO *queryInfo )
	{
	long endPos, value;
	int length, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Read the outer wrapper and key derivation algorithm OID */
	readConstructed( stream, NULL, CTAG_KK_DA );
	status = readFixedOID( stream, OID_PBKDF2 );
	if( cryptStatusError( status ) )
		return( status );

	/* Read the PBKDF2 parameters, limiting the salt and iteration count to
	   sane values */
	readSequence( stream, &length );
	endPos = stell( stream ) + length;
	readOctetString( stream, queryInfo->salt, &queryInfo->saltLength, 
					 2, CRYPT_MAX_HASHSIZE );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	if( value < 1 || value > MAX_KEYSETUP_ITERATIONS )
		return( CRYPT_ERROR_BADDATA );
	queryInfo->keySetupIterations = ( int ) value;
	queryInfo->keySetupAlgo = CRYPT_ALGO_HMAC_SHA;
	if( stell( stream ) < endPos )
		return( sseek( stream, endPos ) );

	return( CRYPT_OK );
	}

static int writeKeyDerivationInfo( STREAM *stream,
								   const CRYPT_CONTEXT iCryptContext )
	{
	MESSAGE_DATA msgData;
	BYTE salt[ CRYPT_MAX_HASHSIZE + 8 ];
	int keySetupIterations, derivationInfoSize, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );

	/* Get the key derivation information */
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
							  &keySetupIterations,
							  CRYPT_CTXINFO_KEYING_ITERATIONS );
	if( cryptStatusOK( status ) )
		{
		setMessageData( &msgData, salt, CRYPT_MAX_HASHSIZE );
		status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S,
								  &msgData, CRYPT_CTXINFO_KEYING_SALT );
		}
	if( cryptStatusError( status ) )
		return( status );
	derivationInfoSize = ( int ) sizeofObject( msgData.length ) + \
						 sizeofShortInteger( keySetupIterations );

	/* Write the PBKDF2 information */
	writeConstructed( stream, sizeofOID( OID_PBKDF2 ) +
					  ( int ) sizeofObject( derivationInfoSize ), CTAG_KK_DA );
	writeOID( stream, OID_PBKDF2 );
	writeSequence( stream, derivationInfoSize );
	writeOctetString( stream, msgData.data, msgData.length, DEFAULT_TAG );
	status = writeShortInteger( stream, keySetupIterations, DEFAULT_TAG );
	zeroise( salt, CRYPT_MAX_HASHSIZE );
	return( status );
	}

/* Read/write CMS KEK data.  This is the weird Spyrus key wrap that was 
   slipped into CMS, nothing seems to support this so we don't either */

static int readCmsKek( STREAM *stream, QUERY_INFO *queryInfo )
	{
	long value;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Read the header */
	readConstructed( stream, NULL, CTAG_RI_KEKRI );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	if( value != KEK_VERSION )
		return( CRYPT_ERROR_BADDATA );

	return( CRYPT_ERROR_NOTAVAIL );
	}

#if 0	/* 21/4/06 Disabled since it was never used */

static int writeCmsKek( STREAM *stream, const CRYPT_CONTEXT iCryptContext,
						const BYTE *encryptedKey, const int encryptedKeyLength )
	{
	STREAM localStream;
	MESSAGE_DATA msgData;
	BYTE kekInfo[ 128 + 8 ], label[ CRYPT_MAX_TEXTSIZE + 8 ];
	const int algoIdInfoSize = \
				sizeofContextAlgoID( iCryptContext, CRYPT_ALGO_NONE, 
									 ALGOID_FLAG_NONE );
	int kekInfoSize, labelSize, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( isReadPtr( encryptedKey, encryptedKeyLength ) );

	if( cryptStatusError( algoIdInfoSize ) )
		return( algoIdInfoSize  );

	/* Get the label */
	setMessageData( &msgData, label, CRYPT_MAX_TEXTSIZE );
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S,
							  &msgData, CRYPT_CTXINFO_LABEL );
	if( cryptStatusError( status ) )
		return( status );
	labelSize = msgData.length;

	/* Determine the size of the KEK info.  To save evaluating it twice in a
	   row and because it's short, we just write it to local buffers */
	sMemOpen( &localStream, kekInfo, 128 );
	writeSequence( &localStream, sizeofOID( OID_PWRIKEK ) + algoIdInfoSize );
	writeOID( &localStream, OID_PWRIKEK );
	status = writeContextAlgoID( &localStream, iCryptContext, CRYPT_ALGO_NONE,
								 ALGOID_FLAG_NONE );
	kekInfoSize = stell( &localStream );
	sMemDisconnect( &localStream );
	if( cryptStatusError( status ) )
		return( status );

	/* Write the algorithm identifiers and encrypted key */
	writeConstructed( stream, ( int ) sizeofShortInteger( KEK_VERSION ) + \
					  sizeofObject( sizeofObject( labelSize ) ) + \
					  kekInfoSize + sizeofObject( encryptedKeyLength ),
					  CTAG_RI_KEKRI );
	writeShortInteger( stream, KEK_VERSION, DEFAULT_TAG );
	writeSequence( stream, sizeofObject( labelSize ) );
	writeOctetString( stream, label, labelSize, DEFAULT_TAG );
	swrite( stream, kekInfo, kekInfoSize );
	return( writeOctetString( stream, encryptedKey, encryptedKeyLength,
							  DEFAULT_TAG ) );
	}
#endif /* 0 */

/* Read/write cryptlib KEK data:

	[3] SEQUENCE {
		version						INTEGER (0),
		keyDerivationAlgorithm	[0]	AlgorithmIdentifier OPTIONAL,
		keyEncryptionAlgorithm		AlgorithmIdentifier,
		encryptedKey				OCTET STRING
		} */

static int readCryptlibKek( STREAM *stream, QUERY_INFO *queryInfo )
	{
	const int startPos = stell( stream );
	long value;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* If it's a CMS KEK, read it as such */
	if( peekTag( stream ) == CTAG_RI_KEKRI )
		return( readCmsKek( stream, queryInfo ) );

	/* Read the header */
	readConstructed( stream, NULL, CTAG_RI_PWRI );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	if( value != PWRI_VERSION )
		return( CRYPT_ERROR_BADDATA );

	/* Read the optional KEK derivation info and KEK algorithm info */
	if( peekTag( stream ) == MAKE_CTAG( CTAG_KK_DA ) )
		{
		status = readKeyDerivationInfo( stream, queryInfo );
		if( cryptStatusError( status ) )
			return( status );
		}
	readSequence( stream, NULL );
	readFixedOID( stream, OID_PWRIKEK );
	status = readContextAlgoID( stream, NULL, queryInfo, DEFAULT_TAG );
	if( cryptStatusError( status ) )
		return( status );

	/* Finally, read the encrypted key */
	status = readOctetStringHole( stream, &queryInfo->dataLength, 
								  MIN_KEYSIZE, DEFAULT_TAG );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->dataStart = stell( stream ) - startPos;
	return( sSkip( stream, queryInfo->dataLength ) );
	}

static int writeCryptlibKek( STREAM *stream, const CRYPT_CONTEXT iCryptContext,
							 const BYTE *encryptedKey, const int encryptedKeyLength )
	{
	STREAM localStream;
	BYTE derivationInfo[ CRYPT_MAX_HASHSIZE + 32 + 8 ], kekInfo[ 128 + 8 ];
	BOOLEAN hasKeyDerivationInfo = TRUE;
	const int algoIdInfoSize = \
				sizeofContextAlgoID( iCryptContext, CRYPT_ALGO_NONE, 
									 ALGOID_FLAG_NONE );
	int derivationInfoSize = 0, kekInfoSize, value, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( isReadPtr( encryptedKey, encryptedKeyLength ) );

	if( cryptStatusError( algoIdInfoSize ) )
		return( algoIdInfoSize  );

	/* If it's a non-password-derived key and there's a label attached,
	   write it as a KEKRI with a PWRI algorithm identifier as the key
	   encryption algorithm */
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
							  &value, CRYPT_CTXINFO_KEYING_ITERATIONS );
	if( status == CRYPT_ERROR_NOTINITED )
		{
		hasKeyDerivationInfo = FALSE;

#if 0	/* 21/4/06 Disabled since it was never used */
		MESSAGE_DATA msgData;

		/* There's no password-derivation information present, see if there's
		   a label present */
		setMessageData( &msgData, NULL, 0 );
		status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S,
								  &msgData, CRYPT_CTXINFO_LABEL );
		if( cryptStatusOK( status ) )
			/* There's a label present, write it as a PWRI within a KEKRI */
			return( writeCmsKek( stream, iCryptContext, encryptedKey,
								 encryptedKeyLength ) );
#endif /* 0 */
		}

	/* Determine the size of the derivation info and KEK info.  To save
	   evaluating it twice in a row and because it's short, we just write
	   it to local buffers */
	if( hasKeyDerivationInfo )
		{
		sMemOpen( &localStream, derivationInfo, CRYPT_MAX_HASHSIZE + 32 );
		status = writeKeyDerivationInfo( &localStream, iCryptContext );
		derivationInfoSize = stell( &localStream );
		sMemDisconnect( &localStream );
		if( cryptStatusError( status ) )
			return( status );
		}
	sMemOpen( &localStream, kekInfo, 128 );
	writeSequence( &localStream, sizeofOID( OID_PWRIKEK ) + algoIdInfoSize );
	writeOID( &localStream, OID_PWRIKEK );
	status = writeContextAlgoID( &localStream, iCryptContext, CRYPT_ALGO_NONE,
								 ALGOID_FLAG_NONE );
	kekInfoSize = stell( &localStream );
	sMemDisconnect( &localStream );
	if( cryptStatusError( status ) )
		return( status );

	/* Write the algorithm identifiers and encrypted key */
	writeConstructed( stream, sizeofShortInteger( PWRI_VERSION ) +
					  derivationInfoSize + kekInfoSize +
					  ( int ) sizeofObject( encryptedKeyLength ),
					  CTAG_RI_PWRI );
	writeShortInteger( stream, PWRI_VERSION, DEFAULT_TAG );
	if( derivationInfoSize > 0 )
		swrite( stream, derivationInfo, derivationInfoSize );
	swrite( stream, kekInfo, kekInfoSize );
	return( writeOctetString( stream, encryptedKey, encryptedKeyLength,
							  DEFAULT_TAG ) );
	}

#ifdef USE_PGP

/* Read/write PGP KEK data.

	SKE:
		byte	ctb = PGP_PACKET_SKE
		byte[]	length
		byte	version = PGP_VERSION_OPENPGP
		byte	cryptAlgo
		byte	stringToKey specifier, 0, 1, or 3
		byte[]	stringToKey data
				0x00: byte		hashAlgo
				0x01: byte[8]	salt
				0x03: byte		iterations */

static int readPgpKek( STREAM *stream, QUERY_INFO *queryInfo )
	{
	int value, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Make sure that the packet header is in order and check the packet
	   version.  This is an OpenPGP-only packet */
	status = getPacketInfo( stream, queryInfo );
	if( cryptStatusError( status ) )
		return( status );
	if( sgetc( stream ) != PGP_VERSION_OPENPGP )
		return( CRYPT_ERROR_BADDATA );
	queryInfo->version = PGP_VERSION_OPENPGP;

	/* Get the password hash algorithm */
	if( ( queryInfo->cryptAlgo = \
			pgpToCryptlibAlgo( sgetc( stream ),
							   PGP_ALGOCLASS_PWCRYPT ) ) == CRYPT_ALGO_NONE )
		return( CRYPT_ERROR_NOTAVAIL );

	/* Read the S2K specifier */
	value = sgetc( stream );
	if( value != 0 && value != 1 && value != 3 )
		return( cryptStatusError( value ) ? value : CRYPT_ERROR_BADDATA );
	if( ( queryInfo->keySetupAlgo = \
			pgpToCryptlibAlgo( sgetc( stream ),
							   PGP_ALGOCLASS_HASH ) ) == CRYPT_ALGO_NONE )
		return( CRYPT_ERROR_NOTAVAIL );
	if( value == 0 )
		/* It's a straight hash, we're done */
		return( CRYPT_OK );
	status = sread( stream, queryInfo->salt, PGP_SALTSIZE );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->saltLength = PGP_SALTSIZE;
	if( value == 3 )
		{
		/* Salted iterated hash, decode the iteration count from the bizarre 
		   fixed-point encoded value, limited to a sane value range:

			count = ( ( Int32 ) 16 + ( c & 15 ) ) << ( ( c >> 4 ) + 6 )
		   
		   The "iteration count" is actually a count of how many bytes are 
		   hashed, this is because the "iterated hashing" treats the salt + 
		   password as an infinitely-repeated sequence of values and hashes 
		   the resulting string for PGP-iteration-count bytes worth.  The 
		   value we calculate here (to prevent overflow on 16-bit machines) 
		   is the count without the base * 64 scaling, this also puts the 
		   range within the value of the standard sanity check */
		value = sgetc( stream );
		if( cryptStatusError( value ) )
			return( value );
		queryInfo->keySetupIterations = \
				( 16 + ( ( long ) value & 0x0F ) ) << ( value >> 4 );
		if( queryInfo->keySetupIterations <= 0 || \
			queryInfo->keySetupIterations > MAX_KEYSETUP_ITERATIONS )
			return( CRYPT_ERROR_BADDATA );
		}

	return( CRYPT_OK );
	}

static int writePgpKek( STREAM *stream, const CRYPT_CONTEXT iCryptContext,
						const BYTE *encryptedKey, const int encryptedKeyLength )
	{
	CRYPT_ALGO_TYPE hashAlgo, cryptAlgo;
	BYTE salt[ CRYPT_MAX_HASHSIZE + 8 ];
	int keySetupIterations, count = 0, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( encryptedKey == NULL && encryptedKeyLength == 0 );

	/* Get the key derivation information */
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
						&keySetupIterations, CRYPT_CTXINFO_KEYING_ITERATIONS );
	if( cryptStatusOK( status ) )
		status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
						&hashAlgo, CRYPT_CTXINFO_KEYING_ALGO );
	if( cryptStatusOK( status ) )
		status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
						&cryptAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusOK( status ) )
		{
		MESSAGE_DATA msgData;

		setMessageData( &msgData, salt, CRYPT_MAX_HASHSIZE );
		status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S,
						&msgData, CRYPT_CTXINFO_KEYING_SALT );
		}
	if( cryptStatusError( status ) )
		return( status );

	/* Calculate the PGP "iteration count" from the value used to derive
	   the key.  The "iteration count" is actually a count of how many bytes
	   are hashed, this is because the "iterated hashing" treats the salt +
	   password as an infinitely-repeated sequence of values and hashes the
	   resulting string for PGP-iteration-count bytes worth.  Instead of
	   being written directly the count is encoded in a complex manner that
	   saves a whole byte, so before we can write it we have to encode it
	   into the base + exponent form expected by PGP.  This has a default
	   base of 16 + the user-supplied base value, we can set this to zero
	   since the iteration count used by cryptlib is always a multiple of
	   16, the remainder is just log2 of what's left of the iteration
	   count */
	assert( keySetupIterations % 16 == 0 );
	keySetupIterations /= 32;	/* Remove fixed offset before log2 op.*/
	while( keySetupIterations > 0 )
		{
		count++;
		keySetupIterations >>= 1;
		}
	count <<= 4;				/* Exponent comes first */

	/* Write the SKE packet */
	pgpWritePacketHeader( stream, PGP_PACKET_SKE, 
						  PGP_VERSION_SIZE + PGP_ALGOID_SIZE + 1 + \
							PGP_ALGOID_SIZE + PGP_SALTSIZE + 1 );
	sputc( stream, PGP_VERSION_OPENPGP );
	sputc( stream, cryptlibToPgpAlgo( cryptAlgo ) );
	sputc( stream, 3 );		/* S2K = salted, iterated hash */
	sputc( stream, cryptlibToPgpAlgo( hashAlgo ) );
	swrite( stream, salt, PGP_SALTSIZE );
	return( sputc( stream, count ) );
	}
#endif /* USE_PGP */

/****************************************************************************
*																			*
*						Public-key Encrypted Key Routines					*
*																			*
****************************************************************************/

/* Read/write CMS key transport data:

	SEQUENCE {
		version						INTEGER (0),
		issuerAndSerial				IssuerAndSerialNumber,
		algorithm					AlgorithmIdentifier,
		encryptedKey				OCTET STRING
		} */

static int readCmsKeytrans( STREAM *stream, QUERY_INFO *queryInfo )
	{
	const int startPos = stell( stream );
	long value;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Read the header and version number */
	readSequence( stream, NULL );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	if( value != KEYTRANS_VERSION )
		return( CRYPT_ERROR_BADDATA );

	/* Read the key ID and PKC algorithm information.  Since we're recording 
	   the position of the issuerAndSerialNumber as a blob, we have to use
	   getStreamObjectLength() to get the overall blob data size */
	value = getStreamObjectLength( stream );
	if( cryptStatusError( value ) )
		return( value );
	queryInfo->iAndSStart = stell( stream ) - startPos;
	queryInfo->iAndSLength = value;
	readUniversal( stream );
	status = readAlgoID( stream, &queryInfo->cryptAlgo );
	if( cryptStatusError( status ) )
		return( status );

	/* Finally, read the encrypted key */
	status = readOctetStringHole( stream, &queryInfo->dataLength, 
								  MIN_PKCSIZE, DEFAULT_TAG );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->dataStart = stell( stream ) - startPos;
	return( sSkip( stream, queryInfo->dataLength ) );
	}

static int writeCmsKeytrans( STREAM *stream,
							 const CRYPT_CONTEXT iCryptContext,
							 const BYTE *encryptedKey, 
							 const int encryptedKeyLength,
							 const void *auxInfo, const int auxInfoLength )
	{
	const int algoIdInfoSize = \
				sizeofContextAlgoID( iCryptContext, CRYPT_ALGO_NONE, 
									 ALGOID_FLAG_ALGOID_ONLY );

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( isReadPtr( encryptedKey, encryptedKeyLength ) );
	assert( isReadPtr( auxInfo, auxInfoLength ) );
	assert( algoIdInfoSize + ( int ) sizeofObject( encryptedKeyLength ) >= \
			8 + encryptedKeyLength );

	if( cryptStatusError( algoIdInfoSize ) )
		return( algoIdInfoSize  );

	writeSequence( stream, sizeofShortInteger( KEYTRANS_VERSION ) +
				   auxInfoLength + algoIdInfoSize + \
				   ( int ) sizeofObject( encryptedKeyLength ) );
	writeShortInteger( stream, KEYTRANS_VERSION, DEFAULT_TAG );
	swrite( stream, auxInfo, auxInfoLength );
	writeContextAlgoID( stream, iCryptContext, CRYPT_ALGO_NONE,
						ALGOID_FLAG_ALGOID_ONLY );
	return( writeOctetString( stream, encryptedKey, encryptedKeyLength, 
							  DEFAULT_TAG ) );
	}

/* Read/write cryptlib key transport data:

	SEQUENCE {
		version						INTEGER (2),
		keyID					[0]	SubjectKeyIdentifier,
		algorithm					AlgorithmIdentifier,
		encryptedKey				OCTET STRING
		} */

static int readCryptlibKeytrans( STREAM *stream, QUERY_INFO *queryInfo )
	{
	const int startPos = stell( stream );
	long value;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Read the header and version number */
	readSequence( stream, NULL );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	if( value != KEYTRANS_EX_VERSION )
		return( CRYPT_ERROR_BADDATA );

	/* Read the key ID and PKC algorithm information */
	readOctetStringTag( stream, queryInfo->keyID, &queryInfo->keyIDlength,
						8, CRYPT_MAX_HASHSIZE, CTAG_KT_SKI );
	status = readAlgoID( stream, &queryInfo->cryptAlgo );
	if( cryptStatusError( status ) )
		return( status );

	/* Finally, read the encrypted key */
	status = readOctetStringHole( stream, &queryInfo->dataLength, 
								  MIN_KEYSIZE, DEFAULT_TAG );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->dataStart = stell( stream ) - startPos;
	return( sSkip( stream, queryInfo->dataLength ) );
	}

static int writeCryptlibKeytrans( STREAM *stream,
								  const CRYPT_CONTEXT iCryptContext,
								  const BYTE *encryptedKey, 
								  const int encryptedKeyLength,
								  const void *auxInfo,
								  const int auxInfoLength )
	{
	MESSAGE_DATA msgData;
	BYTE keyID[ CRYPT_MAX_HASHSIZE + 8 ];
	const int algoIdInfoSize = \
				sizeofContextAlgoID( iCryptContext, CRYPT_ALGO_NONE, 
									 ALGOID_FLAG_ALGOID_ONLY );

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( isReadPtr( encryptedKey, encryptedKeyLength ) );
	assert( algoIdInfoSize + ( int ) sizeofObject( encryptedKeyLength ) >= \
			8 + encryptedKeyLength );

	if( cryptStatusError( algoIdInfoSize ) )
		return( algoIdInfoSize  );

	setMessageData( &msgData, keyID, CRYPT_MAX_HASHSIZE );
	krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S, &msgData,
					 CRYPT_IATTRIBUTE_KEYID );
	writeSequence( stream, sizeofShortInteger( KEYTRANS_EX_VERSION ) +
				   ( int ) sizeofObject( msgData.length ) + algoIdInfoSize + \
				   ( int ) sizeofObject( encryptedKeyLength ) );
	writeShortInteger( stream, KEYTRANS_EX_VERSION, DEFAULT_TAG );
	writeOctetString( stream, msgData.data, msgData.length, CTAG_KT_SKI );
	writeContextAlgoID( stream, iCryptContext, CRYPT_ALGO_NONE,
						ALGOID_FLAG_ALGOID_ONLY );
	return( writeOctetString( stream, encryptedKey, encryptedKeyLength, 
							  DEFAULT_TAG ) );
	}

#ifdef USE_PGP

/* Read/write PGP key transport data:

	PKE:
		byte	ctb = PGP_PACKET_PKE
		byte[]	length
		byte	version = PGP_VERSION_PGP2 or 3 (= OpenPGP, not the expected PGP3)
		byte[8]	keyID
		byte	PKC algo
		mpi(s)	encrypted session key */

static int readPgpKeytrans( STREAM *stream, QUERY_INFO *queryInfo )
	{
	const int startPos = stell( stream );
	int value, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Make sure that the packet header is in order and check the packet
	   version.  For this packet type, a version number of 3 denotes OpenPGP,
	   whereas for signatures it denotes PGP 2.x, so we translate the value
	   that we return to the caller */
	status = getPacketInfo( stream, queryInfo );
	if( cryptStatusError( status ) )
		return( status );
	value = sgetc( stream );
	if( value != PGP_VERSION_2 && value != 3 )
		return( CRYPT_ERROR_BADDATA );
	queryInfo->version = ( value == PGP_VERSION_2 ) ? \
						 PGP_VERSION_2 : PGP_VERSION_OPENPGP;

	/* Get the PGP key ID and algorithm */
	status = sread( stream, queryInfo->keyID, PGP_KEYID_SIZE );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->keyIDlength = PGP_KEYID_SIZE;
	if( ( queryInfo->cryptAlgo = \
			pgpToCryptlibAlgo( sgetc( stream ),
							   PGP_ALGOCLASS_PKCCRYPT ) ) == CRYPT_ALGO_NONE )
		return( CRYPT_ERROR_NOTAVAIL );

	/* Read the RSA-encrypted key, recording the position and length of the raw
	   RSA-encrypted integer value, */
	if( queryInfo->cryptAlgo == CRYPT_ALGO_RSA )
		{
		queryInfo->dataStart = ( stell( stream ) + UINT16_SIZE ) - startPos;
		status = readInteger16Ubits( stream, NULL, &queryInfo->dataLength,
									 MIN_PKCSIZE, CRYPT_MAX_PKCSIZE );
		if( cryptStatusError( status ) )
			return( status );
		}
	else
		{
		assert( queryInfo->cryptAlgo == CRYPT_ALGO_ELGAMAL );

		/* Read the Elgamal-encrypted key, recording the position and
		   combined lengths of the MPI pair */
		queryInfo->dataStart = stell( stream ) - startPos;
		status = readInteger16Ubits( stream, NULL, &value, MIN_PKCSIZE,
									 CRYPT_MAX_PKCSIZE );
		if( cryptStatusError( status ) )
			return( status );
		queryInfo->dataLength = UINT16_SIZE + value;	/* Incl.size of MPI hdr.*/
		status = readInteger16Ubits( stream, NULL, &value, MIN_PKCSIZE,
									 CRYPT_MAX_PKCSIZE );
		if( cryptStatusError( status ) )
			return( status );
		queryInfo->dataLength += UINT16_SIZE + value;	/* Incl.size of MPI hdr.*/
		}

	return( CRYPT_OK );
	}

static int writePgpKeytrans( STREAM *stream,
							 const CRYPT_CONTEXT iCryptContext,
							 const BYTE *encryptedKey, 
							 const int encryptedKeyLength,
							 const void *auxInfo, const int auxInfoLength )
	{
	CRYPT_ALGO_TYPE cryptAlgo;
	BYTE keyID[ PGP_KEYID_SIZE + 8 ];
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( isReadPtr( encryptedKey, encryptedKeyLength ) );

	/* Get the key information */
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE,
							  &cryptAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusOK( status ) )
		{
		MESSAGE_DATA msgData;

		setMessageData( &msgData, keyID, PGP_KEYID_SIZE );
		status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S,
								  &msgData, CRYPT_IATTRIBUTE_KEYID_OPENPGP );
		}
	if( cryptStatusError( status ) )
		return( status );

	/* Write the PKE packet */
	pgpWritePacketHeader( stream, PGP_PACKET_PKE,
						  PGP_VERSION_SIZE + PGP_KEYID_SIZE + PGP_ALGOID_SIZE + \
						  ( ( cryptAlgo == CRYPT_ALGO_RSA ) ? \
							sizeofInteger16U( encryptedKeyLength ) : \
							encryptedKeyLength ) );
	sputc( stream, 3 );		/* Version = 3 (OpenPGP) */
	swrite( stream, keyID, PGP_KEYID_SIZE );
	sputc( stream, cryptlibToPgpAlgo( cryptAlgo ) );
	return( ( cryptAlgo == CRYPT_ALGO_RSA ) ? \
			writeInteger16Ubits( stream, encryptedKey, encryptedKeyLength ) :
			swrite( stream, encryptedKey, encryptedKeyLength ) );
	}
#endif /* USE_PGP */

/****************************************************************************
*																			*
*								Key Agreement Routines						*
*																			*
****************************************************************************/

#if 0	/* 24/11/02 Removed since Fortezza is effectively dead */

/* Read/write a KeyAgreeRecipientInfo (= FortezzaRecipientInfo) record */

int readKeyAgreeInfo( STREAM *stream, QUERY_INFO *queryInfo,
					  CRYPT_CONTEXT *iKeyAgreeContext )
	{
	CRYPT_CONTEXT iLocalKeyAgreeContext;
	long value;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );
	assert( iKeyAgreeContext == NULL || \
			isWritePtr( iKeyAgreeContext, sizeof( CRYPT_CONTEXT ) ) );

	/* Clear return value */
	if( iKeyAgreeContext != NULL )
		*iKeyAgreeContext = CRYPT_ERROR;

	/* Read the header and version number */
	readConstructed( stream, NULL, CTAG_RI_KEYAGREE );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	if( value != 3 )
		return( CRYPT_ERROR_BADDATA );

	/* Read the public key information and encryption algorithm information */
	status = iCryptReadSubjectPublicKey( stream, &iLocalKeyAgreeContext,
										 FALSE );
	if( cryptStatusError( status ) )
		return( status );

	/* If we're doing a query, we're not interested in the key agreement
	   context so we just copy out the information that we need and destroy 
	   it */
	if( iKeyAgreeContext == NULL )
		{
		MESSAGE_DATA msgData;

		setMessageData( &msgData, queryInfo->keyID,
						queryInfo->keyIDlength );
		status = krnlSendMessage( iLocalKeyAgreeContext,
								  IMESSAGE_GETATTRIBUTE_S, &msgData,
								  CRYPT_IATTRIBUTE_KEYID );
		if( cryptStatusOK( status ) )
			status = krnlSendMessage( iLocalKeyAgreeContext,
									  IMESSAGE_GETATTRIBUTE,
									  &queryInfo->cryptAlgo,
									  CRYPT_CTXINFO_ALGO );
		krnlSendNotifier( iLocalKeyAgreeContext, IMESSAGE_DECREFCOUNT );
		return( status );
		}

	/* Make the key agreement context externally visible */
	*iKeyAgreeContext = iLocalKeyAgreeContext;
	return( CRYPT_OK );
	}

int writeKeyAgreeInfo( STREAM *stream, const CRYPT_CONTEXT iCryptContext,
					   const void *encryptedKey, const int encryptedKeyLength,
					   const void *ukm, const int ukmLength,
					   const void *auxInfo, const int auxInfoLength )
	{
	MESSAGE_DATA msgData;
	BYTE rKeyID[ 1024 + 8 ];
	int rKeyIDlength, recipientKeyInfoSize, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( isReadPtr( encryptedKey, encryptedKeyLength ) );
	assert( isReadPtr( ukm, ukmLength ) );
	assert( isReadPtr( auxInfo, auxInfoLength ) );

	/* Get the recipients key ID and determine how large the recipient key
	   info will be */
	setMessageData( &msgData, rKeyID, 1024 );
	status = krnlSendMessage( iCryptContext, MESSAGE_GETATTRIBUTE_S, &msgData,
							  CRYPT_CERTINFO_SUBJECTKEYIDENTIFIER );
	if( cryptStatusError( status ) )
		return( status );
	rKeyIDlength = msgData.length;
	recipientKeyInfoSize = ( int ) ( \
							sizeofObject( sizeofObject( rKeyIDlength ) ) + \
							sizeofObject( encryptedKeyLength ) );

	/* Write the FortezzaRecipientInfo header and version number */
	writeConstructed( stream, ( int ) sizeofShortInteger( 3 ) + \
					  sizeofObject( sizeofObject( auxInfoLength ) ) + \
					  sizeofObject( sizeofObject( ukmLength ) ) + \
					  sizeofOID( ALGOID_FORTEZZA_KEYWRAP ) + \
					  sizeofObject( sizeofObject( recipientKeyInfoSize ) ),
					  CTAG_RI_KEYAGREE );
	writeShortInteger( stream, 3, DEFAULT_TAG );

	/* Write the originator's keyIdentifier, UKM, and Fortezza key wrap OID */
	writeConstructed( stream, ( int ) sizeofObject( auxInfoLength ),
					  CTAG_KA_ORIG );
	writeOctetString( stream, auxInfo, auxInfoLength, 0 );
	writeConstructed( stream, ( int ) sizeofObject( ukmLength ),
					  CTAG_KA_UKM );
	writeOctetString( stream, ukm, ukmLength, DEFAULT_TAG );
	swrite( stream, ALGOID_FORTEZZA_KEYWRAP,
			sizeofOID( ALGOID_FORTEZZA_KEYWRAP ) );

	/* Write the recipient keying info */
	writeSequence( stream, ( int ) sizeofObject( recipientKeyInfoSize ) );
	writeSequence( stream, recipientKeyInfoSize );
	writeConstructed( stream, ( int ) sizeofObject( rKeyIDlength ), 0 );
	writeOctetString( stream, rKeyID, rKeyIDlength, DEFAULT_TAG );
	return( writeOctetString( stream, encryptedKey, encryptedKeyLength,
							  DEFAULT_TAG ) );
	}
#endif /* 0 */

/****************************************************************************
*																			*
*					Key Exchange Read/Write Access Function					*
*																			*
****************************************************************************/

typedef struct {
	const KEYEX_TYPE type;
	const READKEYTRANS_FUNCTION function;
	} KEYTRANS_READ_INFO;
static const KEYTRANS_READ_INFO keytransReadTable[] = {
	{ KEYEX_CMS, readCmsKeytrans },
	{ KEYEX_CRYPTLIB, readCryptlibKeytrans },
#ifdef USE_PGP
	{ KEYEX_PGP, readPgpKeytrans },
#endif /* USE_PGP */
	{ KEYEX_NONE, NULL }, { KEYEX_NONE, NULL }
	};

typedef struct {
	const KEYEX_TYPE type;
	const WRITEKEYTRANS_FUNCTION function;
	} KEYTRANS_WRITE_INFO;
static const KEYTRANS_WRITE_INFO keytransWriteTable[] = {
	{ KEYEX_CMS, writeCmsKeytrans },
	{ KEYEX_CRYPTLIB, writeCryptlibKeytrans },
#ifdef USE_PGP
	{ KEYEX_PGP, writePgpKeytrans },
#endif /* USE_PGP */
	{ KEYEX_NONE, NULL }, { KEYEX_NONE, NULL }
	};

typedef struct {
	const KEYEX_TYPE type;
	const READKEK_FUNCTION function;
	} KEK_READ_INFO;
static const KEK_READ_INFO kekReadTable[] = {
	{ KEYEX_CMS, readCryptlibKek },
	{ KEYEX_CRYPTLIB, readCryptlibKek },
#ifdef USE_PGP
	{ KEYEX_PGP, readPgpKek },
#endif /* USE_PGP */
	{ KEYEX_NONE, NULL }, { KEYEX_NONE, NULL }
	};

typedef struct {
	const KEYEX_TYPE type;
	const WRITEKEK_FUNCTION function;
	} KEK_WRITE_INFO;
static const KEK_WRITE_INFO kekWriteTable[] = {
	{ KEYEX_CMS, writeCryptlibKek },
	{ KEYEX_CRYPTLIB, writeCryptlibKek },
#ifdef USE_PGP
	{ KEYEX_PGP, writePgpKek },
#endif /* USE_PGP */
	{ KEYEX_NONE, NULL }, { KEYEX_NONE, NULL }
	};

READKEYTRANS_FUNCTION getReadKeytransFunction( const KEYEX_TYPE keyexType )
	{
	int i;

	for( i = 0; 
		 keytransReadTable[ i ].type != KEYEX_NONE && \
			i < FAILSAFE_ARRAYSIZE( keytransReadTable, KEYTRANS_READ_INFO ); 
		 i++ )
		{
		if( keytransReadTable[ i ].type == keyexType )
			return( keytransReadTable[ i ].function );
		}
	if( i >= FAILSAFE_ARRAYSIZE( keytransReadTable, KEYTRANS_READ_INFO ) )
		retIntError_Null();

	return( NULL );
	}
WRITEKEYTRANS_FUNCTION getWriteKeytransFunction( const KEYEX_TYPE keyexType )
	{
	int i;

	for( i = 0; 
		 keytransWriteTable[ i ].type != KEYEX_NONE && \
			i < FAILSAFE_ARRAYSIZE( keytransWriteTable, KEYTRANS_WRITE_INFO ); 
		 i++ )
		{
		if( keytransWriteTable[ i ].type == keyexType )
			return( keytransWriteTable[ i ].function );
		}
	if( i >= FAILSAFE_ARRAYSIZE( keytransWriteTable, KEYTRANS_WRITE_INFO ) )
		retIntError_Null();

	return( NULL );
	}
READKEK_FUNCTION getReadKekFunction( const KEYEX_TYPE keyexType )
	{
	int i;

	for( i = 0; 
		 kekReadTable[ i ].type != KEYEX_NONE && \
			i < FAILSAFE_ARRAYSIZE( kekReadTable, KEK_READ_INFO ); 
		 i++ )
		{
		if( kekReadTable[ i ].type == keyexType )
			return( kekReadTable[ i ].function );
		}
	if( i >= FAILSAFE_ARRAYSIZE( kekReadTable, KEK_READ_INFO ) )
		retIntError_Null();
		
	return( NULL );
	}
WRITEKEK_FUNCTION getWriteKekFunction( const KEYEX_TYPE keyexType )
	{
	int i;

	for( i = 0; 
		 kekWriteTable[ i ].type != KEYEX_NONE && \
			i < FAILSAFE_ARRAYSIZE( kekWriteTable, KEK_WRITE_INFO ); 
		 i++ )
		{
		if( kekWriteTable[ i ].type == keyexType )
			return( kekWriteTable[ i ].function );
		}
	if( i >= FAILSAFE_ARRAYSIZE( kekWriteTable, KEK_WRITE_INFO ) )
		retIntError_Null();

	return( NULL );
	}
