/****************************************************************************
*																			*
*						cryptlib CMS Pre-enveloping Routines				*
*					    Copyright Peter Gutmann 1996-2025					*
*																			*
****************************************************************************/

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

#ifdef USE_CMS

/****************************************************************************
*																			*
*						Encrypted Content Pre-processing					*
*																			*
****************************************************************************/

/* Create a context for a particular envelope action type */

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1 ) ) \
static int createActionContext( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr,
								IN_ENUM( ACTION ) const ACTION_TYPE actionType,
								IN_ALGO const CRYPT_ALGO_TYPE cryptAlgo,
								IN_HANDLE_OPT \
									const CRYPT_CONTEXT iMasterKeyContext )
	{
	CRYPT_CONTEXT iActionContext;
	const ACTION_LIST *actionListPtr = \
					DATAPTR_GET( envelopeInfoPtr->actionList );
	MESSAGE_CREATEOBJECT_INFO createInfo;
	int status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( actionListPtr == NULL || \
			isReadPtr( actionListPtr, sizeof( ACTION_LIST ) ) );

	REQUIRES( actionType == ACTION_CRYPT || actionType == ACTION_MAC || \
			  actionType == ACTION_xxx );
	REQUIRES( isConvAlgo( cryptAlgo ) || isMacAlgo( cryptAlgo ) || \
			  isSpecialAlgo( cryptAlgo ) );
	REQUIRES( iMasterKeyContext == CRYPT_UNUSED || \
			  isHandleRangeValid( iMasterKeyContext ) );
	REQUIRES( DATAPTR_ISVALID( envelopeInfoPtr->actionList ) );

	/* Make sure that we can still add another action */
	if( !moreActionsPossible( actionListPtr ) )
		return( CRYPT_ERROR_OVERFLOW );

	/* Create a the appropriate context type and either generate a key for 
	   it if we're using standard encryption/authentication or derive a key
	   from the supplied generic-secret context if we're using authenticated 
	   encryption */
	setMessageCreateObjectInfo( &createInfo, cryptAlgo );
	status = krnlSendMessage( CRYPTO_OBJECT_HANDLE,
							  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
							  OBJECT_TYPE_CONTEXT );
	if( cryptStatusError( status ) )
		return( status );
	iActionContext = createInfo.cryptHandle;
	if( iMasterKeyContext == CRYPT_UNUSED )
		{
		/* We're using standard encryption or authentication, generate a key
		   into the context */
		status = krnlSendNotifier( iActionContext, IMESSAGE_CTX_GENKEY );
		}
	else
		{
		MECHANISM_KDF_INFO mechanismInfo;

		/* We're using authenticated encryption, derive the key for the 
		   context from the generic-secret context */
		if( actionType == ACTION_CRYPT )
			{
			setMechanismKDFInfo( &mechanismInfo, iActionContext, 
								 iMasterKeyContext, 
								 envelopeInfoPtr->defaultMAC, 
								 "encryption", 10 );
			}
		else
			{
			setMechanismKDFInfo( &mechanismInfo, iActionContext, 
								 iMasterKeyContext, 
								 envelopeInfoPtr->defaultMAC, 
								 "authentication", 14 );
			}
		status = krnlSendMessage( MECHANISM_OBJECT_HANDLE, IMESSAGE_DEV_KDF,
								  &mechanismInfo, MECHANISM_DERIVE_PBKDF2 );
		}
	if( cryptStatusError( status ) )
		{
		krnlSendNotifier( iActionContext, IMESSAGE_DECREFCOUNT );
		return( status );
		}

	/* Add the context to the action list */
	status = addAction( envelopeInfoPtr, actionType, iActionContext );
	if( cryptStatusError( status ) )
		{
		krnlSendNotifier( iActionContext, IMESSAGE_DECREFCOUNT );
		return( status );
		}

	return( CRYPT_OK );
	}

