/****************************************************************************
*																			*
*						cryptlib Enveloping Routines						*
*					  Copyright Peter Gutmann 1996-2006						*
*																			*
****************************************************************************/

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

/* Determine the size of the envelope payload after PKCS #5 block padding if
   necessary.  This isn't just the size rounded up to the nearest multiple of
   the block size since if the size is already a multiple of the block size,
   it expands by another block, so we make the payload look one byte longer
   before rounding to the block size to ensure the one-block expansion */

#define paddedSize( size, blockSize )	\
		( ( blockSize > 1 ) ? roundUp( size + 1, blockSize ) : size )

#ifdef USE_ENVELOPES

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

/* Check that a requested algorithm type is valid with enveloped data */

BOOLEAN cmsCheckAlgo( const CRYPT_ALGO_TYPE cryptAlgo,
					  const CRYPT_ALGO_TYPE cryptMode )
	{
	assert( cryptAlgo > CRYPT_ALGO_NONE && \
			cryptAlgo < CRYPT_ALGO_LAST );
	assert( ( cryptMode == CRYPT_MODE_NONE ) || \
			( cryptMode > CRYPT_MODE_NONE && \
			  cryptMode < CRYPT_MODE_LAST ) );

	return( checkAlgoID( cryptAlgo, cryptMode ) );
	}

/* Get the OID for a CMS content type.  If no type is explicitly given, we
   assume raw data */

typedef struct {
	const CRYPT_CONTENT_TYPE contentType;
	const BYTE *oid;
	} CONTENTOID_INFO;
	
static const CONTENTOID_INFO FAR_BSS contentOIDs[] = {
	{ CRYPT_CONTENT_DATA, OID_CMS_DATA },
	{ CRYPT_CONTENT_SIGNEDDATA, OID_CMS_SIGNEDDATA },
	{ CRYPT_CONTENT_ENVELOPEDDATA, OID_CMS_ENVELOPEDDATA },
	{ CRYPT_CONTENT_SIGNEDANDENVELOPEDDATA, MKOID( "\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x07\x04" ) },
	{ CRYPT_CONTENT_DIGESTEDDATA, OID_CMS_DIGESTEDDATA },
	{ CRYPT_CONTENT_ENCRYPTEDDATA, OID_CMS_ENCRYPTEDDATA },
	{ CRYPT_CONTENT_COMPRESSEDDATA, OID_CMS_COMPRESSEDDATA },
	{ CRYPT_CONTENT_TSTINFO, OID_CMS_TSTOKEN },
	{ CRYPT_CONTENT_SPCINDIRECTDATACONTEXT, OID_MS_SPCINDIRECTDATACONTEXT },
	{ CRYPT_CONTENT_RTCSREQUEST, OID_CRYPTLIB_RTCSREQ },
	{ CRYPT_CONTENT_RTCSRESPONSE, OID_CRYPTLIB_RTCSRESP },
	{ CRYPT_CONTENT_RTCSRESPONSE_EXT, OID_CRYPTLIB_RTCSRESP_EXT },
	{ 0, NULL }, { 0, NULL }
	};

static const BYTE *getContentOID( const CRYPT_CONTENT_TYPE contentType )
	{
	int i;

	assert( contentType > CRYPT_CONTENT_NONE && \
			contentType < CRYPT_CONTENT_LAST );

	for( i = 0; contentOIDs[ i ].oid != NULL && \
				i < FAILSAFE_ARRAYSIZE( contentOIDs, CONTENTOID_INFO ); i++ )
		{
		if( contentOIDs[ i ].contentType == contentType )
			return( contentOIDs[ i ].oid );
		}

	assert( NOTREACHED );
	return( contentOIDs[ 0 ].oid );	/* Get rid of compiler warning */
	}

/* Copy as much post-data state information (i.e. signatures) from the
   auxiliary buffer to the main buffer as possible */

static int copyFromAuxBuffer( ENVELOPE_INFO *envelopeInfoPtr )
	{
	int bytesCopied, dataLeft;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Copy as much of the signature data as we can across */
	bytesCopied = min( envelopeInfoPtr->bufSize - envelopeInfoPtr->bufPos,
					   envelopeInfoPtr->auxBufPos );
	if( bytesCopied < 1 || \
		envelopeInfoPtr->bufPos + bytesCopied > envelopeInfoPtr->bufSize )
		{
		/* Sanity check */
		assert( NOTREACHED );
		return( CRYPT_ERROR_FAILED );
		}
	memcpy( envelopeInfoPtr->buffer + envelopeInfoPtr->bufPos,
			envelopeInfoPtr->auxBuffer, bytesCopied );
	envelopeInfoPtr->bufPos += bytesCopied;

	/* Since we're in the post-data state, any necessary payload data
	   segmentation has been completed.  However, the caller can't copy out
	   any post-payload data because it's past the end-of-segment position.
	   In order to allow the buffer to be emptied to make room for new data
	   from the auxBuffer, we set the end-of-segment position to the end of
	   the new data */
	envelopeInfoPtr->segmentDataEnd = envelopeInfoPtr->bufPos;

	/* If there's anything left, move it down in the buffer */
	dataLeft = envelopeInfoPtr->auxBufPos - bytesCopied;
	if( dataLeft > 0 )
		memmove( envelopeInfoPtr->auxBuffer, \
				 envelopeInfoPtr->auxBuffer + bytesCopied, dataLeft );
	envelopeInfoPtr->auxBufPos = dataLeft;
	assert( dataLeft >= 0 );

	return( ( dataLeft > 0 ) ? CRYPT_ERROR_OVERFLOW : CRYPT_OK );
	}

/* Write one or more indefinite-length end-of-contents indicators */

