/****************************************************************************
*																			*
*						 cryptlib CMP Server Management						*
*						Copyright Peter Gutmann 1999-2019					*
*																			*
****************************************************************************/

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

#ifdef USE_CMP

/****************************************************************************
*																			*
*								Utility Routines							*
*																			*
****************************************************************************/

/* Map request to response types */

static const MAP_TABLE reqClibReqMapTbl[] = {
	{ CTAG_PB_IR, CRYPT_REQUESTTYPE_INITIALISATION },
	{ CTAG_PB_CR, CRYPT_REQUESTTYPE_CERTIFICATE },
	{ CTAG_PB_P10CR, CRYPT_REQUESTTYPE_CERTIFICATE },
	{ CTAG_PB_KUR, CRYPT_REQUESTTYPE_KEYUPDATE },
	{ CTAG_PB_RR, CRYPT_REQUESTTYPE_REVOCATION },
	{ CTAG_PB_GENM, CRYPT_REQUESTTYPE_PKIBOOT },
	{ CRYPT_ERROR, CRYPT_ERROR }, { CRYPT_ERROR, CRYPT_ERROR }
	};

CHECK_RETVAL_RANGE( CRYPT_REQUESTTYPE_NONE, CRYPT_REQUESTTYPE_LAST ) \
static int reqToClibReq( IN_ENUM_OPT( CMP_MESSAGE ) \
							const CMP_MESSAGE_TYPE reqType )
	{
	int value, status;

	REQUIRES( reqType >= CTAG_PB_IR && reqType < CTAG_PB_LAST );
			  /* CTAG_PB_IR == 0 so this is the same as _NONE */

	status = mapValue( reqType, &value, reqClibReqMapTbl, 
					   FAILSAFE_ARRAYSIZE( reqClibReqMapTbl, MAP_TABLE ) );
	return( cryptStatusError( status ) ? status : value );
	}