/* Create the contexts needed for the enveloping process */

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1 ) ) \
static int createEnvelopeContexts( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr )
	{
	const ACTION_LIST *actionListPtr;
	int status;

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

	REQUIRES( DATAPTR_ISNULL( envelopeInfoPtr->actionList ) );

	switch( envelopeInfoPtr->usage )
		{
		case ACTION_CRYPT:
			/* If we're performing straight encryption, there's only one 
			   context to create */
			if( !TEST_FLAG( envelopeInfoPtr->flags, ENVELOPE_FLAG_AUTHENC ) )
				{
				return( createActionContext( envelopeInfoPtr, ACTION_CRYPT,
											 envelopeInfoPtr->defaultAlgo,
											 CRYPT_UNUSED ) );
				}

			/* We're performing authenticated encryption, we need to create 
			   a generic-secret context for the master secret and separate 
			   encryption and MAC contexts to provide the protection */
			status = createActionContext( envelopeInfoPtr, ACTION_xxx,
										  CRYPT_IALGO_GENERIC_SECRET,
										  CRYPT_UNUSED );
			if( cryptStatusError( status ) )
				return( status );
			actionListPtr = findAction( envelopeInfoPtr, ACTION_xxx ); 
			ENSURES( actionListPtr != NULL );
			REQUIRES( sanityCheckActionList( actionListPtr ) );
			status = createActionContext( envelopeInfoPtr, ACTION_CRYPT,
										  envelopeInfoPtr->defaultAlgo,
										  actionListPtr->iCryptHandle );
			if( cryptStatusOK( status ) )
				{
				status = createActionContext( envelopeInfoPtr, ACTION_MAC,
											  envelopeInfoPtr->defaultMAC,
											  actionListPtr->iCryptHandle );
				}
			return( status );

		case ACTION_MAC:
			return( createActionContext( envelopeInfoPtr, ACTION_MAC,
										 envelopeInfoPtr->defaultMAC,
										 CRYPT_UNUSED ) );

		default:
			retIntError();
		}

	retIntError();
	}