static int writeEOCs( ENVELOPE_INFO *envelopeInfoPtr, const int count )
	{
	static const BYTE indefEOC[ 16 ] = \
						{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
						  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
	const int dataLeft = envelopeInfoPtr->bufSize - envelopeInfoPtr->bufPos;
	const int eocLength = count * 2;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( count > 0 && count <= 8 );

	if( dataLeft < eocLength )
		return( CRYPT_ERROR_OVERFLOW );
	memcpy( envelopeInfoPtr->buffer + envelopeInfoPtr->bufPos, indefEOC,
			eocLength );
	envelopeInfoPtr->bufPos += eocLength;
	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						Emit Content-Specific Headers						*
*																			*
****************************************************************************/

/* Write the header fields that encapsulate any enveloped data:

   SignedData/DigestedData */

static int writeSignedDataHeader( STREAM *stream,
								  const ENVELOPE_INFO *envelopeInfoPtr,
								  const BOOLEAN isSignedData )
	{
	const BYTE *contentOID = getContentOID( envelopeInfoPtr->contentType );
	ACTION_LIST *actionListPtr;
	long dataSize;
	int hashActionSize = 0, iterationCount;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Determine the size of the hash actions */
	iterationCount = 0;
	for( actionListPtr = envelopeInfoPtr->actionList; 
		 actionListPtr != NULL && iterationCount++ < FAILSAFE_ITERATIONS_MAX;
		 actionListPtr = actionListPtr->next )
		{
		const int actionSize = \
			sizeofContextAlgoID( actionListPtr->iCryptHandle,
								 CRYPT_ALGO_NONE, ALGOID_FLAG_ALGOID_ONLY );
		if( cryptStatusError( actionSize ) )
			return( actionSize );
		hashActionSize += actionSize;
		}
	if( iterationCount >= FAILSAFE_ITERATIONS_MAX )
		retIntError();
	
	/* Determine the size of the SignedData/DigestedData */
	if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED || \
		( envelopeInfoPtr->dataFlags & ENVDATA_HASINDEFTRAILER ) )
		dataSize = CRYPT_UNUSED;
	else
		{
		/* Determine the size of the content OID + content */
		dataSize = ( envelopeInfoPtr->payloadSize > 0 ) ? \
			sizeofObject( sizeofObject( envelopeInfoPtr->payloadSize ) ) : 0;
		dataSize = sizeofObject( sizeofOID( contentOID ) + dataSize );

		/* Determine the size of the version, hash algoID, content, cert
		   chain, and signatures */
		dataSize = sizeofShortInteger( 1 ) + sizeofObject( hashActionSize ) + \
				   dataSize + envelopeInfoPtr->extraDataSize + \
				   sizeofObject( envelopeInfoPtr->signActionSize );
		}

	/* Write the SignedData/DigestedData header, version number, and SET OF
	   DigestInfo */
	writeCMSheader( stream, ( isSignedData ) ? \
					OID_CMS_SIGNEDDATA : OID_CMS_DIGESTEDDATA, dataSize,
					FALSE );
	writeShortInteger( stream, 1, DEFAULT_TAG );
	writeSet( stream, hashActionSize );
	iterationCount = 0;
	for( actionListPtr = envelopeInfoPtr->actionList; 
		 actionListPtr != NULL && iterationCount++ < FAILSAFE_ITERATIONS_MAX;
		 actionListPtr = actionListPtr->next )
		{
		int status = writeContextAlgoID( stream,
							actionListPtr->iCryptHandle, CRYPT_ALGO_NONE,
							ALGOID_FLAG_ALGOID_ONLY );
		if( cryptStatusError( status ) )
			return( status );
		}
	if( iterationCount >= FAILSAFE_ITERATIONS_MAX )
		retIntError();

	/* Write the inner Data header */
	return( writeCMSheader( stream, contentOID, envelopeInfoPtr->payloadSize,
							TRUE ) );
	}

/* EncryptedContentInfo contained within EnvelopedData */

static int writeEncryptedContentHeader( STREAM *stream,
							const BYTE *contentOID,
							const CRYPT_CONTEXT iCryptContext,
							const long payloadSize, const long blockSize )
	{
	const long blockedPayloadSize = ( payloadSize == CRYPT_UNUSED ) ? \
						CRYPT_UNUSED : paddedSize( payloadSize, blockSize );

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( contentOID, sizeofOID( contentOID ) ) );
	assert( isHandleRangeValid( iCryptContext ) );
	assert( payloadSize == CRYPT_UNUSED || payloadSize > 0 );
	assert( blockSize > 1 && blockSize <= CRYPT_MAX_IVSIZE );

	return( writeCMSencrHeader( stream, contentOID, blockedPayloadSize,
								iCryptContext ) );
	}

/* EncryptedData, EnvelopedData */

static int getEncrypedContentSize( const ENVELOPE_INFO *envelopeInfoPtr,
								   const BYTE *contentOID,
								   long *blockedPayloadSize,
								   long *encrContentInfoSize )
	{
	long length;

	assert( isReadPtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( isReadPtr( contentOID, sizeofOID( contentOID ) ) );
	assert( isWritePtr( blockedPayloadSize, sizeof( long ) ) );
	assert( isWritePtr( encrContentInfoSize, sizeof( long ) ) );

	/* Calculate the size of the payload after encryption blocking */
	if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED )
		*blockedPayloadSize = CRYPT_UNUSED;
	else
		*blockedPayloadSize = paddedSize( envelopeInfoPtr->payloadSize, 
										  envelopeInfoPtr->blockSize );

	/* Calculate the size of the CMS ContentInfo header */
	length = sizeofCMSencrHeader( contentOID, *blockedPayloadSize, 
								  envelopeInfoPtr->iCryptContext );
	if( cryptStatusError( length ) )
		return( ( int ) length );
	*encrContentInfoSize = length;

	return( CRYPT_OK );
	}

static void writeEncryptionHeader( STREAM *stream, const BYTE *oid,
								   const int version,
								   const long blockedPayloadSize,
								   const long extraSize )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( oid, sizeofOID( oid ) ) );
	assert( version >= 0 && version < 10 );
	assert( blockedPayloadSize == CRYPT_UNUSED || blockedPayloadSize > 0 );
	assert( extraSize == CRYPT_UNUSED || extraSize > 0 );

	writeCMSheader( stream, oid,
					( blockedPayloadSize == CRYPT_UNUSED || \
					  extraSize == CRYPT_UNUSED ) ? CRYPT_UNUSED : \
						sizeofShortInteger( 0 ) + extraSize + blockedPayloadSize,
					FALSE );
	writeShortInteger( stream, version, DEFAULT_TAG );
	}

static int writeEncryptedDataHeader( STREAM *stream,
									 const ENVELOPE_INFO *envelopeInfoPtr )
	{
	const BYTE *contentOID = getContentOID( envelopeInfoPtr->contentType );
	long blockedPayloadSize, encrContentInfoSize;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Calculate the size of the payload due to blocking and the ContentInfo
	   header */
	status = getEncrypedContentSize( envelopeInfoPtr, contentOID,
									 &blockedPayloadSize, 
									 &encrContentInfoSize );
	if( cryptStatusError( status ) )
		return( status );

	/* Write the EncryptedData header and version number, and
	   EncryptedContentInfo header */
	writeEncryptionHeader( stream, OID_CMS_ENCRYPTEDDATA, 0,
						   blockedPayloadSize, encrContentInfoSize );
	return( writeEncryptedContentHeader( stream, contentOID,
				envelopeInfoPtr->iCryptContext, envelopeInfoPtr->payloadSize,
				envelopeInfoPtr->blockSize ) );
	}

static int writeEnvelopedDataHeader( STREAM *stream,
									 const ENVELOPE_INFO *envelopeInfoPtr )
	{
	const BYTE *contentOID = getContentOID( envelopeInfoPtr->contentType );
	long blockedPayloadSize, encrContentInfoSize;
#ifdef USE_KEA
	const int originatorInfoSize = ( envelopeInfoPtr->extraDataSize > 0 ) ? \
			( int ) sizeofObject( envelopeInfoPtr->extraDataSize ) : 0;
#else
	#define originatorInfoSize	0
#endif /* USE_KEA */
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Calculate the size of the payload due to blocking and the ContentInfo
	   header */
	status = getEncrypedContentSize( envelopeInfoPtr, contentOID,
									 &blockedPayloadSize, 
									 &encrContentInfoSize );
	if( cryptStatusError( status ) )
		return( status );

	/* Write the EnvelopedData header and version number and start of the SET
	   OF RecipientInfo/EncryptionKeyInfo */
	writeEncryptionHeader( stream, OID_CMS_ENVELOPEDDATA,
					( originatorInfoSize > 0 ) ? 2 : 0, blockedPayloadSize,
					( envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED ) ? \
						CRYPT_UNUSED : \
						sizeofObject( envelopeInfoPtr->cryptActionSize ) + \
							originatorInfoSize + encrContentInfoSize );
#ifdef USE_KEA
	if( originatorInfoSize > 0 )
		{
		int status;

		/* Write the wrapper for the originator info and the originator info
		   itself */
		writeConstructed( stream, envelopeInfoPtr->extraDataSize, 0 );

		/* Export the originator cert chain either directly into the main
		   buffer or into the auxBuffer if there's not enough room */
		if( originatorInfoSize >= sMemDataLeft( stream ) )
			{
			/* The originator chain is too big for the main buffer, we have
			   to write everything from this point on into the auxBuffer.
			   This is then flushed into the main buffer in the calling code
			   before anything else is written */
			stream = ( STREAM * ) &envelopeInfoPtr->auxStream;
			}
		status = exportCertToStream( stream, envelopeInfoPtr->iExtraCertChain,
									 CRYPT_ICERTFORMAT_CERTSET );
		if( cryptStatusError( status ) )
			return( status );
		}
#endif /* USE_KEA */

	return( ( envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED ) ? \
			writeSetIndef( stream ) : \
			writeSet( stream, envelopeInfoPtr->cryptActionSize ) );
	}