/* Set up user authentication information (either a MAC context or a public 
   key) based on a request submitted by the client.  This is done whenever 
   the client starts a new transaction with a new user ID or certificate 
   ID */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int initServerAuthentMAC( INOUT_PTR SESSION_INFO *sessionInfoPtr, 
						  INOUT_PTR CMP_PROTOCOL_INFO *protocolInfo )
	{
	CMP_INFO *cmpInfo = sessionInfoPtr->sessionCMP;
	MESSAGE_KEYMGMT_INFO getkeyInfo;
	MESSAGE_DATA msgData;
	char password[ CRYPT_MAX_TEXTSIZE + 8 ];
	int status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) );

	REQUIRES( sanityCheckSessionCMP( sessionInfoPtr ) );
	REQUIRES( sanityCheckCMPProtocolInfo( protocolInfo ) );

	/* Set up general authentication information and if there's user 
	   information still present from a previous transaction, clear it */
	status = setCMPprotocolInfo( protocolInfo, NULL, 0, 
								 CMP_INIT_FLAG_MACCTX, TRUE );
	if( cryptStatusError( status ) )
		return( status );
	if( cmpInfo->userInfo != CRYPT_ERROR )
		{
		krnlSendNotifier( cmpInfo->userInfo, IMESSAGE_DECREFCOUNT );
		cmpInfo->userInfo = CRYPT_ERROR;
		}
	if( protocolInfo->altMacKeySize > 0 )
		{
		zeroise( protocolInfo->altMacKey, CRYPT_MAX_HASHSIZE );
		protocolInfo->altMacKeySize = 0;
		}
	protocolInfo->userIsRA = FALSE;

	/* Get the user information for the user identified by the user ID from 
	   the certificate store.  If we get a not-found error we report it as 
	   "signer not trusted", which can also mean "signer unknown" */
	setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_KEYID,
						   protocolInfo->userID, protocolInfo->userIDsize, 
						   NULL, 0, KEYMGMT_FLAG_NONE );
	status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
							  IMESSAGE_KEY_GETKEY, &getkeyInfo, 
							  KEYMGMT_ITEM_PKIUSER );
	if( cryptStatusError( status ) )
		{
		const ATTRIBUTE_LIST *userNamePtr = \
					findSessionInfo( sessionInfoPtr, CRYPT_SESSINFO_USERNAME );
		char userID[ CRYPT_MAX_TEXTSIZE + 8 ];
		int userIDlen;

		REQUIRES( userNamePtr != NULL );
		if( TEST_FLAG( userNamePtr->flags, ATTR_FLAG_ENCODEDVALUE ) )
			{
			userIDlen = min( userNamePtr->valueLength, CRYPT_MAX_TEXTSIZE );
			REQUIRES( rangeCheck( userIDlen, 1, CRYPT_MAX_TEXTSIZE ) );
			memcpy( userID, userNamePtr->value, userIDlen );
			}
		else
			{
			/* The ID isn't a cryptlib user ID but a certificate ID used for 
			   a cr rather than the user ID for an ir or rr (see the comment 
			   at the start of initServerAuthentSign() for details), we have 
			   to use a placeholder string since it's meaningless binary 
			   data.  We can't try and map this to a user ID since we've just 
			   failed to find the PKI user object that would contain it.
			   
			   Alternatively, in less likely cases it could also be an 
			   incorrect user ID that we couldn't recognise and record as a 
			   cryptlib user ID, which we can't do much with either */
			userIDlen = 18;
			REQUIRES( rangeCheck( userIDlen, 1, CRYPT_MAX_TEXTSIZE ) );
			memcpy( userID, "the requested user", userIDlen );
			}
		protocolInfo->pkiFailInfo = CMPFAILINFO_SIGNERNOTTRUSTED;
		retExtObj( status, 
				   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
					 "Couldn't find PKI user information for %s",
					 sanitiseString( userID, CRYPT_MAX_TEXTSIZE, 
									 userIDlen ) ) );
		}
	cmpInfo->userInfo = getkeyInfo.cryptHandle;
	protocolInfo->userIDchanged = FALSE;

	/* Get the password from the PKI user object */
	setMessageData( &msgData, password, CRYPT_MAX_TEXTSIZE );
	status = krnlSendMessage( cmpInfo->userInfo, IMESSAGE_GETATTRIBUTE_S, 
							  &msgData, CRYPT_CERTINFO_PKIUSER_ISSUEPASSWORD );
	if( cryptStatusOK( status ) )
		{
		status = updateSessionInfo( sessionInfoPtr, CRYPT_SESSINFO_PASSWORD, 
									password, msgData.length, 
									CRYPT_MAX_TEXTSIZE, 
									ATTR_FLAG_ENCODEDVALUE );
		}
	zeroise( password, CRYPT_MAX_TEXTSIZE );
	if( cryptStatusError( status ) )
		{
		retExt( status, 
				( status, SESSION_ERRINFO, 
				  "Couldn't copy PKI user data from PKI user object to "
				  "session object" ) );
		}

	/* Things get a bit complicated for password-authenticated operations 
	   because there's a second password that may (optionally) be used for
	   revocations that doesn't fit into the standard pattern of username+
	   password.  In order to handle MAC'd revocation requests, we keep a 
	   copy of the revocation MAC key and use it in place of the standard
	   password if we get a revocation request rather than an issue
	   request */
	setMessageData( &msgData, password, CRYPT_MAX_TEXTSIZE );
	status = krnlSendMessage( cmpInfo->userInfo, IMESSAGE_GETATTRIBUTE_S, 
							  &msgData, CRYPT_CERTINFO_PKIUSER_REVPASSWORD );
	if( cryptStatusOK( status ) )
		{
		status = decodePKIUserValue( protocolInfo->altMacKey, CRYPT_MAX_HASHSIZE, 
									 &protocolInfo->altMacKeySize, password, 
									 msgData.length );
		ENSURES( cryptStatusOK( status ) );
		}
	zeroise( password, CRYPT_MAX_TEXTSIZE );

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int initServerAuthentSign( INOUT_PTR SESSION_INFO *sessionInfoPtr, 
						   INOUT_PTR CMP_PROTOCOL_INFO *protocolInfo )
	{
	CMP_INFO *cmpInfo = sessionInfoPtr->sessionCMP;
	MESSAGE_KEYMGMT_INFO getkeyInfo;
	MESSAGE_DATA msgData;
	char userName[ CRYPT_MAX_TEXTSIZE + 8 ];
	BOOLEAN_INT isRA;
	int userNameLen DUMMY_INIT, status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) );

	REQUIRES( sanityCheckSessionCMP( sessionInfoPtr ) );
	REQUIRES( sanityCheckCMPProtocolInfo( protocolInfo ) );

	/* Set up general authentication information and if there's client 
	   authentication information still present from a previous transaction 
	   that used MAC authentication, clear it */
	status = setCMPprotocolInfo( protocolInfo, NULL, 0, CMP_INIT_FLAG_NONE,
								 TRUE );
	if( cryptStatusError( status ) )
		return( status );
	if( cmpInfo->userInfo != CRYPT_ERROR )
		{
		krnlSendNotifier( cmpInfo->userInfo, IMESSAGE_DECREFCOUNT );
		cmpInfo->userInfo = CRYPT_ERROR;
		}
	protocolInfo->userIsRA = FALSE;

	/* Get the user information for the user that originally authorised the 
	   issue of the certificate that signed the request.  This serves two 
	   purposes, it obtains the original user ID if it wasn't supplied in 
	   the request (for example if the request uses only a certificate ID 
	   from a certificate five generations downstream from the original PKI
	   user after an ir -> cr -> cr -> cr -> cr and we've got the 
	   certificate ID from the final cr in the series), and it verifies that 
	   the authorising certificate belongs to a currently valid user */
	setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_CERTID,
						   protocolInfo->certID, protocolInfo->certIDsize, 
						   NULL, 0, KEYMGMT_FLAG_GETISSUER );
	status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
							  IMESSAGE_KEY_GETKEY, &getkeyInfo, 
							  KEYMGMT_ITEM_PKIUSER );
	if( cryptStatusError( status ) )
		{
		protocolInfo->pkiFailInfo = CMPFAILINFO_SIGNERNOTTRUSTED;
		retExtObj( status, 
				   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
					 "Couldn't find PKI user information for owner of CMP "
					 "requesting certificate" ) );
		}
	cmpInfo->userInfo = getkeyInfo.cryptHandle;

	/* Update the user ID from the PKI user object and record whether the 
	   user is an RA rather than a standard user */
	setMessageData( &msgData, userName, CRYPT_MAX_TEXTSIZE );
	status = krnlSendMessage( getkeyInfo.cryptHandle, 
							  IMESSAGE_GETATTRIBUTE_S, &msgData,
							  CRYPT_CERTINFO_PKIUSER_ID );
	if( cryptStatusOK( status ) )
		{
		userNameLen = msgData.length;
		status = updateSessionInfo( sessionInfoPtr, CRYPT_SESSINFO_USERNAME, 
									userName, userNameLen, 
									CRYPT_MAX_TEXTSIZE, 
									ATTR_FLAG_ENCODEDVALUE );
		}
	if( cryptStatusError( status ) )
		{
		retExt( status, 
				( status, SESSION_ERRINFO, 
				  "Couldn't copy PKI user data from PKI user object to CMP "
				  "session object" ) );
		}
	status = krnlSendMessage( getkeyInfo.cryptHandle, 
							  IMESSAGE_GETATTRIBUTE, &isRA,
							  CRYPT_CERTINFO_PKIUSER_RA );
	if( cryptStatusOK( status ) && isRA == TRUE )
		protocolInfo->userIsRA = TRUE;

	/* Get the public key identified by the certificate ID from the 
	   certificate store.  This verifies the assumption that the owner of an 
	   existing certificate/existing user is authorised to request further 
	   certificates using the existing one.  If we get a not found error we 
	   report it as "signer not trusted", which can also mean "signer 
	   unknown" */
	setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_CERTID,
						   protocolInfo->certID, protocolInfo->certIDsize, 
						   NULL, 0, KEYMGMT_FLAG_USAGE_SIGN );
	status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
							  IMESSAGE_KEY_GETKEY, &getkeyInfo, 
							  KEYMGMT_ITEM_PUBLICKEY );
	if( cryptStatusError( status ) )
		{
		protocolInfo->pkiFailInfo = CMPFAILINFO_SIGNERNOTTRUSTED;
		retExtObj( status, 
				   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
					 "Certificate for requested PKI user '%s' couldn't be "
					 "fetched from certificate store",
					 sanitiseString( userName, CRYPT_MAX_TEXTSIZE, 
									 userNameLen ) ) );
		}
	sessionInfoPtr->iAuthInContext = getkeyInfo.cryptHandle;
	protocolInfo->certIDchanged = FALSE;

	return( CRYPT_OK );
	}