/* Process an individual key exchange action for the main envelope action */

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
static int processKeyexchangeAction( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr,
									 INOUT_PTR ACTION_LIST *preActionListPtr,
									 IN_HANDLE_OPT \
										const CRYPT_DEVICE iCryptDevice,
									 INOUT_PTR ERROR_INFO *errorInfo )
	{
	ACTION_LIST *actionListPtr = DATAPTR_GET( envelopeInfoPtr->actionList );
	int keyexAlgorithm DUMMY_INIT, keyexFormat = CRYPT_PKCFORMAT_DEFAULT;
	int status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( isWritePtr( preActionListPtr, sizeof( ACTION_LIST ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	
	REQUIRES( preActionListPtr != NULL && \
			  ( preActionListPtr->action == ACTION_KEYEXCHANGE_PKC || \
				preActionListPtr->action == ACTION_KEYEXCHANGE ) );
	REQUIRES( iCryptDevice == CRYPT_UNUSED || \
			  isHandleRangeValid( iCryptDevice ) );
	REQUIRES( actionListPtr != NULL );
	REQUIRES( sanityCheckActionList( actionListPtr ) );

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

		status = krnlSendMessage( preActionListPtr->iCryptHandle,
								  MESSAGE_GETDEPENDENT, &iKeyexDevice,
								  OBJECT_TYPE_DEVICE );
		if( cryptStatusError( status ) || iCryptDevice != iKeyexDevice )
			{
			retExt( CRYPT_ERROR_INVALID,
					( CRYPT_ERROR_INVALID, errorInfo,
					  "%s object must be associated with the same crypto "
					  "device as the key exchange object",
					  ( envelopeInfoPtr->usage == ACTION_CRYPT ) ? \
						"Session key" : "MAC key" ) );
			}
		}

	/* Connect the controller to the subject and remember that this action 
	   now has a controlling action */
	DATAPTR_SET( preActionListPtr->associatedAction, actionListPtr );
	CLEAR_FLAG( actionListPtr->flags, ACTION_FLAG_NEEDSCONTROLLER );

	/* Evaluate the size of the exported action.  If it's a conventional key
	   exchange then we force the use of the CMS format since there's no 
	   reason to use the cryptlib format.  Note that this assumes that the 
	   first action is the one that we'll be exporting the key for, which is 
	   required for authenticated encryption where there can be multiple 
	   actions (one for encryption and one for authentication) alongside the
	   generic-secret action present */
	status = iCryptExportKey( NULL, 0, &preActionListPtr->encodedSize, 
						( preActionListPtr->action == ACTION_KEYEXCHANGE ) ? \
							CRYPT_FORMAT_CMS : envelopeInfoPtr->type,
						actionListPtr->iCryptHandle, 
						preActionListPtr->iCryptHandle, errorInfo );
	if( cryptStatusOK( status ) )
		{
		status = krnlSendMessage( preActionListPtr->iCryptHandle,
								  IMESSAGE_GETATTRIBUTE, &keyexAlgorithm,
								  CRYPT_CTXINFO_ALGO );
		}
	if( cryptStatusOK( status ) && keyexAlgorithm == CRYPT_ALGO_RSA )
		{
		status = krnlSendMessage( DEFAULTUSER_OBJECT_HANDLE, 
								  IMESSAGE_GETATTRIBUTE, &keyexFormat, 
								  CRYPT_OPTION_PKC_FORMAT );
		}
	if( cryptStatusError( status ) )
		return( status );

	/* If there are any key exchange actions that will result in indefinite-
	   length encodings present then we can't use a definite-length encoding 
	   for the key exchange actions.
	   
	   In theory we could opportunistically generate a deterministic encoding 
	   by counting the number of keyex actions and, if there's only one 
	   present and it's not overly long (which is almost always the case) 
	   emitting it into the currently-unused space at the end of the envelope
	   buffer, and then memmove()ing it into place in cms_env.c.  However 
	   this isn't really worth the effort as it's only ever going to be used
	   with the virtually nonexistent Elgamal and RSA-OAEP keyex mechanisms,
	   and adds a lot of extra complexity and things to go wrong */
	if( isDlpAlgo( keyexAlgorithm ) || isEccAlgo( keyexAlgorithm ) )
		return( OK_SPECIAL );
	if( keyexFormat == CRYPT_PKCFORMAT_OAEP )
		return( OK_SPECIAL );
	return( CRYPT_OK );
	}

/* Pre-process information for encrypted enveloping */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int cmsPreEnvelopeEncrypt( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr )
	{
	CRYPT_DEVICE iCryptDevice = CRYPT_UNUSED;
	LOOP_INDEX_PTR ACTION_LIST *actionListPtr = \
					DATAPTR_GET( envelopeInfoPtr->actionList );
	BOOLEAN hasIndefSizeActions = FALSE;
	int totalSize, status;

	assert( isWritePtr( envelopeInfoPtr, sizeof( ENVELOPE_INFO ) ) );
	assert( actionListPtr == NULL || \
			isReadPtr( actionListPtr, sizeof( ACTION_LIST ) ) );

	REQUIRES( sanityCheckEnvelope( envelopeInfoPtr ) );
	REQUIRES( envelopeInfoPtr->usage == ACTION_CRYPT || \
			  envelopeInfoPtr->usage == ACTION_MAC );
	REQUIRES( DATAPTR_ISVALID( envelopeInfoPtr->actionList ) );

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

	/* Create the enveloping context(s) if necessary */
	if( actionListPtr == NULL )
		{
		status = createEnvelopeContexts( envelopeInfoPtr );
		if( cryptStatusError( status ) )
			{
			retExt( status,
					( status, ENVELOPE_ERRINFO,
					  "Couldn't create encryption contexts for the "
					  "enveloping process" ) );
			}
		actionListPtr = DATAPTR_GET( envelopeInfoPtr->actionList );
		}
	else
		{
		/* If the encryption/MAC context is tied to a device, get its handle 
		   so that we can check that all key exchange objects are also in the 
		   same device.

		   In theory if we're using a device for our crypto and performing
		   authenticated encryption then we'd need to check that all of the 
		   generic-secret, encryption and MAC contexts are contained in the 
		   same device, however since we don't allow these to be explicitly 
		   set by the user (the encryption and MAC keys are derived from the 
		   generic-secret context so it's not possible to set a key-loaded 
		   encryption/MAC context) this can never occur */
		REQUIRES( DATAPTR_ISNULL( actionListPtr->next ) );
		status = krnlSendMessage( actionListPtr->iCryptHandle,
								  MESSAGE_GETDEPENDENT, &iCryptDevice,
								  OBJECT_TYPE_DEVICE );
		if( cryptStatusError( status ) )
			iCryptDevice = CRYPT_UNUSED;
		}
	REQUIRES( actionListPtr != NULL );
	REQUIRES( sanityCheckActionList( actionListPtr ) );

	/* If we're performing straight encryption or MACing, notify the kernel 
	   that the encryption/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.  If we're
	   performing authenticated encryption then we can't do this because 
	   we're going via an intermediate generic-secret object from which keys 
	   will be diversified into distinct encryption and MAC objects */
	if( !( envelopeInfoPtr->usage == ACTION_CRYPT && \
		   TEST_FLAG( envelopeInfoPtr->flags, ENVELOPE_FLAG_AUTHENC ) ) )
		{
		REQUIRES( DATAPTR_ISNULL( actionListPtr->next ) );

		status = krnlSendMessage( envelopeInfoPtr->objectHandle, 
								  IMESSAGE_SETDEPENDENT,
								  &actionListPtr->iCryptHandle,
								  SETDEP_OPTION_NOINCREF );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Now walk down the list of key exchange actions evaluating their size
	   and connecting each one to the encryption/MAC/generic-secret action */
	totalSize = 0; 
	LOOP_MED( actionListPtr = DATAPTR_GET( envelopeInfoPtr->preActionList ), 
			  actionListPtr != NULL,
			  actionListPtr = DATAPTR_GET( actionListPtr->next ) )
		{
		ERROR_INFO localErrorInfo;

		REQUIRES( sanityCheckActionList( actionListPtr ) );

		ENSURES( LOOP_INVARIANT_MED_GENERIC() );

		clearErrorInfo( &localErrorInfo );
		status = processKeyexchangeAction( envelopeInfoPtr, actionListPtr,
										   iCryptDevice, &localErrorInfo );
		if( cryptStatusError( status ) )
			{
			/* An OK_SPECIAL state means that this keyex action will result 
			   in an indefinite-length encoding */
			if( status != OK_SPECIAL )
				{
				retExtErr( status,
						   ( status, ENVELOPE_ERRINFO, &localErrorInfo, 
							 "Couldn't calculate key exchange size for "
							 "envelope header" ) );
				}
			hasIndefSizeActions = TRUE;
			}
		REQUIRES( !checkOverflowAdd( totalSize, 
									 actionListPtr->encodedSize ) );
		totalSize += actionListPtr->encodedSize;
		}
	ENSURES( LOOP_BOUND_OK );
	envelopeInfoPtr->cryptActionSize = hasIndefSizeActions ? \
									   CRYPT_UNUSED : totalSize;
	ENSURES( envelopeInfoPtr->cryptActionSize == CRYPT_UNUSED || \
			 isBufsizeRangeNZ( envelopeInfoPtr->cryptActionSize ) );

	/* If we're MACing the data (either directly or because we're performing
	   authenticated encryption), hashing is now active.  The two actions 
	   have different flags because standalone MACing hashes plaintext while 
	   MACing as part of authenticated encryption hashes ciphertext */
	if( envelopeInfoPtr->usage == ACTION_MAC )
		{
		SET_FLAG( envelopeInfoPtr->dataFlags, 
				  ENVDATA_FLAG_HASHACTIONSACTIVE );
		}
	if( envelopeInfoPtr->usage == ACTION_CRYPT && \
		TEST_FLAG( envelopeInfoPtr->flags, ENVELOPE_FLAG_AUTHENC ) )
		{
		SET_FLAG( envelopeInfoPtr->dataFlags, 
				  ENVDATA_FLAG_AUTHENCACTIONSACTIVE );
		}

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						Signed Content Pre-processing						*
*																			*
****************************************************************************/

/* Set up signature parameters such as signature attributes and timestamps 
   if necessary */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 4 ) ) \
int cmsInitSigParams( const ACTION_LIST *actionListPtr,
					  IN_ENUM( CRYPT_FORMAT ) const CRYPT_FORMAT_TYPE formatType,
					  IN_HANDLE const CRYPT_USER iCryptOwner,
					  OUT_PTR SIG_PARAMS *sigParams )
	{
	BOOLEAN_INT useDefaultAttributes;
	int status;

	REQUIRES( sanityCheckActionList( actionListPtr ) );
	REQUIRES( formatType == CRYPT_FORMAT_CRYPTLIB || \
			  formatType == CRYPT_FORMAT_CMS || \
			  formatType == CRYPT_FORMAT_SMIME );
	REQUIRES( iCryptOwner == DEFAULTUSER_OBJECT_HANDLE || \
			  isHandleRangeValid( iCryptOwner ) );

	assert( isReadPtr( actionListPtr, sizeof( ACTION_LIST ) ) );
	assert( isWritePtr( sigParams, sizeof( SIG_PARAMS ) ) );

	initSigParams( sigParams );

	/* If it's a raw signature, there are no additional signing parameters */
	if( formatType == CRYPT_FORMAT_CRYPTLIB )
		return( CRYPT_OK );

	/* Add the timestamping session if there's one present */
	if( actionListPtr->iTspSession != CRYPT_ERROR )
		sigParams->iTspSession = actionListPtr->iTspSession;

	/* If the caller has provided signing attributes, use those */
	if( actionListPtr->iExtraData != CRYPT_ERROR )
		{
		sigParams->iAuthAttr = actionListPtr->iExtraData;

		return( CRYPT_OK );
		}

	/* There are no siging attributes explicitly specified (which can only 
	   happen under circumstances controlled by the pre-envelope signing 
	   code) in which case we 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 */
	status = krnlSendMessage( iCryptOwner, IMESSAGE_GETATTRIBUTE,  
							  &useDefaultAttributes,
							  CRYPT_OPTION_CMS_DEFAULTATTRIBUTES );
	if( cryptStatusError( status ) )
		return( status );
	if( useDefaultAttributes )
		sigParams->useDefaultAuthAttr = TRUE;

	return( CRYPT_OK );
	}

/* Process signing certificates 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) */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int processSigningCerts( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr,
							    INOUT_PTR ACTION_LIST *actionListPtr )
	{
	int contentType, dummy, status;

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

	/* If we're including signing certificates and there are multiple 
	   signing certificates present add the currently-selected one to the 
	   overall certificate collection */
	if( !TEST_FLAG( envelopeInfoPtr->flags, 
					ENVELOPE_FLAG_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 */
	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 there are no signed attributes, we're done */
	if( actionListPtr->iExtraData == CRYPT_ERROR )
		return( CRYPT_OK );

	/* Make sure that the content-type in the attributes matches the actual 
	   content type by deleting any existing content-type if necessary and 
	   adding our one (quietly fixing things is easier than trying to report 
	   this error back to the caller - ex duobus malis minimum eligendum 
	   est) */
	if( krnlSendMessage( actionListPtr->iExtraData, 
						 IMESSAGE_GETATTRIBUTE, &dummy, 
						 CRYPT_CERTINFO_CMS_CONTENTTYPE ) != CRYPT_ERROR_NOTFOUND )
		{
		/* There's already a content-type present, delete it so that we can 
		   add our one.  We ignore the return status from the deletion since 
		   the status from the add that follows will be more meaningful to 
		   the caller */
		( void ) krnlSendMessage( actionListPtr->iExtraData, 
								  IMESSAGE_DELETEATTRIBUTE, NULL, 
								  CRYPT_CERTINFO_CMS_CONTENTTYPE );
		}
	contentType = envelopeInfoPtr->contentType;	/* int vs.enum */
	return( krnlSendMessage( actionListPtr->iExtraData, 
							 IMESSAGE_SETATTRIBUTE, &contentType, 
							 CRYPT_CERTINFO_CMS_CONTENTTYPE ) );
	}

/* Pre-process information for signed enveloping */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int processSignatureAction( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr,
								   INOUT_PTR ACTION_LIST *actionListPtr )
	{
	const ACTION_LIST *associatedActionPtr;
	SIG_DATA_INFO sigDataInfo;
	SIG_PARAMS sigParams;
	ERROR_INFO localErrorInfo;
	int signatureAlgo DUMMY_INIT, signatureSize, status;

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

	REQUIRES( actionListPtr != NULL && \
			  actionListPtr->action == ACTION_SIGN && \
			  DATAPTR_ISSET( actionListPtr->associatedAction ) );

	/* Process signing certificates and fix up the content-type in the 
	   authenticated attributes if necessary */
	if( envelopeInfoPtr->type == CRYPT_FORMAT_CMS || \
		envelopeInfoPtr->type == CRYPT_FORMAT_SMIME )
		{
		status = processSigningCerts( envelopeInfoPtr, actionListPtr );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Set up any necessary signature parameters such as signature 
	  attributes and timestamps if necessary */
	status = cmsInitSigParams( actionListPtr, envelopeInfoPtr->type, 
							   envelopeInfoPtr->ownerHandle, 
							   &sigParams );
	if( cryptStatusError( status ) )
		return( status );

	/* Evaluate the size of the exported action */
	clearErrorInfo( &localErrorInfo );
	associatedActionPtr = DATAPTR_GET( actionListPtr->associatedAction );
	REQUIRES( associatedActionPtr != NULL );
	setSigDataInfoHash( &sigDataInfo, associatedActionPtr->iCryptHandle );
	status = iCryptCreateSignature( NULL, 0, &signatureSize, 
						envelopeInfoPtr->type, actionListPtr->iCryptHandle,
						&sigDataInfo,
						( envelopeInfoPtr->type == CRYPT_FORMAT_CRYPTLIB ) ? \
							NULL : &sigParams, &localErrorInfo );
	if( cryptStatusOK( status ) )
		{
		status = krnlSendMessage( actionListPtr->iCryptHandle,
								  IMESSAGE_GETATTRIBUTE, &signatureAlgo,
								  CRYPT_CTXINFO_ALGO );
		}
	if( cryptStatusError( status ) )
		{
		retExtErr( status,
				   ( status, ENVELOPE_ERRINFO, &localErrorInfo,
					 "Couldn't calculate signature size for envelope "
					 "trailer" ) );
		}
	if( isDlpAlgo( signatureAlgo ) || isEccAlgo( signatureAlgo ) || \
		actionListPtr->iTspSession != CRYPT_ERROR )
		{
		/* If there are any signature actions that will result in indefinite-
		   length encodings present then we can't use a definite-length 
		   encoding for the signature */
		SET_FLAG( envelopeInfoPtr->dataFlags, ENVDATA_FLAG_HASINDEFTRAILER );
		actionListPtr->encodedSize = CRYPT_UNUSED;
		}
	else
		{
		actionListPtr->encodedSize = signatureSize;
		REQUIRES( !checkOverflowAdd( envelopeInfoPtr->signActionSize, 
									 signatureSize ) );
		envelopeInfoPtr->signActionSize += signatureSize;
		}
	if( TEST_FLAG( envelopeInfoPtr->dataFlags, 
				   ENVDATA_FLAG_HASINDEFTRAILER ) )
		envelopeInfoPtr->signActionSize = CRYPT_UNUSED;
	ENSURES( envelopeInfoPtr->signActionSize == CRYPT_UNUSED || \
			 isBufsizeRangeNZ( envelopeInfoPtr->signActionSize ) );

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int cmsPreEnvelopeSign( INOUT_PTR ENVELOPE_INFO *envelopeInfoPtr )
	{
	const ACTION_LIST *postActionListPtr;
	LOOP_INDEX_PTR ACTION_LIST *actionListPtr;
	int status;

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

	REQUIRES( sanityCheckEnvelope( envelopeInfoPtr ) );
	REQUIRES( envelopeInfoPtr->usage == ACTION_SIGN );

	postActionListPtr = DATAPTR_GET( envelopeInfoPtr->postActionList );
	REQUIRES( postActionListPtr != NULL );
	REQUIRES( sanityCheckActionList( postActionListPtr ) );
	REQUIRES( DATAPTR_ISSET( postActionListPtr->associatedAction ) );

	/* If we're generating a detached signature then the content is supplied
	   externally and has zero size */
	if( TEST_FLAG( envelopeInfoPtr->flags, ENVELOPE_FLAG_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( TEST_FLAG( envelopeInfoPtr->flags, ENVELOPE_FLAG_ATTRONLY ) )
		{
		if( envelopeInfoPtr->type != CRYPT_FORMAT_CMS || \
			postActionListPtr->iExtraData == CRYPT_ERROR )
			{
			setObjectErrorInfo( envelopeInfoPtr, 
								CRYPT_ENVINFO_SIGNATURE_EXTRADATA,
								CRYPT_ERRTYPE_ATTR_ABSENT );
			return( CRYPT_ERROR_NOTINITED );
			}
		if( envelopeInfoPtr->payloadSize > 0 )
			{
			setObjectErrorInfo( envelopeInfoPtr, CRYPT_ENVINFO_DATASIZE,
								CRYPT_ERRTYPE_ATTR_VALUE );
			return( CRYPT_ERROR_INITED );
			}
		}

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

			/* There are multiple sets of signing certificates present, 
			   create a signing-certificate meta-object to hold the overall 
			   set of certificates */
			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 certificate present, determine its 
			   size */
			setMessageData( &msgData, NULL, 0 );
			status = krnlSendMessage( postActionListPtr->iCryptHandle,
									  IMESSAGE_CRT_EXPORT, &msgData,
									  CRYPT_ICERTFORMAT_CERTSET );
			if( cryptStatusError( status ) )
				return( status );
			envelopeInfoPtr->extraDataSize = msgData.length;
			}
		}

	/* Evaluate the size of each signature action */
	LOOP_MED( actionListPtr = ( ACTION_LIST * ) postActionListPtr,  
			  actionListPtr != NULL,
			  actionListPtr = DATAPTR_GET( actionListPtr->next ) )
		{
		REQUIRES( sanityCheckActionList( actionListPtr ) );

		ENSURES( LOOP_INVARIANT_MED_GENERIC() );

		status = processSignatureAction( envelopeInfoPtr, actionListPtr );
		if( cryptStatusError( status ) )
			return( status );
		}
	ENSURES( LOOP_BOUND_OK );

	/* If we're writing the signing certificate chain and there are multiple 
	   signing certificates present, get the size of the overall certificate 
	   collection */
	if( envelopeInfoPtr->iExtraCertChain != CRYPT_ERROR )
		{
		MESSAGE_DATA msgData;

		setMessageData( &msgData, NULL, 0 );
		status = krnlSendMessage( envelopeInfoPtr->iExtraCertChain,
								  IMESSAGE_CRT_EXPORT, &msgData,
								  CRYPT_ICERTFORMAT_CERTSET );
		if( cryptStatusError( status ) )
			return( status );
		envelopeInfoPtr->extraDataSize = msgData.length;
		}
	ENSURES( isBufsizeRange( envelopeInfoPtr->extraDataSize ) );

	/* Hashing is now active (you have no chance to survive make your 
	   time) */
	SET_FLAG( envelopeInfoPtr->dataFlags, ENVDATA_FLAG_HASHACTIONSACTIVE );

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