/* AuthenticatedData */

static int writeAuthenticatedDataHeader( STREAM *stream,
							const ENVELOPE_INFO *envelopeInfoPtr )
	{
	const BYTE *contentOID = getContentOID( envelopeInfoPtr->contentType );
	const int macActionSize = \
				sizeofContextAlgoID( envelopeInfoPtr->actionList->iCryptHandle,
									 CRYPT_ALGO_NONE, ALGOID_FLAG_ALGOID_ONLY );

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	if( cryptStatusError( macActionSize ) )
		return( macActionSize );

	/* Write the AuthenticatedData header and version number and start of the SET
	   OF RecipientInfo */
	if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED )
		writeEncryptionHeader( stream, OID_CMS_AUTHDATA, 0, 1, CRYPT_UNUSED );
	else
		{
		int dataSize;

		dataSize = ( envelopeInfoPtr->payloadSize > 0 ) ? \
			sizeofObject( sizeofObject( envelopeInfoPtr->payloadSize ) ) : 0;
		dataSize = sizeofObject( sizeofOID( contentOID ) + dataSize );

		writeEncryptionHeader( stream, OID_CMS_AUTHDATA, 0,
				envelopeInfoPtr->payloadSize,
				( envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED ) ? \
					CRYPT_UNUSED : \
					sizeofObject( envelopeInfoPtr->cryptActionSize ) + \
					macActionSize + dataSize );
		}

	return( ( envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED ) ? \
			writeSetIndef( stream ) : \
			writeSet( stream, envelopeInfoPtr->cryptActionSize ) );
	}

/* CompressedData */

static int writeCompressedDataHeader( STREAM *stream,
									  ENVELOPE_INFO *envelopeInfoPtr )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Since compressing the data changes its length, we have to use the
	   indefinite-length encoding even if we know how big the payload is */
	envelopeInfoPtr->payloadSize = CRYPT_UNUSED;

	/* Write the CompressedData header, version number, and Zlib algoID */
	writeCMSheader( stream, OID_CMS_COMPRESSEDDATA, CRYPT_UNUSED, FALSE );
	writeShortInteger( stream, 0, DEFAULT_TAG );
	writeGenericAlgoID( stream, OID_ZLIB );

	/* Write the inner Data header */
	return( writeCMSheader( stream, getContentOID( envelopeInfoPtr->contentType ),
							CRYPT_UNUSED, TRUE ) );
	}

/****************************************************************************
*																			*
*						Content-Specific Pre-processing						*
*																			*
****************************************************************************/

/* Pre-process information for encrypted enveloping */