/* Clean up after a failed/aborted certificate issue */

STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int cleanupFailedIssue( INOUT_PTR SESSION_INFO *sessionInfoPtr,
							   INOUT_PTR CMP_PROTOCOL_INFO *protocolInfo,
							   IN_STATUS const int status )
	{
	MESSAGE_CERTMGMT_INFO certMgmtInfo;
	int localStatus;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) );

	REQUIRES( cryptStatusOK( status ) || \
			  cryptStatusError( status ) );

	destroyCMPprotocolInfo( protocolInfo );

	/* Reverse the certificate issue operation by revoking the incompletely-
	   issued certificate, returning the status that caused the failure if we 
	   were called because of an error */
	setMessageCertMgmtInfo( &certMgmtInfo, CRYPT_UNUSED,
							sessionInfoPtr->iCertResponse );
	localStatus = krnlSendMessage( sessionInfoPtr->cryptKeyset,
								   IMESSAGE_KEY_CERTMGMT, &certMgmtInfo,
								   CRYPT_CERTACTION_CERT_CREATION_REVERSE );
	if( cryptStatusOK( status ) )
		{
		/* This was an intended reversal, report any error that occurred in 
		   processing it */
		if( cryptStatusError( localStatus ) )
			{
#ifdef USE_ERRMSGS
			char certName[ CRYPT_MAX_TEXTSIZE + 8 ];
#endif /* USE_ERRMSGS */

			retExtObj( localStatus,
					   ( localStatus, SESSION_ERRINFO, 
					     sessionInfoPtr->cryptKeyset,
					     "Couldn't reverse CMP certificate issue operation "
						 "for '%s'",
						 getCertHolderName( sessionInfoPtr->iCertResponse, 
											certName, CRYPT_MAX_TEXTSIZE ) ) );
			}

		return( CRYPT_OK );
		}

	/* This was a reversal due to an error, report the original error */
	return( status );
	}