static int processKeyexchangeAction( ENVELOPE_INFO *envelopeInfoPtr,
									 ACTION_LIST *actionListPtr,
									 const CRYPT_DEVICE iCryptDevice )
	{
	int cryptAlgo, status;
#ifdef USE_KEA
	BYTE originatorDomainParams[ CRYPT_MAX_HASHSIZE + 8 ];
	int originatorDomainParamSize = 0;
#endif /* USE_KEA */

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( actionListPtr != NULL && \
			( actionListPtr->action == ACTION_KEYEXCHANGE_PKC || \
			  actionListPtr->action == ACTION_KEYEXCHANGE ) );
	assert( iCryptDevice == CRYPT_ERROR || \
			isHandleRangeValid( iCryptDevice ) );

	/* If the session key/MAC context is tied to a device, make sure that
	   the key exchange object is in the same device */
	if( iCryptDevice != CRYPT_ERROR )
		{
		CRYPT_DEVICE iKeyexDevice;

		status = krnlSendMessage( actionListPtr->iCryptHandle,
								  MESSAGE_GETDEPENDENT, &iKeyexDevice,
								  OBJECT_TYPE_DEVICE );
		if( cryptStatusError( status ) || iCryptDevice != iKeyexDevice )
			{
			setErrorInfo( envelopeInfoPtr, 
						  ( envelopeInfoPtr->usage == ACTION_CRYPT ) ? \
							CRYPT_ENVINFO_SESSIONKEY : CRYPT_ENVINFO_MAC,
						  CRYPT_ERRTYPE_CONSTRAINT );
			return( CRYPT_ERROR_INVALID );
			}
		}

#ifdef USE_KEA
	/* If there's an originator chain present, get the originator's domain
	   parameters and if the key is tied to a device, get the device's handle
	   so that we can create the session key object in it */
	if( envelopeInfoPtr->iExtraCertChain != CRYPT_ERROR )
		{
		MESSAGE_DATA msgData;

		setMessageData( &msgData, originatorDomainParams,
						 CRYPT_MAX_HASHSIZE );
		status = krnlSendMessage( envelopeInfoPtr->iExtraCertChain,
								  IMESSAGE_GETATTRIBUTE_S, &msgData,
								  CRYPT_IATTRIBUTE_KEY_KEADOMAINPARAMS );
		if( cryptStatusError( status ) )
			return( status );
		originatorDomainParamSize = msgData.length;
		status = krnlSendMessage( envelopeInfoPtr->iExtraCertChain,
								  IMESSAGE_GETDEPENDENT, &iCryptDevice,
								  OBJECT_TYPE_DEVICE );
		if( cryptStatusError( status ) )
			iCryptDevice = CRYPT_ERROR;
		}

	/* If it's a key agreement action, make sure that there's originator
	   info present and that the domain parameters match */
	if( actionListPtr->action == ACTION_KEYEXCHANGE_PKC && \
		cryptStatusOK( krnlSendMessage( actionListPtr->iCryptHandle,
										IMESSAGE_CHECK, NULL,
										MESSAGE_CHECK_PKC_KA_EXPORT ) ) )
		{
		MESSAGE_DATA msgData;
		BYTE domainParams[ CRYPT_MAX_HASHSIZE + 8 ];

		if( originatorDomainParamSize <= 0 )
			{
			setErrorInfo( envelopeInfoPtr, CRYPT_ENVINFO_ORIGINATOR,
						  CRYPT_ERRTYPE_ATTR_ABSENT );
			return( CRYPT_ERROR_NOTINITED );
			}
		setMessageData( &msgData, domainParams, CRYPT_MAX_HASHSIZE );
		status = krnlSendMessage( actionListPtr->iCryptHandle,
								  IMESSAGE_GETATTRIBUTE_S, &msgData,
								  CRYPT_IATTRIBUTE_KEY_DOMAINPARAMS );
		if( cryptStatusError( status ) )
			return( status );
		if( ( originatorDomainParamSize != msgData.length ) || \
			memcmp( originatorDomainParams, domainParams,
					originatorDomainParamSize ) )
			{
			setErrorInfo( envelopeInfoPtr, CRYPT_ENVINFO_ORIGINATOR,
						  CRYPT_ERRTYPE_CONSTRAINT );
			return( CRYPT_ERROR_INVALID );
			}
		}
#endif /* USE_KEA */

	/* Remember that we now have a controlling action and connect the
	   controller to the subject */
	envelopeInfoPtr->actionList->flags &= ~ACTION_NEEDSCONTROLLER;
	actionListPtr->associatedAction = envelopeInfoPtr->actionList;

	/* Evaluate the size of the exported action.  If it's a conventional key
	   exchange, we force the use of the CMS format since there's no reason
	   to use the cryptlib format */
	status = iCryptExportKeyEx( NULL, &actionListPtr->encodedSize, 0,
						( actionListPtr->action == ACTION_KEYEXCHANGE ) ? \
							CRYPT_FORMAT_CMS : envelopeInfoPtr->type,
						envelopeInfoPtr->actionList->iCryptHandle,
						actionListPtr->iCryptHandle );
	if( cryptStatusOK( status ) )
		status = krnlSendMessage( actionListPtr->iCryptHandle,
								  IMESSAGE_GETATTRIBUTE, &cryptAlgo,
								  CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( status );

	/* If there are any key exchange actions that will result in indefinite-
	   length encodings present, we can't use a definite-length encoding for
	   the key exchange actions */
	return( ( cryptAlgo == CRYPT_ALGO_ELGAMAL ) ? OK_SPECIAL : CRYPT_OK );
	}

static int preEnvelopeEncrypt( ENVELOPE_INFO *envelopeInfoPtr )
	{
	CRYPT_DEVICE iCryptDevice = CRYPT_ERROR;
	ACTION_LIST *actionListPtr;
	BOOLEAN hasIndefSizeActions = FALSE;
	int totalSize, iterationCount, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( envelopeInfoPtr->usage == ACTION_CRYPT || \
			envelopeInfoPtr->usage == ACTION_MAC );

#ifdef USE_KEA
	/* If there's originator info present, find out what it'll take to encode
	   it into the envelope header */
	if( envelopeInfoPtr->iExtraCertChain != CRYPT_ERROR )
		{
		MESSAGE_DATA msgData;
		int status;

		/* Determine how big the originator cert chain will be */
		setMessageData( &msgData, NULL, 0 );
		status = krnlSendMessage( envelopeInfoPtr->iExtraCertChain,
								  IMESSAGE_CRT_EXPORT, &msgData,
								  CRYPT_ICERTFORMAT_CERTSET );
		if( cryptStatusError( status ) )
			return( status );
		envelopeInfoPtr->extraDataSize = msgData.length;

		/* If we have very long originator cert chains the auxBuffer may not
		   be large enough to contain the resulting chain, so we have to
		   expand it to handle the chain */
		if( envelopeInfoPtr->auxBufSize < envelopeInfoPtr->extraDataSize + 64 )
			{
			assert( envelopeInfoPtr->auxBuffer == NULL );
			if( ( envelopeInfoPtr->auxBuffer = \
					clDynAlloc( "preEnvelopeEncrypt", \
								envelopeInfoPtr->extraDataSize + 64 ) ) == NULL )
				return( CRYPT_ERROR_MEMORY );
			envelopeInfoPtr->auxBufSize = envelopeInfoPtr->extraDataSize + 64;
			}
		}
#endif /* USE_KEA */

	/* If there are no key exchange actions present, we're done */
	if( envelopeInfoPtr->preActionList == NULL )
		return( CRYPT_OK );

	/* Create the session/MAC key if necessary */
	if( envelopeInfoPtr->actionList == NULL )
		{
		MESSAGE_CREATEOBJECT_INFO createInfo;

		/* Create a default encryption action and add it to the action
		   list */
		setMessageCreateObjectInfo( &createInfo,
							( envelopeInfoPtr->usage == ACTION_CRYPT ) ? \
								envelopeInfoPtr->defaultAlgo : \
								envelopeInfoPtr->defaultMAC );
		status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
								  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
								  OBJECT_TYPE_CONTEXT );
		if( cryptStatusError( status ) )
			return( status );
		status = krnlSendMessage( createInfo.cryptHandle, 
								  IMESSAGE_CTX_GENKEY, NULL, FALSE );
		if( cryptStatusOK( status ) && \
			addAction( &envelopeInfoPtr->actionList,
					   envelopeInfoPtr->memPoolState,
					   envelopeInfoPtr->usage,
					   createInfo.cryptHandle ) == NULL )
			status = CRYPT_ERROR_MEMORY;
		if( cryptStatusError( status ) )
			{
			krnlSendNotifier( createInfo.cryptHandle, IMESSAGE_DECREFCOUNT );
			return( status );
			}
		}
	else
		{
		/* If the session key/MAC context is tied to a device, get its handle
		   so we can check that all key exchange objects are also in the same
		   device */
		status = krnlSendMessage( envelopeInfoPtr->actionList->iCryptHandle,
								  MESSAGE_GETDEPENDENT, &iCryptDevice,
								  OBJECT_TYPE_DEVICE );
		if( cryptStatusError( status ) )
			iCryptDevice = CRYPT_ERROR;
		}
	assert( envelopeInfoPtr->actionList != NULL );

	/* Notify the kernel that the session key/MAC context is attached to the
	   envelope.  This is an internal object used only by the envelope, so
	   we tell the kernel not to increment its reference count when it
	   attaches it */
	krnlSendMessage( envelopeInfoPtr->objectHandle, IMESSAGE_SETDEPENDENT,
					 &envelopeInfoPtr->actionList->iCryptHandle,
					 SETDEP_OPTION_NOINCREF );

	/* Now walk down the list of key exchange actions evaluating their size
	   and connecting each one to the session key action */
	totalSize = 0; iterationCount = 0;
	for( actionListPtr = envelopeInfoPtr->preActionList;
		 actionListPtr != NULL && iterationCount++ < FAILSAFE_ITERATIONS_MAX; 
		 actionListPtr = actionListPtr->next )
		{
		status = processKeyexchangeAction( envelopeInfoPtr, actionListPtr,
										   iCryptDevice );
		if( cryptStatusError( status ) )
			{
			if( status != OK_SPECIAL )
				return( status );
			hasIndefSizeActions = TRUE;
			}
		totalSize += actionListPtr->encodedSize;
		}
	if( iterationCount >= FAILSAFE_ITERATIONS_MAX )
		retIntError();
	envelopeInfoPtr->cryptActionSize = hasIndefSizeActions ? \
									   CRYPT_UNUSED : totalSize;
	return( CRYPT_OK );
	}

/* Pre-process information for signed enveloping */

static int processSignatureAction( ENVELOPE_INFO *envelopeInfoPtr,
								   ACTION_LIST *actionListPtr )
	{
	int cryptAlgo, signatureSize, signingAttributes, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( isWritePtr( actionListPtr, sizeof( ACTION_LIST ) ) && \
			actionListPtr->action == ACTION_SIGN );
	assert( actionListPtr->associatedAction != NULL );

	/* Process signing certs if necessary and match the content-type in the
	   authenticated attributes with the signed content type if it's anything
	   other than 'data' (the data content-type is added automatically) */
	if( envelopeInfoPtr->type == CRYPT_FORMAT_CMS || \
		envelopeInfoPtr->type == CRYPT_FORMAT_SMIME )
		{
		/* If we're including signing certs and there are multiple signing
		   certs present, add the currently-selected one to the overall cert
		   collection */
		if( !( envelopeInfoPtr->flags & ENVELOPE_NOSIGNINGCERTS ) && \
			envelopeInfoPtr->iExtraCertChain != CRYPT_ERROR )
			{
			status = krnlSendMessage( envelopeInfoPtr->iExtraCertChain,
									  IMESSAGE_SETATTRIBUTE,
									  &actionListPtr->iCryptHandle,
									  CRYPT_IATTRIBUTE_CERTCOLLECTION );
			if( cryptStatusError( status ) )
				return( status );
			}

		/* If there's no content-type present and the signed content type
		   isn't 'data' or it's an S/MIME envelope, create signing attributes
		   to hold the content-type and smimeCapabilities.  Then, make sure
		   that the content-type in the attributes matches the actual content
		   type */
		if( actionListPtr->iExtraData == CRYPT_ERROR && \
			( envelopeInfoPtr->contentType != CRYPT_CONTENT_DATA || \
			  envelopeInfoPtr->type == CRYPT_FORMAT_SMIME ) )
			{
			MESSAGE_CREATEOBJECT_INFO createInfo;

			setMessageCreateObjectInfo( &createInfo,
										CRYPT_CERTTYPE_CMS_ATTRIBUTES );
			status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
									  IMESSAGE_DEV_CREATEOBJECT,
									  &createInfo, OBJECT_TYPE_CERTIFICATE );
			if( cryptStatusError( status ) )
				return( status );
			actionListPtr->iExtraData = createInfo.cryptHandle;
			}
		if( actionListPtr->iExtraData != CRYPT_ERROR )
			{
			int value;

			/* Delete any existing content-type (quietly fixing things if
			   necessary is easier than trying to report this error back to
			   the caller) and add our one */
			if( krnlSendMessage( actionListPtr->iExtraData,
						IMESSAGE_GETATTRIBUTE, &value,
						CRYPT_CERTINFO_CMS_CONTENTTYPE ) != CRYPT_ERROR_NOTFOUND )
				krnlSendMessage( actionListPtr->iExtraData,
								 IMESSAGE_DELETEATTRIBUTE, NULL,
								 CRYPT_CERTINFO_CMS_CONTENTTYPE );
			krnlSendMessage( actionListPtr->iExtraData,
							 IMESSAGE_SETATTRIBUTE,
							 &envelopeInfoPtr->contentType,
							 CRYPT_CERTINFO_CMS_CONTENTTYPE );
			}
		}

	/* Determine the type of signing attributes to use.  If none are
	   specified  (which can only happen if the signed content is data),
	   either get the signing code to add the default ones for us, or use
	   none at all if the use of default attributes is disabled */
	signingAttributes = actionListPtr->iExtraData;
	if( signingAttributes == CRYPT_ERROR )
		{
		int useDefaultAttributes;

		status = krnlSendMessage( envelopeInfoPtr->ownerHandle, 
								  IMESSAGE_GETATTRIBUTE,
								  &useDefaultAttributes,
								  CRYPT_OPTION_CMS_DEFAULTATTRIBUTES );
		if( cryptStatusError( status ) )
			return( status );
		signingAttributes = useDefaultAttributes ? \
							CRYPT_USE_DEFAULT : CRYPT_UNUSED;
		}

	/* Evaluate the size of the exported action */
	status = iCryptCreateSignatureEx( NULL, &signatureSize, 0,
						envelopeInfoPtr->type, actionListPtr->iCryptHandle,
						actionListPtr->associatedAction->iCryptHandle,
						signingAttributes,
						( actionListPtr->iTspSession != CRYPT_ERROR ) ? \
							actionListPtr->iTspSession : CRYPT_UNUSED );
	if( cryptStatusOK( status ) )
		status = krnlSendMessage( actionListPtr->iCryptHandle,
								  IMESSAGE_GETATTRIBUTE, &cryptAlgo,
								  CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( status );
	if( cryptAlgo == CRYPT_ALGO_DSA || \
		actionListPtr->iTspSession != CRYPT_ERROR )
		{
		/* If there are any signature actions that will result in indefinite-
		   length encodings present, we can't use a definite-length encoding
		   for the signature */
		envelopeInfoPtr->dataFlags |= ENVDATA_HASINDEFTRAILER;
		actionListPtr->encodedSize = CRYPT_UNUSED;
		}
	else
		{
		actionListPtr->encodedSize = signatureSize;
		envelopeInfoPtr->signActionSize += signatureSize;
		}

	return( CRYPT_OK );
	}

static int preEnvelopeSign( ENVELOPE_INFO *envelopeInfoPtr )
	{
	ACTION_LIST *actionListPtr = envelopeInfoPtr->postActionList;
	int iterationCount, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( envelopeInfoPtr->usage == ACTION_SIGN );

	/* Make sure that there's at least one signing action present */
	if( actionListPtr == NULL )
		return( CRYPT_ERROR_NOTINITED );

	assert( isWritePtr( actionListPtr, sizeof( ACTION_LIST ) ) );
	assert( actionListPtr->associatedAction != NULL );

	/* If we're generating a detached signature, the content is supplied
	   externally and has zero size */
	if( envelopeInfoPtr->flags & ENVELOPE_DETACHED_SIG )
		envelopeInfoPtr->payloadSize = 0;

	/* If it's an attributes-only message, it must be zero-length CMS signed
	   data with signing attributes present */
	if( envelopeInfoPtr->flags & ENVELOPE_ATTRONLY )
		{
		if( envelopeInfoPtr->type != CRYPT_FORMAT_CMS || \
			actionListPtr->iExtraData == CRYPT_ERROR )
			{
			setErrorInfo( envelopeInfoPtr, CRYPT_ENVINFO_SIGNATURE_EXTRADATA,
						  CRYPT_ERRTYPE_ATTR_ABSENT );
			return( CRYPT_ERROR_NOTINITED );
			}
		if( envelopeInfoPtr->payloadSize > 0 )
			{
			setErrorInfo( envelopeInfoPtr, CRYPT_ENVINFO_DATASIZE,
						  CRYPT_ERRTYPE_ATTR_VALUE );
			return( CRYPT_ERROR_INITED );
			}
		}

	/* If it's a CMS envelope we have to write the signing cert chain
	   alongside the signatures as extra data unless it's explicitly
	   excluded, so we record how large the info will be for later */
	if( ( envelopeInfoPtr->type == CRYPT_FORMAT_CMS || \
		  envelopeInfoPtr->type == CRYPT_FORMAT_SMIME ) && \
		!( envelopeInfoPtr->flags & ENVELOPE_NOSIGNINGCERTS ) )
		{
		if( actionListPtr->next != NULL )
			{
			MESSAGE_CREATEOBJECT_INFO createInfo;

			/* There are multiple sets of signing certs present, create a
			   signing-cert meta-object to hold the overall set of certs */
			setMessageCreateObjectInfo( &createInfo,
										CRYPT_CERTTYPE_CERTCHAIN );
			status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
									  IMESSAGE_DEV_CREATEOBJECT,
									  &createInfo, OBJECT_TYPE_CERTIFICATE );
			if( cryptStatusError( status ) )
				return( status );
			envelopeInfoPtr->iExtraCertChain = createInfo.cryptHandle;
			}
		else
			{
			MESSAGE_DATA msgData;

			/* There's a single signing cert present, determine its size */
			setMessageData( &msgData, NULL, 0 );
			status = krnlSendMessage( actionListPtr->iCryptHandle,
									  IMESSAGE_CRT_EXPORT, &msgData,
									  CRYPT_ICERTFORMAT_CERTSET );
			if( cryptStatusError( status ) )
				return( status );
			envelopeInfoPtr->extraDataSize = msgData.length;
			}
		}

	/* Evaluate the size of each signature action */
	iterationCount = 0;
	for( actionListPtr = envelopeInfoPtr->postActionList; 
		 actionListPtr != NULL && iterationCount++ < FAILSAFE_ITERATIONS_MAX;
		 actionListPtr = actionListPtr->next )
		{
		status = processSignatureAction( envelopeInfoPtr, actionListPtr );
		if( cryptStatusError( status ) )
			return( status );
		}
	if( iterationCount >= FAILSAFE_ITERATIONS_MAX )
		retIntError();
	if( envelopeInfoPtr->iExtraCertChain != CRYPT_ERROR )
		{
		MESSAGE_DATA msgData;

		/* We're writing the signing cert chain and there are multiple
		   signing certs present, get the size of the overall cert
		   collection */
		setMessageData( &msgData, NULL, 0 );
		status = krnlSendMessage( envelopeInfoPtr->iExtraCertChain,
								  IMESSAGE_CRT_EXPORT, &msgData,
								  CRYPT_ICERTFORMAT_CERTSET );
		if( cryptStatusError( status ) )
			return( status );
		envelopeInfoPtr->extraDataSize = msgData.length;
		}

	/* Hashing is now active */
	envelopeInfoPtr->dataFlags |= ENVDATA_HASHACTIONSACTIVE;

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Header Processing Routines						*
*																			*
****************************************************************************/

/* Write the envelope header */