/* Deliver an Einladung betreff Kehrseite to the client.  We don't bother
   checking the return value for the write since there's nothing that we can 
   do in the case of an error except close the connection, which we do 
   anyway since this is the last message */

STDC_NONNULL_ARG( ( 1, 2 ) ) \
static void sendErrorResponse( INOUT_PTR SESSION_INFO *sessionInfoPtr,
							   INOUT_PTR CMP_PROTOCOL_INFO *protocolInfo,
							   IN_ERROR const int errorStatus )
	{
	BOOLEAN writeHttpResponseOnly = protocolInfo->headerRead ? FALSE : TRUE;
	int status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) );
	
	REQUIRES_V( cryptStatusError( errorStatus ) );

	/* If we were going to protect the communication with the client with a
	   MAC and something failed we don't try and MAC the response since the 
	   failure could be a client MAC failure, failure to locate the MAC key, 
	   or something similar */
	protocolInfo->useMACsend = FALSE;
	protocolInfo->status = errorStatus;

	/* Write the error response if we can.  We only do this if at least the
	   header of the client's message was successfully read (indicated by 
	   the headerRead flag in the protocolInfo being set), otherwise we 
	   can't create our own header for the response */
	sioctlSet( &sessionInfoPtr->stream, STREAM_IOCTL_LASTMESSAGE, TRUE );
	if( !writeHttpResponseOnly )
		{
		status = writePkiMessage( sessionInfoPtr, protocolInfo, CMPBODY_ERROR );
		if( cryptStatusError( status ) )
			writeHttpResponseOnly = TRUE;
		}
	if( writeHttpResponseOnly )
		{
		HTTP_DATA_INFO httpDataInfo;

		/* If we encounter an error processing the initial request then 
		   there won't be enough information available to create an error 
		   response.  At this point the best that we can do is send an error
		   at the HTTP level */
		status = initHttpInfoReq( &httpDataInfo );
		ENSURES_V( cryptStatusOK( status ) ); 
		httpDataInfo.reqStatus = errorStatus;
		( void ) swrite( &sessionInfoPtr->stream, &httpDataInfo, 
						 sizeof( HTTP_DATA_INFO ) );
		}
	else
		{
		DEBUG_DUMP_CMP( CTAG_PB_ERROR, 1, sessionInfoPtr );
		( void ) writePkiDatagram( sessionInfoPtr, CMP_CONTENT_TYPE, 
								   CMP_CONTENT_TYPE_LEN,
								   MK_ERRTEXT( "Couldn't send CMP error "
											   "response to client" ) );
		}
	}

/****************************************************************************
*																			*
*								Init/Shutdown Functions						*
*																			*
****************************************************************************/