static int writeEnvelopeHeader( ENVELOPE_INFO *envelopeInfoPtr )
	{
	STREAM stream;
	int length, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* If we're encrypting, set up the encryption-related information */
	if( envelopeInfoPtr->usage == ACTION_CRYPT )
		{
		status = initEnvelopeEncryption( envelopeInfoPtr,
								envelopeInfoPtr->actionList->iCryptHandle,
								CRYPT_ALGO_NONE, CRYPT_MODE_NONE, NULL, 0,
								FALSE );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Write the appropriate CMS header based on the envelope usage.  The
	   DigestedData/ACTION_HASH action is never taken since the higher-level 
	   code assumes that the presence of hash actions indicates the desire 
	   to create signed data and returns an error if no signature actions are 
	   present */
	sMemOpen( &stream, envelopeInfoPtr->buffer, envelopeInfoPtr->bufSize );
	switch( envelopeInfoPtr->usage )
		{
		case ACTION_CRYPT:
			if( envelopeInfoPtr->preActionList == NULL )
				status = writeEncryptedDataHeader( &stream,
												   envelopeInfoPtr );
			else
				status = writeEnvelopedDataHeader( &stream,
												   envelopeInfoPtr );
			break;

		case ACTION_SIGN:
			status = writeSignedDataHeader( &stream, envelopeInfoPtr, TRUE );
			break;

		case ACTION_HASH:
			status = writeSignedDataHeader( &stream, envelopeInfoPtr, FALSE );
			break;

		case ACTION_COMPRESS:
			status = writeCompressedDataHeader( &stream, envelopeInfoPtr );
			break;

		case ACTION_NONE:
			status = writeCMSheader( &stream,
								getContentOID( envelopeInfoPtr->contentType ),
								envelopeInfoPtr->payloadSize, FALSE );
			break;

		case ACTION_MAC:
			status = writeAuthenticatedDataHeader( &stream, envelopeInfoPtr );
			break;

		default:
			assert( NOTREACHED );
			return( CRYPT_ERROR_INTERNAL );
		}
	length = stell( &stream );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );
	envelopeInfoPtr->bufPos = length;

	/* If we're not encrypting with key exchange actions, we're done */
	if( envelopeInfoPtr->usage != ACTION_CRYPT || \
		envelopeInfoPtr->preActionList == NULL )
		{
		/* Make sure that we start a new segment if we try to add any data, 
		   set the block size mask to all ones if we're not encrypting since 
		   we can begin and end data segments on arbitrary boundaries, and 
		   inform the caller that we're done */
		envelopeInfoPtr->dataFlags |= ENVDATA_SEGMENTCOMPLETE;
		if( envelopeInfoPtr->usage != ACTION_CRYPT )
			envelopeInfoPtr->blockSizeMask = -1;
		envelopeInfoPtr->lastAction = NULL;
		return( OK_SPECIAL );
		}

	/* Start emitting the key exchange actions */
	envelopeInfoPtr->lastAction = findAction( envelopeInfoPtr->preActionList,
											  ACTION_KEYEXCHANGE_PKC );
	if( envelopeInfoPtr->lastAction == NULL )
		envelopeInfoPtr->lastAction = findAction( envelopeInfoPtr->preActionList,
												  ACTION_KEYEXCHANGE );
	assert( envelopeInfoPtr->lastAction != NULL );

	return( CRYPT_OK );
	}

/* Write key exchange actions */

static int writeKeyex( ENVELOPE_INFO *envelopeInfoPtr )
	{
	ACTION_LIST *lastActionPtr;
	int iterationCount = 0, status = CRYPT_OK;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Export the session key using each of the PKC or conventional keys.  
	   If it's a conventional key exchange, we force the use of the CMS 
	   format since there's no reason to use the cryptlib format */
	for( lastActionPtr = envelopeInfoPtr->lastAction;
		 lastActionPtr != NULL && iterationCount++ < FAILSAFE_ITERATIONS_MAX;
		 lastActionPtr = lastActionPtr->next )
		{
		const CRYPT_FORMAT_TYPE formatType = \
						( lastActionPtr->action == ACTION_KEYEXCHANGE ) ? \
						CRYPT_FORMAT_CMS : envelopeInfoPtr->type;
		const int dataLeft = min( envelopeInfoPtr->bufSize - \
								  envelopeInfoPtr->bufPos, 8192 );
		int keyexSize;

		/* Make sure that there's enough room to emit this key exchange 
		   action */
		if( lastActionPtr->encodedSize + 128 > dataLeft )
			{
			status = CRYPT_ERROR_OVERFLOW;
			break;
			}

		/* Emit the key exchange action */
		status = iCryptExportKeyEx( envelopeInfoPtr->buffer + \
									envelopeInfoPtr->bufPos, &keyexSize,
									dataLeft, formatType,
									envelopeInfoPtr->iCryptContext,
									lastActionPtr->iCryptHandle );
		if( cryptStatusError( status ) )
			break;
		envelopeInfoPtr->bufPos += keyexSize;
		}
	if( iterationCount >= FAILSAFE_ITERATIONS_MAX )
		retIntError();
	envelopeInfoPtr->lastAction = lastActionPtr;
	if( cryptStatusError( status ) )
		return( status );

	/* If it's an indefinite-length header, close off the set of key 
	   exchange actions */
	if( envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED )
		return( writeEOCs( envelopeInfoPtr, 1 ) );

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Trailer Processing Routines						*
*																			*
****************************************************************************/

/* Write signing cert chain.  This can grow arbitrarily large, and in 
   particular can become larger than the main envelope buffer if multiple 
   signatures with long chains and a small envelope buffer are used, so we 
   emit the cert chain into a dynamically-allocated auxiliary buffer if 
   there isn't enough room to emit it into the main buffer  */

static int writeCertchainTrailer( ENVELOPE_INFO *envelopeInfoPtr )
	{
	STREAM stream;
	void *certChainBufPtr;
	const int dataLeft = min( envelopeInfoPtr->bufSize - \
							  envelopeInfoPtr->bufPos, 32767 );
	const int eocSize = ( envelopeInfoPtr->payloadSize == CRYPT_UNUSED ) ? \
						( 3 * 2 ) : 0;
	int certChainBufSize, certChainSize, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );

	/* Check whether there's enough room left in the buffer to emit the 
	   signing cert chain directly into it */
	if( envelopeInfoPtr->extraDataSize + 64 < dataLeft )
		{
		certChainBufPtr = envelopeInfoPtr->buffer + \
						  envelopeInfoPtr->bufPos + eocSize;
		certChainBufSize = dataLeft - eocSize;
		}
	else
		{
		/* If there's almost no room left in the buffer anyway, tell the 
		   user that they have to pop some data before they can continue.  
		   Hopefully this will create enough room to emit the certs directly 
		   into the buffer */
		if( dataLeft < 1024 )
			return( CRYPT_ERROR_OVERFLOW );

		/* We can't emit the certs directly into the envelope buffer, 
		   allocate an auxiliary buffer for them and from there copy them 
		   into the main buffer */
		assert( envelopeInfoPtr->auxBuffer == NULL );
		if( ( envelopeInfoPtr->auxBuffer = \
				clDynAlloc( "emitPostamble",
							envelopeInfoPtr->extraDataSize + 64 ) ) == NULL )
			return( CRYPT_ERROR_MEMORY );
		certChainBufPtr = envelopeInfoPtr->auxBuffer;
		certChainBufSize = envelopeInfoPtr->auxBufSize = \
									envelopeInfoPtr->extraDataSize + 64;
		}

	/* Write the end-of-contents octets for the Data OCTET STRING, [0], and 
	   SEQUENCE if necessary */
	if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED )
		{
		status = writeEOCs( envelopeInfoPtr, 3 );
		if( cryptStatusError( status ) )
			return( status );
		}
	envelopeInfoPtr->lastAction = envelopeInfoPtr->postActionList;

	/* Write the signing cert chain if it's a CMS signature and they're not 
	   explicitly excluded, followed by the SET OF SignerInfo header */
	sMemOpen( &stream, certChainBufPtr, certChainBufSize );
	if( ( envelopeInfoPtr->type == CRYPT_FORMAT_CMS || \
		  envelopeInfoPtr->type == CRYPT_FORMAT_SMIME ) && \
		!( envelopeInfoPtr->flags & ENVELOPE_NOSIGNINGCERTS ) )
		{
		status = exportCertToStream( &stream,
							( envelopeInfoPtr->iExtraCertChain != CRYPT_ERROR ) ? \
							  envelopeInfoPtr->iExtraCertChain : \
							  envelopeInfoPtr->lastAction->iCryptHandle,
							CRYPT_ICERTFORMAT_CERTSET );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( &stream );
			return( status );
			}
		}
	if( envelopeInfoPtr->dataFlags & ENVDATA_HASINDEFTRAILER )
		status = writeSetIndef( &stream );
	else
		status = writeSet( &stream, envelopeInfoPtr->signActionSize );
	certChainSize = stell( &stream );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );

	/* If we're copying data via the auxBuffer, flush as much as we can into
	   the main buffer.  If we can't copy it all in, resulting in an overflow
	   error, we use the OK_SPECIAL status to tell the caller that although
	   an overflow occurred, it was due to the auxBuffer copy and not the
	   certchain write, and it's OK to move on to the next state */
	if( envelopeInfoPtr->auxBufSize > 0 )
		{
		envelopeInfoPtr->auxBufPos = certChainSize;
		status = copyFromAuxBuffer( envelopeInfoPtr );
		return( ( status == CRYPT_ERROR_OVERFLOW ) ? OK_SPECIAL : status );
		}

	/* Since we're in the post-data state, any necessary payload data 
	   segmentation has been completed.  However, the caller can't copy out 
	   any post-payload data because it's past the end-of-segment position.  
	   In order to allow the buffer to be emptied to make room for signature 
	   data, we set the end-of-segment position to the end of the new data */
	envelopeInfoPtr->bufPos += certChainSize;
	envelopeInfoPtr->segmentDataEnd = envelopeInfoPtr->bufPos;

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Emit Envelope Preamble/Postamble				*
*																			*
****************************************************************************/

/* Output as much of the preamble as possible into the envelope buffer */

static int emitPreamble( ENVELOPE_INFO *envelopeInfoPtr )
	{
	int status = CRYPT_OK;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( envelopeInfoPtr->envState >= ENVSTATE_NONE && \
			envelopeInfoPtr->envState <= ENVSTATE_DONE );

	/* If we've finished processing the header information, don't do
	   anything */
	if( envelopeInfoPtr->envState == ENVSTATE_DONE )
		return( CRYPT_OK );

	/* If we haven't started doing anything yet, perform various final
	   initialisations */
	if( envelopeInfoPtr->envState == ENVSTATE_NONE )
		{
		/* If there's no nested content type set, default to plain data */
		if( envelopeInfoPtr->contentType == CRYPT_CONTENT_NONE )
			envelopeInfoPtr->contentType = CRYPT_CONTENT_DATA;

		/* If there's an absolute data length set, remember it for when we
		   copy in data */
		if( envelopeInfoPtr->payloadSize != CRYPT_UNUSED )
			envelopeInfoPtr->segmentSize = envelopeInfoPtr->payloadSize;

		/* Perform any remaining initialisation.  MAC'd data is a special-
		   case form of encrypted data so we treat them as the same thing
		   at the key exchange level */
		if( envelopeInfoPtr->usage == ACTION_CRYPT || \
			envelopeInfoPtr->usage == ACTION_MAC )
			status = preEnvelopeEncrypt( envelopeInfoPtr );
		else
			if( envelopeInfoPtr->usage == ACTION_SIGN )
				status = preEnvelopeSign( envelopeInfoPtr );
		if( cryptStatusError( status ) )
			return( status );

		/* Delete any orphaned actions such as automatically-added hash
		   actions that were overridden with user-supplied alternate
		   actions */
		deleteUnusedActions( envelopeInfoPtr );

		/* We're ready to go, prepare to emit the outer header */
		envelopeInfoPtr->envState = ENVSTATE_HEADER;
		if( !checkActions( envelopeInfoPtr ) )
			{
			/* Sanity check */
			assert( NOTREACHED );
			return( CRYPT_ERROR_INTERNAL );
			}
		}

	/* Emit the outer header.  This always follows directly from the final
	   initialisation step, but we keep the two logically distinct to
	   emphasise that the former is merely finalising enveloping actions
	   without performing any header processing, while the latter is the
	   first stage that actually emits header data */
	if( envelopeInfoPtr->envState == ENVSTATE_HEADER )
		{
		status = writeEnvelopeHeader( envelopeInfoPtr );
		if( cryptStatusError( status ) )
			{
			/* If there's nothing else to emit, we're done */
			if( status == OK_SPECIAL )
				{
				envelopeInfoPtr->envState = ENVSTATE_DONE;
				return( CRYPT_OK );
				}

			return( status );
			}

		/* Move on to the next state */
		envelopeInfoPtr->envState = ENVSTATE_KEYINFO;
		}

	/* Handle key export actions */
	if( envelopeInfoPtr->envState == ENVSTATE_KEYINFO )
		{
		status = writeKeyex( envelopeInfoPtr );
		if( cryptStatusError( status ) )
			return( status );

		/* Move on to the next state */
		envelopeInfoPtr->envState = ENVSTATE_ENCRINFO;
		}

	/* Handle encrypted content information */
	if( envelopeInfoPtr->envState == ENVSTATE_ENCRINFO )
		{
		STREAM stream;
		const int dataLeft = min( envelopeInfoPtr->bufSize - \
								  envelopeInfoPtr->bufPos, 8192 );
		int length;

		/* Make sure that there's enough room to emit the data header.  The
		   value used is only approximate, if there's not enough room left
		   the write will also return an overflow error */
		if( dataLeft < 256 )
			return( CRYPT_ERROR_OVERFLOW );

		/* Write the encrypted content header */
		sMemOpen( &stream, envelopeInfoPtr->buffer + envelopeInfoPtr->bufPos,
				  dataLeft );
		status = writeEncryptedContentHeader( &stream,
							getContentOID( envelopeInfoPtr->contentType ),
							envelopeInfoPtr->iCryptContext,
							envelopeInfoPtr->payloadSize,
							envelopeInfoPtr->blockSize );
		length = stell( &stream );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			return( status );
		envelopeInfoPtr->bufPos += length;

		/* Make sure that we start a new segment if we try to add any data */
		envelopeInfoPtr->dataFlags |= ENVDATA_SEGMENTCOMPLETE;

		/* We're done */
		envelopeInfoPtr->envState = ENVSTATE_DONE;
		}

	return( CRYPT_OK );
	}

/* Output as much of the postamble as possible into the envelope buffer */