/* Exchange data with a CMP client */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int serverTransact( INOUT_PTR SESSION_INFO *sessionInfoPtr )
	{
	CMP_INFO *cmpInfo = sessionInfoPtr->sessionCMP;
	MESSAGE_CERTMGMT_INFO certMgmtInfo;
	MESSAGE_KEYMGMT_INFO setkeyInfo;
	STREAM stream;
	CMP_PROTOCOL_INFO protocolInfo;
	const ATTRIBUTE_LIST *userNamePtr;
#ifdef USE_ERRMSGS
	char certName[ CRYPT_MAX_TEXTSIZE + 8 ];
#endif /* USE_ERRMSGS */
	CFI_CHECK_TYPE CFI_CHECK_VALUE = CFI_CHECK_INIT;
	int status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );

	REQUIRES( sanityCheckSessionCMP( sessionInfoPtr ) );

	/* Initialise the server-side protocol state information.  Since the 
	   server doesn't have a user ID (it uses what the client sends it), we 
	   set the userID-sent flag to indicate that it's been implicitly 
	   exchanged */
	status  = initCMPprotocolInfo( &protocolInfo, sessionInfoPtr, TRUE );
	if( cryptStatusError( status ) )
		return( status );
	SET_FLAG( sessionInfoPtr->protocolFlags, CMP_PFLAG_USERIDSENT );
	userNamePtr = findSessionInfo( sessionInfoPtr, CRYPT_SESSINFO_USERNAME );
	if( userNamePtr != NULL )
		{
		BYTE userID[ CRYPT_MAX_TEXTSIZE ];
		int userIDsize;

		/* There's already user information present from a previous 
		   transaction, try and re-use it (this can happen if we're
		   handling a series of transactions from the client, see the
		   dicussion in cmp.h for details).  This information can still
		   be overridden by the client sending us new user information */
		if( TEST_FLAG( userNamePtr->flags, ATTR_FLAG_ENCODEDVALUE ) )
			{
			/* It's a cryptlib-style encoded user ID, decode it into its 
			   binary value */
			status = decodePKIUserValue( userID, CRYPT_MAX_TEXTSIZE,
										 &userIDsize,
										 userNamePtr->value,
										 userNamePtr->valueLength );
			ENSURES( cryptStatusOK( status ) );
			}
		else
			{
			/* It's a standard user ID, use it as is */
			userIDsize = min( userNamePtr->valueLength, CRYPT_MAX_TEXTSIZE );
			REQUIRES( rangeCheck( userIDsize, 1, CRYPT_MAX_TEXTSIZE ) );
			memcpy( userID, userNamePtr->value, userIDsize );
			}
		status = setCMPprotocolInfo( &protocolInfo, userID, userIDsize,
									 CMP_INIT_FLAG_USERID, FALSE );
		if( cryptStatusError( status ) )
			{
			destroyCMPprotocolInfo( &protocolInfo );
			return( status );
			}

		/* We're (potentially) re-using user information from a previous 
		   session, if there are cryptographic credentials associated with
		   the user information we have to move them over to the current
		   protocol state data */
		if( cmpInfo->iSavedMacContext != CRYPT_ERROR )
			{
			protocolInfo.iMacContext = cmpInfo->iSavedMacContext;
			cmpInfo->iSavedMacContext = CRYPT_ERROR;
			}
		}
	CFI_CHECK_UPDATE( "initCMPprotocolInfo" );

	/* Read the initial message from the client.  We don't write an error
	   response at the initial read stage to prevent scanning/DOS attacks 
	   (vir sapit qui pauca loquitur) */
	status = readPkiDatagram( sessionInfoPtr, MIN_CRYPT_OBJECTSIZE,
							  MK_ERRTEXT( "Couldnt read CMP request from "
										  "client" ) );
	if( cryptStatusError( status ) )
		{
		destroyCMPprotocolInfo( &protocolInfo );
		return( status );
		}
	CFI_CHECK_UPDATE( "readPkiDatagram" );

	/* Basic lint filter to check for approximately-OK requests before we
	   try applying message-processing operations to the data:

		SEQUENCE {
			SEQUENCE {
				INTEGER 1	-- version
				... */
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer,
				 sessionInfoPtr->receiveBufEnd );
	readSequence( &stream, NULL );
	readSequence( &stream, NULL );
	status = readInteger( &stream, NULL, 2, NULL );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		{
		destroyCMPprotocolInfo( &protocolInfo );
		retExt( status, 
				( status, SESSION_ERRINFO, 
				  "Invalid CMP client request header" ) );
		}

	/* Process the initial client message */
	status = readPkiMessage( sessionInfoPtr, &protocolInfo,
							 CTAG_PB_READ_ANY );
	if( cryptStatusOK( status ) )
		{
		status = cmpInfo->requestType = \
					reqToClibReq( protocolInfo.operation );
		}
	if( cryptStatusError( status ) )
		{
		/* If we're fuzzing the input then we're done, the writes below are 
		   turned into no-ops but the response still requires a private-key 
		   operation so we exit before we get there */
		FUZZ_EXIT();

		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		destroyCMPprotocolInfo( &protocolInfo );
		return( status );
		}
	DEBUG_DUMP_CMP( protocolInfo.operation, 1, sessionInfoPtr );
	CFI_CHECK_UPDATE( "readPkiMessage" );

	/* If we're fuzzing the input then we're reading static data for which 
	   we can't go beyond this point */
	FUZZ_EXIT();

	/* If it's a PKIBoot request, send the PKIBoot response */
	if( cmpInfo->requestType == CRYPT_REQUESTTYPE_PKIBOOT )
		{
		status = writePkiMessage( sessionInfoPtr, &protocolInfo, 
								  CMPBODY_GENMSG );
		if( cryptStatusError( status ) )
			{
			delayRandom();	/* Dither error timing info */
			sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
			destroyCMPprotocolInfo( &protocolInfo );
			return( status );
			}
		DEBUG_DUMP_CMP( CTAG_PB_GENM, 2, sessionInfoPtr );
		status = writePkiDatagram( sessionInfoPtr, CMP_CONTENT_TYPE, 
								   CMP_CONTENT_TYPE_LEN,
								   MK_ERRTEXT( "Couldn't send PKIBoot "
											   "response to CMP client" ) );
		if( cryptStatusError( status ) )
			{
			destroyCMPprotocolInfo( &protocolInfo );
			return( status );
			}

		/* Remember the authentication context in case we can reuse it for 
		   another transaction */
		cmpInfo->iSavedMacContext = protocolInfo.iMacContext;
		protocolInfo.iMacContext = CRYPT_ERROR;

		ENSURES( CFI_CHECK_SEQUENCE_3( "initCMPprotocolInfo", "readPkiDatagram", 
									   "readPkiMessage" ) );

		return( CRYPT_OK );
		}
	CFI_CHECK_UPDATE( "PKIBoot" );

	/* Make sure that the signature on the request data is OK (unless it's a 
	   non-signed revocation request or a request for an encryption-only 
	   key) */
	if( protocolInfo.operation != CTAG_PB_RR && !protocolInfo.cryptOnlyKey )
		{
		status = krnlSendMessage( sessionInfoPtr->iCertRequest,
								  IMESSAGE_CRT_SIGCHECK, NULL, 
								  CRYPT_UNUSED );
		if( cryptStatusError( status ) )
			{
			delayRandom();	/* Dither error timing info */
			sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
			destroyCMPprotocolInfo( &protocolInfo );
			if( cryptArgError( status ) )
				{
				/* There's a problem with one of the parameters, convert the
				   error status to a general invalid-information error */
				status = CRYPT_ERROR_INVALID;
				}
			retExtObj( status, 
					   ( status, SESSION_ERRINFO, 
					     sessionInfoPtr->iCertRequest,
						 "CMP client request signature check failed" ) );
			}
		}
	CFI_CHECK_UPDATE( "IMESSAGE_CRT_SIGCHECK" );

	/* Add the request to the certificate store */
	setMessageKeymgmtInfo( &setkeyInfo, CRYPT_KEYID_NONE, NULL, 0, NULL, 0,
						   ( protocolInfo.operation == CTAG_PB_IR ) ? \
								KEYMGMT_FLAG_INITIALOP : \
						   ( protocolInfo.operation == CTAG_PB_KUR ) ? \
								KEYMGMT_FLAG_UPDATE : KEYMGMT_FLAG_NONE );
	setkeyInfo.cryptHandle = sessionInfoPtr->iCertRequest;
	status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
							  IMESSAGE_KEY_SETKEY, &setkeyInfo, 
							  ( protocolInfo.operation == CTAG_PB_RR ) ? \
								KEYMGMT_ITEM_REVREQUEST : \
								KEYMGMT_ITEM_REQUEST );
	if( cryptStatusError( status ) )
		{
		/* If the certificate store reports that there's a problem with the 
		   request, convert it to an invalid request error */
		if( cryptArgError( status ) )
			status = CRYPT_ERROR_INVALID;

		/* A special error condition at this point arises when the user 
		   tries to submit a second initialisation request for a PKI user 
		   that has already had a certificate issued for it, so we catch 
		   this condition and provide a more informative error response to 
		   the client than the generic failure response */
		if( protocolInfo.operation == CTAG_PB_IR && \
			status == CRYPT_ERROR_DUPLICATE )
			protocolInfo.pkiFailInfo = CMPFAILINFO_DUPLICATECERTREQ;

		/* Clean up and return the appropriate error information to the
		   caller */
		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		destroyCMPprotocolInfo( &protocolInfo );
		if( protocolInfo.operation == CTAG_PB_IR && \
			status == CRYPT_ERROR_DUPLICATE )
			{
			retExtObj( status, 
					   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
						 "CMP initialisation request for '%s' couldn't be "
						 "added to the certificate store because another "
						 "initialisation request has already been processed "
						 "for this user",
						 getCertHolderName( sessionInfoPtr->iCertRequest, 
											certName, CRYPT_MAX_TEXTSIZE ) ) );
			}
		retExtObj( status, 
				   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
					 "CMP %s request for '%s' couldn't be added to the "
					 "certificate store", 
					 ( protocolInfo.operation == CTAG_PB_IR ) ? \
					   "initialisation" : \
					 ( protocolInfo.operation == CTAG_PB_KUR ) ? \
					   "key update" : \
					 ( protocolInfo.operation == CTAG_PB_RR ) ? \
					   "revocation" : "certificate",
					 getCertHolderName( sessionInfoPtr->iCertRequest, 
										certName, CRYPT_MAX_TEXTSIZE ) ) );
 		}
	CFI_CHECK_UPDATE( "IMESSAGE_KEY_SETKEY" );

	/* Create or revoke a certificate from the request */
	if( protocolInfo.operation != CTAG_PB_RR )
		{
		setMessageCertMgmtInfo( &certMgmtInfo, sessionInfoPtr->privateKey,
								sessionInfoPtr->iCertRequest );
		status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
								  IMESSAGE_KEY_CERTMGMT, &certMgmtInfo,
								  CRYPT_CERTACTION_CERT_CREATION );
		if( cryptStatusOK( status ) )
			{
			int value;

			/* Remember the issued certificate and save various pieces
			   of information that we'll need later */
			sessionInfoPtr->iCertResponse = certMgmtInfo.cryptCert;
			status = krnlSendMessage( sessionInfoPtr->iCertResponse,
									  IMESSAGE_GETATTRIBUTE, &value,
									  CRYPT_IATTRIBUTE_CERTHASHALGO );
			if( cryptStatusOK( status ) )
				protocolInfo.confHashAlgo = value;
			}
		}
	else
		{
		setMessageCertMgmtInfo( &certMgmtInfo, CRYPT_UNUSED,
								sessionInfoPtr->iCertRequest );
		status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
								  IMESSAGE_KEY_CERTMGMT, &certMgmtInfo,
								  CRYPT_CERTACTION_REVOKE_CERT );
		}
	if( cryptStatusError( status ) )
		{
		/* If the certificate store reports that there's a problem with the 
		   request, convert it to an invalid request error */
		if( cryptArgError( status ) )
			status = CRYPT_ERROR_INVALID;
		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		destroyCMPprotocolInfo( &protocolInfo );
		retExtObj( status, 
				   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
					 "CMP certificate %s for '%s' was denied by certificate "
					 "store",
					 ( protocolInfo.operation != CTAG_PB_RR ) ? \
						"issue" : "revocation",
					 getCertHolderName( sessionInfoPtr->iCertRequest, 
										certName, CRYPT_MAX_TEXTSIZE ) ) );
		}
	CFI_CHECK_UPDATE( "IMESSAGE_KEY_CERTMGMT" );

	/* Send the response to the client */
	status = writePkiMessage( sessionInfoPtr, &protocolInfo, CMPBODY_NORMAL );
	if( cryptStatusError( status ) )
		{
		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );

		/* If there was a problem, drop the partially-issued certificate.  
		   We don't have to go all the way and do a full reversal because it 
		   hasn't really been issued yet since we couldn't get it to the 
		   client (or even if the client somehow got it, the certificate 
		   store hasn't marked it as complete yet so it's regarded as 
		   invalid if queried).  In addition we don't do anything with the 
		   return status since we want to return the status that caused the 
		   problem, not the result of the drop operation */
		if( protocolInfo.operation != CTAG_PB_RR )
			{
			setMessageCertMgmtInfo( &certMgmtInfo, CRYPT_UNUSED,
									sessionInfoPtr->iCertResponse );
			( void ) krnlSendMessage( sessionInfoPtr->cryptKeyset,
									  IMESSAGE_KEY_CERTMGMT, &certMgmtInfo,
									  CRYPT_CERTACTION_CERT_CREATION_DROP );
			}
		destroyCMPprotocolInfo( &protocolInfo );
		return( status );
		}
	DEBUG_DUMP_CMP( protocolInfo.operation, 2, sessionInfoPtr );
	status = writePkiDatagram( sessionInfoPtr, CMP_CONTENT_TYPE,
							   CMP_CONTENT_TYPE_LEN,
							   MK_ERRTEXT( "Couldn't send CMP response to "
										   "client" ) );
	if( cryptStatusError( status ) )
		{
		/* As above, drop the partially-issued certificate if required */
		if( protocolInfo.operation != CTAG_PB_RR )
			{
			setMessageCertMgmtInfo( &certMgmtInfo, CRYPT_UNUSED,
									sessionInfoPtr->iCertResponse );
			( void ) krnlSendMessage( sessionInfoPtr->cryptKeyset,
									  IMESSAGE_KEY_CERTMGMT, &certMgmtInfo,
									  CRYPT_CERTACTION_CERT_CREATION_DROP );
			}
		destroyCMPprotocolInfo( &protocolInfo );
		return( status );
		}
	CFI_CHECK_UPDATE( "writePkiMessage" );

	/* If it's a transaction type that doesn't need a confirmation, we're 
	   done */
	if( protocolInfo.operation == CTAG_PB_RR )
		{
		/* Remember the authentication context in case we can reuse it for 
		   another transaction */
		cmpInfo->iSavedMacContext = protocolInfo.iMacContext;
		protocolInfo.iMacContext = CRYPT_ERROR;

		destroyCMPprotocolInfo( &protocolInfo );

		ENSURES( CFI_CHECK_SEQUENCE_8( "initCMPprotocolInfo", "readPkiDatagram", 
									   "readPkiMessage", "PKIBoot", 
									   "IMESSAGE_CRT_SIGCHECK", 
									   "IMESSAGE_KEY_SETKEY", 
									   "IMESSAGE_KEY_CERTMGMT", 
									   "writePkiMessage" ) );
		return( CRYPT_OK );
		}
	CFI_CHECK_UPDATE( "noConf" );

	/* Read back the confirmation from the client */
	status = readPkiDatagram( sessionInfoPtr, MIN_CRYPT_OBJECTSIZE,
							  MK_ERRTEXT( "Couldnt read CMP confirmation "
										  "from client" ) );
	if( cryptStatusError( status ) )
		{
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		return( cleanupFailedIssue( sessionInfoPtr, &protocolInfo, 
									status ) );
		}
	DEBUG_DUMP_CMP( protocolInfo.operation, 3, sessionInfoPtr );
	status = readPkiMessage( sessionInfoPtr, &protocolInfo,
							 CTAG_PB_CERTCONF );
	if( cryptStatusError( status ) )
		{
		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		return( cleanupFailedIssue( sessionInfoPtr, &protocolInfo, 
									status ) );
		}
	if( protocolInfo.status == CRYPT_ERROR )
		{
		/* The client rejected the certificate, this isn't a protocol error 
		   so we send back a standard ack */
		status = writePkiMessage( sessionInfoPtr, &protocolInfo, 
								  CMPBODY_ACK );
		if( cryptStatusError( status ) )
			{
			delayRandom();	/* Dither error timing info */
			sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
			}
		else
			{
			DEBUG_DUMP_CMP( protocolInfo.operation, 4, sessionInfoPtr );
			status = writePkiDatagram( sessionInfoPtr, CMP_CONTENT_TYPE, 
									   CMP_CONTENT_TYPE_LEN,
									   MK_ERRTEXT( "Couldn't send CMP "
												   "acknowledgement of "
												   "client's certificate "
												   "rejection" ) );
			}
		return( cleanupFailedIssue( sessionInfoPtr, &protocolInfo, 
									status ) );
		}
	CFI_CHECK_UPDATE( "confMessage" );

	/* The client has confirmed the certificate creation, finalise it */
	setMessageCertMgmtInfo( &certMgmtInfo, CRYPT_UNUSED,
							sessionInfoPtr->iCertResponse );
	status = krnlSendMessage( sessionInfoPtr->cryptKeyset,
							  IMESSAGE_KEY_CERTMGMT, &certMgmtInfo,
							  CRYPT_CERTACTION_CERT_CREATION_COMPLETE );
	if( cryptStatusError( status ) )
		{
		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		destroyCMPprotocolInfo( &protocolInfo );
		retExtObj( status, 
				   ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset,
					 "CMP certificate issue completion for '%s' failed",
					 getCertHolderName( sessionInfoPtr->iCertResponse, 
										certName, CRYPT_MAX_TEXTSIZE ) ) );
		}
	CFI_CHECK_UPDATE( "IMESSAGE_KEY_CERTMGMT" );

	/* Send back the final ack and clean up.  It's not certain whether we
	   should send an error response at this late stage if the creation of 
	   the final ack fails since the certificate has been issued 
	   successfully, so we leave it up to the client to decide how it should 
	   treat this situation */
	status = writePkiMessage( sessionInfoPtr, &protocolInfo, CMPBODY_ACK );
	if( cryptStatusError( status ) )
		{
		delayRandom();	/* Dither error timing info */
		sendErrorResponse( sessionInfoPtr, &protocolInfo, status );
		destroyCMPprotocolInfo( &protocolInfo );
		return( status );
		}
	DEBUG_DUMP_CMP( protocolInfo.operation, 4, sessionInfoPtr );
	status = writePkiDatagram( sessionInfoPtr, CMP_CONTENT_TYPE,
							   CMP_CONTENT_TYPE_LEN,
							   MK_ERRTEXT( "Couldn't send CMP confirmation "
										   "acknowledgement to client" ) );
	if( cryptStatusError( status ) )
		{
		destroyCMPprotocolInfo( &protocolInfo );
		return( status );
		}
	CFI_CHECK_UPDATE( "writePkiMessage" );

	/* Remember the authentication context in case we can reuse it for 
	   another transaction */
	cmpInfo->iSavedMacContext = protocolInfo.iMacContext;
	protocolInfo.iMacContext = CRYPT_ERROR;
	destroyCMPprotocolInfo( &protocolInfo );

	ENSURES( CFI_CHECK_SEQUENCE_12( "initCMPprotocolInfo", "readPkiDatagram", 
									"readPkiMessage", "PKIBoot", 
									"IMESSAGE_CRT_SIGCHECK", "IMESSAGE_KEY_SETKEY", 
									"IMESSAGE_KEY_CERTMGMT", "writePkiMessage", 
									"noConf", "confMessage", 
									"IMESSAGE_KEY_CERTMGMT", "writePkiMessage" ) );
	
	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Session Access Routines							*
*																			*
****************************************************************************/

STDC_NONNULL_ARG( ( 1 ) ) \
void initCMPserverProcessing( SESSION_INFO *sessionInfoPtr )
	{
	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );

	FNPTR_SET( sessionInfoPtr->transactFunction, serverTransact );
	}
#endif /* USE_CMP */