static int emitPostamble( ENVELOPE_INFO *envelopeInfoPtr )
	{
	ACTION_LIST *lastActionPtr;
	int iterationCount, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( envelopeInfoPtr->envState >= ENVSTATE_NONE && \
			envelopeInfoPtr->envState <= ENVSTATE_DONE );

	/* Before we can emit the trailer we need to flush any remaining data
	   from internal buffers */
	if( envelopeInfoPtr->envState == ENVSTATE_NONE )
		{
		status = envelopeInfoPtr->copyToEnvelopeFunction( envelopeInfoPtr,
													( BYTE * ) "", 0 );
		if( cryptStatusError( status ) )
			return( status );
		envelopeInfoPtr->envState = ENVSTATE_FLUSHED;
		}

	/* The only message type that has a trailer is signed data, so if we're 
	   not signing data we can exit now */
	if( envelopeInfoPtr->usage != ACTION_SIGN )
		{
		/* Emit the various end-of-contents octets if necessary */
		if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED || \
			( envelopeInfoPtr->usage == ACTION_CRYPT &&
			  envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED ) )
			{
			/* Write the end-of-contents octets for the encapsulated data if
			   necessary.  Normally we have two EOC's, however compressed
			   data requires an extra one due to the explicit tagging */
			if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED && \
				( envelopeInfoPtr->usage == ACTION_CRYPT || \
				  envelopeInfoPtr->usage == ACTION_COMPRESS ) )
				status = writeEOCs( envelopeInfoPtr, 3 + \
									( ( envelopeInfoPtr->usage == \
										ACTION_COMPRESS ) ? \
									  3 : 2 ) );
			else
				{
				/* Write the remaining end-of-contents octets for the OCTET
				   STRING/SEQUENCE, [0], and SEQUENCE */
				status = writeEOCs( envelopeInfoPtr, 3 );
				}
			if( cryptStatusError( status ) )
				return( status );
			}

		/* Now that we've written the final end-of-contents octets, set the end-
		   of-segment-data pointer to the end of the data in the buffer so that
		   copyFromEnvelope() can copy out the remaining data */
		envelopeInfoPtr->segmentDataEnd = envelopeInfoPtr->bufPos;
		envelopeInfoPtr->envState = ENVSTATE_DONE;

		return( CRYPT_OK );
		}

	/* If there's any signature data left in the auxiliary buffer, try and
	   empty that first */
	if( envelopeInfoPtr->auxBufPos > 0 )
		{
		status = copyFromAuxBuffer( envelopeInfoPtr );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Handle signing cert chain */
	if( envelopeInfoPtr->envState == ENVSTATE_FLUSHED )
		{
		status = writeCertchainTrailer( envelopeInfoPtr );
		if( cryptStatusError( status ) && status != OK_SPECIAL )
			return( status );

		/* Move on to the next state */
		envelopeInfoPtr->envState = ENVSTATE_SIGNATURE;

		/* If we were copying from the auxBuffer and got an overflow error,
		   we have to resume later in the signature state */
		if( status == OK_SPECIAL )
			return( CRYPT_ERROR_OVERFLOW );
		}

	/* Handle signing actions */
	assert( envelopeInfoPtr->envState == ENVSTATE_SIGNATURE );

	/* Sign each hash using the associated signature key */
	iterationCount = 0;
	for( lastActionPtr = envelopeInfoPtr->lastAction;
		 lastActionPtr != NULL && iterationCount++ < FAILSAFE_ITERATIONS_MAX; 
		 lastActionPtr = lastActionPtr->next )
		{
		const int sigBufSize = min( envelopeInfoPtr->bufSize - \
									envelopeInfoPtr->bufPos, 32767 );
		int sigSize, signingAttributes = lastActionPtr->iExtraData;

		assert( lastActionPtr->action == ACTION_SIGN );

		/* Check whether there's enough room left in the buffer to emit the
		   signature directly into it.  Since sigs are fairly small (a
		   few hundred bytes), we always require enough room in the buffer
		   and don't bother with any overflow handling via the auxBuffer */
		if( lastActionPtr->encodedSize + 64 > sigBufSize )
			{
			status = CRYPT_ERROR_OVERFLOW;
			break;
			}

		/* Determine the type of signing attributes to use.  If none are
		   specified (which can only happen under circumstances controlled
		   by the pre-envelope signing code), either get the signing code to
		   add the default ones for us, or use none at all if the use of
		   default attributes is disabled */
		if( signingAttributes == CRYPT_ERROR )
			{
			int useDefaultAttributes;

			status = krnlSendMessage( envelopeInfoPtr->ownerHandle,
							 IMESSAGE_GETATTRIBUTE, &useDefaultAttributes,
							 CRYPT_OPTION_CMS_DEFAULTATTRIBUTES );
			if( cryptStatusError( status ) )
				return( status );
			signingAttributes = useDefaultAttributes ? CRYPT_USE_DEFAULT : \
													   CRYPT_UNUSED;
			}

		/* Sign the data */
		status = iCryptCreateSignatureEx( envelopeInfoPtr->buffer + \
										  envelopeInfoPtr->bufPos, &sigSize,
							sigBufSize, envelopeInfoPtr->type,
							lastActionPtr->iCryptHandle,
							lastActionPtr->associatedAction->iCryptHandle,
							signingAttributes,
							( lastActionPtr->iTspSession != CRYPT_ERROR ) ? \
							lastActionPtr->iTspSession : CRYPT_UNUSED );
		if( cryptStatusError( status ) )
			break;
		envelopeInfoPtr->bufPos += sigSize;
		}
	if( iterationCount >= FAILSAFE_ITERATIONS_MAX )
		retIntError();
	envelopeInfoPtr->lastAction = lastActionPtr;
	if( cryptStatusError( status ) )
		return( status );

	/* Write the end-of-contents octets for the OCTET STRING/SEQUENCE, [0],
	   and SEQUENCE if necessary.  If the trailer has an indefinite length
	   then we need to add an EOC for the trailer as well */
	if( envelopeInfoPtr->payloadSize == CRYPT_UNUSED || \
		( envelopeInfoPtr->dataFlags & ENVDATA_HASINDEFTRAILER ) )
		{
		status = writeEOCs( envelopeInfoPtr,
							3 + ( ( envelopeInfoPtr->dataFlags & \
									ENVDATA_HASINDEFTRAILER ) ? \
								  1 : 0 ) );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Now that we've written the final end-of-contents octets, set the end-
	   of-segment-data pointer to the end of the data in the buffer so that
	   copyFromEnvelope() can copy out the remaining data */
	envelopeInfoPtr->segmentDataEnd = envelopeInfoPtr->bufPos;
	envelopeInfoPtr->envState = ENVSTATE_DONE;

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Envelope Access Routines						*
*																			*
****************************************************************************/

void initCMSEnveloping( ENVELOPE_INFO *envelopeInfoPtr )
	{
	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( !( envelopeInfoPtr->flags & ENVELOPE_ISDEENVELOPE ) );

	/* Set the access method pointers */
	envelopeInfoPtr->processPreambleFunction = emitPreamble;
	envelopeInfoPtr->processPostambleFunction = emitPostamble;
	envelopeInfoPtr->checkAlgo = cmsCheckAlgo;

	/* Set up the processing state information */
	envelopeInfoPtr->envState = ENVSTATE_NONE;

	/* Remember the current default settings for use with the envelope.
	   We force the use of the CBC encryption mode because this is the
	   safest and most efficient encryption mode, and the only mode defined
	   for many CMS algorithms.  Since the CMS algorithms represent only a
	   subset of what's available, we have to drop back to fixed values if
	   the caller has selected something exotic */
	krnlSendMessage( envelopeInfoPtr->ownerHandle, IMESSAGE_GETATTRIBUTE,
					 &envelopeInfoPtr->defaultHash, CRYPT_OPTION_ENCR_HASH );
	if( !checkAlgoID( envelopeInfoPtr->defaultHash, CRYPT_MODE_NONE ) )
		envelopeInfoPtr->defaultHash = CRYPT_ALGO_SHA;
	krnlSendMessage( envelopeInfoPtr->ownerHandle, IMESSAGE_GETATTRIBUTE,
					 &envelopeInfoPtr->defaultAlgo, CRYPT_OPTION_ENCR_ALGO );
	if( !checkAlgoID( envelopeInfoPtr->defaultAlgo,
					  ( envelopeInfoPtr->defaultAlgo == CRYPT_ALGO_RC4 ) ? \
					  CRYPT_MODE_OFB : CRYPT_MODE_CBC ) )
		envelopeInfoPtr->defaultAlgo = CRYPT_ALGO_3DES;
	krnlSendMessage( envelopeInfoPtr->ownerHandle, IMESSAGE_GETATTRIBUTE,
					 &envelopeInfoPtr->defaultMAC, CRYPT_OPTION_ENCR_MAC );
	if( !checkAlgoID( envelopeInfoPtr->defaultMAC, CRYPT_MODE_NONE ) )
		envelopeInfoPtr->defaultMAC = CRYPT_ALGO_HMAC_SHA;
	}
#endif /* USE_ENVELOPES */
