/****************************************************************************
*																			*
*					cryptlib Internal Error Reporting API					*
*						Copyright Peter Gutmann 1992-2025					*
*																			*
****************************************************************************/

#include <stdarg.h>
#include <stdio.h>	/* Needed on some systems for macro-mapped *printf()'s */
#if defined( INC_ALL )
  #include "crypt.h"
#else
  #include "crypt.h"
#endif /* Compiler-specific includes */

#ifdef USE_ERRMSGS

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

/* Format a printf-style error string.  The ERROR_INFO is annotated as
   OUT_ALWAYS because it's initialised unconditionally, the return status 
   exists only to signal to the caller that, in the case where further 
   information is added to the error information, that it's OK to add this
   further information.

   In the following we can't make the third arg a NONNULL_ARG because in the 
   Arm ABI it's a scalar value */

RETVAL_BOOL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static BOOLEAN formatErrorString( OUT_ALWAYS ERROR_INFO *errorInfo, 
								  IN_STRING const char *format, 
								  IN_PTR va_list argPtr )
	{
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( format, 4 ) );

	ANALYSER_HINT_STRING( format );
	ANALYSER_HINT_FORMAT_STRING( format );

	REQUIRES_B( verifyVAList( argPtr ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	/* This function is a bit tricky to deal with because of the 
	   braindamaged behaviour of some of the underlying functions that it 
	   may be mapped to.  Specifically, (v)snprintf() returns the number of 
	   bytes it *could* have written had it felt like it rather than how 
	   many it actually wrote on non-Windows systems and an error indicator 
	   with no guarantee of null-termination on Windows systems.  The latter 
	   isn't a problem because we both catch the error and don't require 
	   null termination, the former is more problematic because it can lead 
	   to a length indication that's larger than the actual buffer.  To 
	   handle this we explicitly check for an overflow as well as an 
	   error/underflow */
	errorInfo->errorStringLength = \
				vsprintf_s( errorInfo->errorString, MAX_ERRMSG_SIZE, 
							format, argPtr ); 
	if( errorInfo->errorStringLength <= 0 || \
		errorInfo->errorStringLength > MAX_ERRMSG_SIZE )
		{
		DEBUG_DIAG(( "Invalid error string data" ));
		assert( DEBUG_WARN );
		setErrorString( errorInfo, 
						"(Couldn't record error information)", 35 );

		return( FALSE );
		}

	return( TRUE );
	}

/* Append a second error string containing further explanatory information 
   to an existing one.  There's no failure/success return value for this 
   function since there's not much that we can do in the case of a failure, 
   we rely on the existing primary error string to convey as much 
   information as possible */

STDC_NONNULL_ARG( ( 1, 2 ) ) \
static void appendErrorString( INOUT_PTR ERROR_INFO *errorInfo, 
							   IN_BUFFER( extErrorStringLength ) \
									const char *extErrorString, 
							   IN_LENGTH_ERRORMESSAGE \
									const int extErrorStringLength )
	{
	BOOLEAN appendDots = FALSE;
	int secondStringLength = extErrorStringLength;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtrDynamic( extErrorString, extErrorStringLength ) );

	REQUIRES_V( errorInfo->errorStringLength > 0 && \
				errorInfo->errorStringLength <= MAX_ERRMSG_SIZE );
	REQUIRES_V( extErrorStringLength > 0 && \
				extErrorStringLength <= MAX_ERRMSG_SIZE );

	/* If there's no room to store the full strings, truncate the second one,
	   which is secondary and therefore less important than the first one.  
	   This can't overflow since we've just checked the lengths above, the
	   overflow check is present purely for documentation purposes */
	if( checkOverflowAdd( errorInfo->errorStringLength, 
						  secondStringLength ) || \
		errorInfo->errorStringLength + \
							secondStringLength > MAX_ERRMSG_SIZE - 8 )
		{
		/* If there's nothing much to be added by appending the second 
		   string then don't do anything */
		if( errorInfo->errorStringLength >= MAX_ERRMSG_SIZE - 8 || \
			secondStringLength < 8 )
			return;

		/* Append a truncated form of the second string to the first one.
		   We know that the length of the existing string plus the 
		   additional string is > MAX_ERRMSG_SIZE - 8, so the amount of
		   the second string that we can add is less than its original 
		   length */
		secondStringLength = ( MAX_ERRMSG_SIZE - 8 ) - \
							 errorInfo->errorStringLength;
		ENSURES_V( secondStringLength < extErrorStringLength );
		ENSURES_V( rangeCheck( secondStringLength, 1, 
						MAX_ERRMSG_SIZE - errorInfo->errorStringLength ) );
		appendDots = TRUE;
		}

	/* Append the second string to the first, with dots to indicate 
	   truncation if necessary */
	REQUIRES_V( boundsCheck( errorInfo->errorStringLength,
							 secondStringLength, MAX_ERRMSG_SIZE ) );
	memcpy( errorInfo->errorString + errorInfo->errorStringLength,
			extErrorString, secondStringLength );
	REQUIRES_V( !checkOverflowAdd( errorInfo->errorStringLength, 
								   secondStringLength ) );
	errorInfo->errorStringLength += secondStringLength;
	if( appendDots )
		{
		REQUIRES_V( boundsCheck( errorInfo->errorStringLength, 3, 
								 MAX_ERRMSG_SIZE ) );
		memcpy( errorInfo->errorString + errorInfo->errorStringLength,
				"...", 3 );
		REQUIRES_V( !checkOverflowAdd( errorInfo->errorStringLength, 3 ) );
		errorInfo->errorStringLength += 3;
		}
#ifndef NDEBUG
	/* Null-terminate the string for use with DEBUG_DIAG() and similar */
	errorInfo->errorString[ errorInfo->errorStringLength ] = '\0';
#endif /* NDEBUG */
	}

/****************************************************************************
*																			*
*							Clear/Set/Copy Error Strings					*
*																			*
****************************************************************************/

/* Set a fixed string as the error message.  This is used to set a 
   predefined error string from something like a table of error messages */

STDC_NONNULL_ARG( ( 1, 2 ) ) \
void setErrorString( OUT_PTR ERROR_INFO *errorInfo, 
					 IN_BUFFER( stringLength ) const char *string, 
					 IN_LENGTH_ERRORMESSAGE const int stringLength )
	{
	int length = stringLength;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtrDynamic( string, stringLength ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	/* Since we're already in an error-handling function we don't use the 
	   REQUIRES() predicate (which would result in an infinite page fault)
	   but make the sanity-checking of parameters explicit */
	if( stringLength <= 0 || stringLength > MAX_ERRMSG_SIZE )
		{
		DEBUG_DIAG(( "Invalid error string data" ));
		assert( DEBUG_WARN );
		string = "(Couldn't record error information)";
		length = 35;
		}
	
	REQUIRES_V( rangeCheck( length, 1, MAX_ERRMSG_SIZE ) );
	memcpy( errorInfo->errorString, string, length );
	errorInfo->errorStringLength = length;
	}

/* Copy error information from a low-level state structure (for example a 
   stream) to a high-level one (for example a session or envelope) */

STDC_NONNULL_ARG( ( 1, 2 ) ) \
void copyErrorInfo( OUT_PTR ERROR_INFO *destErrorInfo, 
					IN_PTR const ERROR_INFO *srcErrorInfo )
	{
	assert( isWritePtr( destErrorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( srcErrorInfo, sizeof( ERROR_INFO ) ) );

	memset( destErrorInfo, 0, sizeof( ERROR_INFO ) );
	if( srcErrorInfo->errorStringLength > 0 )
		{
		setErrorString( destErrorInfo, srcErrorInfo->errorString, 
						srcErrorInfo->errorStringLength );
		}
	}

/* Read error information from an object into an error-info structure */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int readErrorInfo( OUT_PTR ERROR_INFO *errorInfo, 
				   IN_HANDLE const CRYPT_HANDLE objectHandle )
	{
	MESSAGE_DATA msgData;
	int status;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	REQUIRES( objectHandle == DEFAULTUSER_OBJECT_HANDLE || \
			  isHandleRangeValid( objectHandle ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	/* Read any additional error information that may be available */
	setMessageData( &msgData, errorInfo->errorString, MAX_ERRMSG_SIZE );
	status = krnlSendMessage( objectHandle, IMESSAGE_GETATTRIBUTE_S,
							  &msgData, CRYPT_ATTRIBUTE_ERRORMESSAGE );
	if( cryptStatusError( status ) )
		return( status );
	errorInfo->errorStringLength = msgData.length;
	ENSURES( errorInfo->errorStringLength > 0 && \
			 errorInfo->errorStringLength < MAX_ERRMSG_SIZE );

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						Return Extended Error Information					*
*																			*
****************************************************************************/

/* Exit after recording a detailed error message.  This is used by lower-
   level code to provide more information to the caller than a basic error 
   code.  Felix qui potuit rerum cognoscere causas.

   Since we're already in an error-handling function when we call these 
   functions we don't use the REQUIRES() predicate (which would result in an 
   infinite page fault) but make the sanity-checking of parameters 
   explicit */

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 3 ) ) STDC_PRINTF_FN( 3, 4 ) \
int retExtFn( IN_ERROR const int status, 
			  OUT_PTR ERROR_INFO *errorInfo, 
			  FORMAT_STRING const char *format, ... )
	{
	va_list argPtr;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( format, 4 ) );

	REQUIRES( cryptStatusError( status ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	va_start( argPtr, format );
	formatErrorString( errorInfo, format, argPtr );
	va_end( argPtr );

	DEBUG_PRINT_COND( memcmp( errorInfo->errorString, "Key in ", 7 ),
					  ( "%s\n", errorInfo->errorString ) );
					  /* The key usage message is a warning so we don't 
					     print this as it occurs for many certificate usage 
						 checks */

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 3 ) ) STDC_PRINTF_FN( 3, 4 ) \
int retExtArgFn( IN_ERROR const int status, 
				 OUT_PTR ERROR_INFO *errorInfo, 
				 FORMAT_STRING const char *format, ... )
	{
	va_list argPtr;

	/* This function is identical to retExtFn() except that it doesn't trap
	   CRYPT_ARGERROR_xxx values, since they're valid return values in some
	   cases */

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( format, 4 ) );

	REQUIRES( cryptStatusError( status ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	va_start( argPtr, format );
	formatErrorString( errorInfo, format, argPtr );
	va_end( argPtr );

	DEBUG_PUTS( errorInfo->errorString );

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 4 ) ) STDC_PRINTF_FN( 4, 5 ) \
int retExtObjFn( IN_ERROR const int status, 
				 OUT_PTR ERROR_INFO *errorInfo, 
				 IN_HANDLE const CRYPT_HANDLE extErrorObject, 
				 FORMAT_STRING const char *format, ... )
	{
	ERROR_INFO extErrorInfo;
	va_list argPtr;
	BOOLEAN errorStringOK;
	int errorStringLength, extErrorStatus;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( format, 4 ) );

	REQUIRES( cryptStatusError( status ) );
	REQUIRES( extErrorObject == DEFAULTUSER_OBJECT_HANDLE || \
			  isHandleRangeValid( extErrorObject ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	/* If we're being used as a direct pass-through, for example when a 
	   device acts as a keyset, then we just copy the error string up from
	   the lower-level object and exit  */
	if( strnlen_s( format, MAX_ERRMSG_SIZE ) == 4 && \
		!memcmp( format, "NULL", 4 ) )
		{
		extErrorStatus = readErrorInfo( &extErrorInfo, extErrorObject );
		if( cryptStatusOK( extErrorStatus ) )
			{
			copyErrorInfo( errorInfo, &extErrorInfo );

			DEBUG_PUTS( errorInfo->errorString );
			}

		return( status );
		}

	/* Format the basic error string */
	va_start( argPtr, format );
	errorStringOK = formatErrorString( errorInfo, format, argPtr );
	va_end( argPtr );
	if( !errorStringOK )
		{
		/* If we couldn't format the basic error string then there's no 
		   point in continuing.  formatErrorString() throws a debug 
		   exception if there's a problem so there's no need to throw one 
		   here */
		return( status );
		}
	errorStringLength = errorInfo->errorStringLength;
	ENSURES( errorStringLength > 0 && errorStringLength < MAX_ERRMSG_SIZE );

	/* Check whether there's any additional error information available */
	extErrorStatus = readErrorInfo( &extErrorInfo, extErrorObject );
	if( cryptStatusError( extErrorStatus ) )
		{
		/* Nothing further to report, exit */
		return( status );
		}

	/* There's additional information present via the additional object, 
	   fetch it and append it to the higher-level error message */
	appendErrorString( errorInfo, ": ", 2 );
	appendErrorString( errorInfo, extErrorInfo.errorString, 
					   extErrorInfo.errorStringLength );

	DEBUG_PUTS( errorInfo->errorString );

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 3, 4 ) ) STDC_PRINTF_FN( 4, 5 ) \
int retExtErrFn( IN_ERROR const int status, 
				 OUT_PTR ERROR_INFO *errorInfo, 
				 IN_PTR const ERROR_INFO *existingErrorInfo, 
				 FORMAT_STRING const char *format, ... )
	{
	va_list argPtr;
	char extErrorString[ MAX_ERRMSG_SIZE + 8 ];
	int extErrorStringLength = 0;
	BOOLEAN errorStringOK;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( existingErrorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( format, 4 ) );
	
	REQUIRES( cryptStatusError( status ) );

	/* We can't clear the return value at this point because errorInfo
	   could be the same as existingErrorInfo */

	/* This function is used when the caller wants to add a possible
	   existing lower-level error string to the current error string.  If
	   there's no lower-level error information present, this function acts
	   like retExt(), "Error string".  If there is lower-level error
	   information present it appends it to the current error string, 
	   "Error string: Lower-level error string".  
	   
	   Since the lower-level error string may already be held in the 
	   errorInfo buffer where the current error string needs to go, we copy 
	   the string into a temporary buffer from where it can be appended back 
	   onto the string in the errorInfo buffer */
	if( existingErrorInfo->errorStringLength > 0 && \
		existingErrorInfo->errorStringLength <= MAX_ERRMSG_SIZE )
		{
		REQUIRES( rangeCheck( existingErrorInfo->errorStringLength, 1, 
							  MAX_ERRMSG_SIZE ) );
		memcpy( extErrorString, existingErrorInfo->errorString,
				existingErrorInfo->errorStringLength );
		extErrorStringLength = existingErrorInfo->errorStringLength;
		}
	ENSURES( extErrorStringLength >= 0 && \
			 extErrorStringLength <= MAX_ERRMSG_SIZE );

	/* Format the basic error string */
	clearErrorInfo( errorInfo );
	va_start( argPtr, format );
	errorStringOK = formatErrorString( errorInfo, format, argPtr );
	va_end( argPtr );
	if( !errorStringOK )
		{
		/* If we couldn't format the basic error string then there's no 
		   point in continuing.  formatErrorString() throws a debug 
		   exception if there's a problem so there's no need to throw one 
		   here */
		return( status );
		}

	/* If there's no lower-level error information present then we just act 
	   like the standard retExt() */
	if( extErrorStringLength <= 0 )
		return( status );

	/* Append the additional status string */
	appendErrorString( errorInfo, ": ", 2 );
	appendErrorString( errorInfo, extErrorString, extErrorStringLength );

	DEBUG_PUTS( errorInfo->errorString );

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 3 ) ) STDC_PRINTF_FN( 3, 4 ) \
int retExtAdditionalFn( IN_ERROR const int status, 
						INOUT_PTR ERROR_INFO *errorInfo, 
						FORMAT_STRING const char *format, ... )
	{
	va_list argPtr;
	char extErrorString[ MAX_ERRMSG_SIZE + 8 ];
	int extErrorStringLength;

	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( format, 4 ) );

	REQUIRES( cryptStatusError( status ) );

	/* This function is typically used when the caller wants to convert 
	   something like "Low-level error string" into "Low-level error string,
	   additional comments".  
	   
	   First we need to check that there's actually a base error string 
	   present.  This situation can occur when a low-level function returns 
	   a cryptlib-level error status without getting/setting any extended 
	   error information, which then percolates up to a point where it's 
	   returned via a retExt(). 
	   
	   We have to be careful how we present this information because the
	   additional-information string that we're about to append could appear 
	   to be part of any indication that there's no error information 
	   present.  The additional information will be in the form:

		$MAIN, because ...
		$MAIN, this could be because ...
		$MAIN, however other data suggests ...

	   There isn't any obvious string we can use here that doesn't have the
	   potential to read in a confusing manner when the additional 
	   information is appended, "(Primary error information missing)" seems
	   to be the best bet.  In any case it's mitigated by the fact that this
	   situation should never really occur */
	if( errorInfo->errorStringLength <= 0 || \
		errorInfo->errorStringLength > MAX_ERRMSG_SIZE )
		{
		DEBUG_DIAG(( "Invalid error string data" ));
		assert_nofuzz( DEBUG_WARN );
		setErrorString( errorInfo, 
						"(Primary error information missing)", 35 );
		}

	/* Format the additional-comments string prior to appending it to the 
	   base error string */
	va_start( argPtr, format );
	extErrorStringLength = vsprintf_s( extErrorString, MAX_ERRMSG_SIZE, 
									   format, argPtr ); 
	va_end( argPtr );
	if( extErrorStringLength <= 0 || extErrorStringLength >= MAX_ERRMSG_SIZE )
		{
		DEBUG_DIAG(( "Invalid error string data" ));
		assert( DEBUG_WARN );
		setErrorString( errorInfo, 
						"(Couldn't record error information)", 35 );
		return( status );
		}

	/* Append the additional status string */
	appendErrorString( errorInfo, extErrorString, extErrorStringLength );

	DEBUG_PUTS( errorInfo->errorString );

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 3, 4 ) ) \
int retExtSanFn( IN_ERROR const int status, 
				 OUT_PTR ERROR_INFO *errorInfo,
				 FORMAT_STRING const char *formatString,
				 IN_BUFFER( string1Length ) const BYTE *string1,
				 IN_LENGTH_SHORT_Z const int string1Length,
				 IN_BUFFER_OPT( string2Length ) const BYTE *string2,
				 IN_LENGTH_SHORT_Z const int string2Length,
				 IN_BUFFER_OPT( string3Length ) const BYTE *string3,
				 IN_LENGTH_SHORT_Z const int string3Length )
	{
	char string1Buffer[ CRYPT_MAX_TEXTSIZE + 8 ];
	char string2Buffer[ CRYPT_MAX_TEXTSIZE + 8 ];
	char string3Buffer[ CRYPT_MAX_TEXTSIZE + 8 ];
	const char *string1Ptr = string1, *string2Ptr = string2;
	const char *string3Ptr = string3;

	assert( isReadPtr( formatString, 16 ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( ( string1Length == 0 && isReadPtr( string1, 4 ) ) || \
			isReadPtrDynamic( string1, string1Length ) );
	assert( string2 == NULL || \
			( string2 != NULL && string2Length == 0 ) || \
			isReadPtrDynamic( string2, string2Length ) );
	assert( string3 == NULL || \
			( string3 != NULL && string3Length == 0 ) || \
			isReadPtrDynamic( string3, string3Length ) );

	REQUIRES( cryptStatusError( status ) );
	REQUIRES( string1 != NULL && \
			  isShortIntegerRange( string1Length ) );
	REQUIRES( ( string2 == NULL && string2Length == 0 ) || \
			  ( string2 != NULL && \
				isShortIntegerRange( string2Length ) ) );
	REQUIRES( ( string3 == NULL && string3Length == 0 ) || \
			  ( string3 != NULL && \
				isShortIntegerRange( string3Length ) ) );

	/* Clear return value */
	clearErrorInfo( errorInfo );

	/* Set up the various strings that we'll need in the error message.  The 
	   string2 and string3 arguments are optional, in addition any of the
	   arguments can be literal strings denoted by the length being 0 so we
	   only sanitise them if they're present and it's required */
	if( string1Length > 0 )
		{
		const int stringLength = min( string1Length, CRYPT_MAX_TEXTSIZE );

		REQUIRES( rangeCheck( stringLength, 1, CRYPT_MAX_TEXTSIZE ) );
		memcpy( string1Buffer, string1, stringLength );
		sanitiseString( string1Buffer, CRYPT_MAX_TEXTSIZE, string1Length );
		string1Ptr = string1Buffer;
		}
	if( string2 != NULL && string2Length > 0 )
		{
		const int stringLength = min( string2Length, CRYPT_MAX_TEXTSIZE );

		REQUIRES( rangeCheck( stringLength, 1, CRYPT_MAX_TEXTSIZE ) );
		memcpy( string2Buffer, string2, stringLength );
		sanitiseString( string2Buffer, CRYPT_MAX_TEXTSIZE, string2Length );
		string2Ptr = string2Buffer;
		}
	if( string3 != NULL && string3Length > 0 )
		{
		const int stringLength = min( string3Length, CRYPT_MAX_TEXTSIZE );

		REQUIRES( rangeCheck( stringLength, 1, CRYPT_MAX_TEXTSIZE ) );
		memcpy( string3Buffer, string3, stringLength );
		sanitiseString( string3Buffer, CRYPT_MAX_TEXTSIZE, string3Length );
		string3Ptr = string3Buffer;
		}

	/* Format the error message.  Some of the string pointers may be NULL
	   but this is OK, it's a printf-style varargs function that only takes
	   the arguments it needs, so for example { "...%s...", string1Ptr, 
	   NULL, NULL } */
	retExt( status,
			( status, errorInfo, formatString, string1Ptr, 
			  string2Ptr, string3Ptr ) );
	}

/****************************************************************************
*																			*
*								Helper Functions							*
*																			*
****************************************************************************/

/* Additional helper functions used to provide extended information for
   error messages.  The first set of functions provides names for error
   status values, algorithms, and key IDs */

CHECK_RETVAL_PTR_NONNULL \
const char *getStatusName( IN_STATUS const int errorStatus )
	{
	static const OBJECT_NAME_INFO objectNameInfo[] = {
		{ CRYPT_ERROR_PARAM1, "CRYPT_ERROR_PARAM1" },
		{ CRYPT_ERROR_PARAM2, "CRYPT_ERROR_PARAM2" },
		{ CRYPT_ERROR_PARAM3, "CRYPT_ERROR_PARAM3" },
		{ CRYPT_ERROR_PARAM4, "CRYPT_ERROR_PARAM4" },
		{ CRYPT_ERROR_PARAM5, "CRYPT_ERROR_PARAM5" },
		{ CRYPT_ERROR_PARAM6, "CRYPT_ERROR_PARAM6" },
		{ CRYPT_ERROR_PARAM7, "CRYPT_ERROR_PARAM7" },
		{ CRYPT_ERROR_MEMORY, "CRYPT_ERROR_MEMORY" },
		{ CRYPT_ERROR_NOTINITED, "CRYPT_ERROR_NOTINITED" },
		{ CRYPT_ERROR_INITED, "CRYPT_ERROR_INITED" },
		{ CRYPT_ERROR_NOSECURE, "CRYPT_ERROR_NOSECURE" },
		{ CRYPT_ERROR_RANDOM, "CRYPT_ERROR_RANDOM" },
		{ CRYPT_ERROR_FAILED, "CRYPT_ERROR_FAILED" },
		{ CRYPT_ERROR_INTERNAL, "CRYPT_ERROR_INTERNAL" },
		{ CRYPT_ERROR_NOTAVAIL, "CRYPT_ERROR_NOTAVAIL" },
		{ CRYPT_ERROR_PERMISSION, "CRYPT_ERROR_PERMISSION" },
		{ CRYPT_ERROR_WRONGKEY, "CRYPT_ERROR_WRONGKEY" },
		{ CRYPT_ERROR_INCOMPLETE, "CRYPT_ERROR_INCOMPLETE" },
		{ CRYPT_ERROR_COMPLETE, "CRYPT_ERROR_COMPLETE" },
		{ CRYPT_ERROR_TIMEOUT, "CRYPT_ERROR_TIMEOUT" },
		{ CRYPT_ERROR_INVALID, "CRYPT_ERROR_INVALID" },
		{ CRYPT_ERROR_SIGNALLED, "CRYPT_ERROR_SIGNALLED" },
		{ CRYPT_ERROR_OVERFLOW, "CRYPT_ERROR_OVERFLOW" },
		{ CRYPT_ERROR_UNDERFLOW, "CRYPT_ERROR_UNDERFLOW" },
		{ CRYPT_ERROR_BADDATA, "CRYPT_ERROR_BADDATA" },
		{ CRYPT_ERROR_SIGNATURE, "CRYPT_ERROR_SIGNATURE" },
		{ CRYPT_ERROR_OPEN, "CRYPT_ERROR_OPEN" },
		{ CRYPT_ERROR_READ, "CRYPT_ERROR_READ" },
		{ CRYPT_ERROR_WRITE, "CRYPT_ERROR_WRITE" },
		{ CRYPT_ERROR_NOTFOUND, "CRYPT_ERROR_NOTFOUND" },
		{ CRYPT_ERROR_DUPLICATE, "CRYPT_ERROR_DUPLICATE" },
		{ CRYPT_ENVELOPE_RESOURCE, "CRYPT_ENVELOPE_RESOURCE" },
			{ CRYPT_OK, "<Unknown>" }, { CRYPT_OK, "<Unknown>" },
		};

	REQUIRES_EXT( cryptStatusError( errorStatus ), "<Unknown>" );

	return( getObjectName( objectNameInfo,
						   FAILSAFE_ARRAYSIZE( objectNameInfo, \
											   OBJECT_NAME_INFO ),
						   errorStatus ) ); 
	}

CHECK_RETVAL_PTR_NONNULL \
const char *getAlgoName( IN_ALGO const CRYPT_ALGO_TYPE cryptAlgo )
	{
	static const OBJECT_NAME_INFO objectNameInfo[] = {
		/* Conventional algorithms */
		{ CRYPT_ALGO_DES, "DES" },
		{ CRYPT_ALGO_3DES, "3DES" },
		{ CRYPT_ALGO_IDEA, "IDEA" },
		{ CRYPT_ALGO_CAST, "CAST-128" },
		{ CRYPT_ALGO_RC2, "RC2" },
		{ CRYPT_ALGO_RC4, "RC4" },
		{ CRYPT_ALGO_AES, "AES" },
		/* PKC algorithms */
		{ CRYPT_ALGO_DH, "DH" },
		{ CRYPT_ALGO_RSA, "RSA" },
		{ CRYPT_ALGO_DSA, "DSA" },
		{ CRYPT_ALGO_ELGAMAL, "ElGamal" },
		{ CRYPT_ALGO_ECDSA,	"ECDSA" },
		{ CRYPT_ALGO_ECDH, "ECDH" },
		{ CRYPT_ALGO_25519,	"Curve25519" },
		{ CRYPT_ALGO_ED25519, "Ed25519" },
		/* Hash algorithms */
		{ CRYPT_ALGO_MD5, "MD5" },
		{ CRYPT_ALGO_SHA1, "SHA1" },
		{ CRYPT_ALGO_SHA2, "SHA2" },
		{ CRYPT_ALGO_SHAng,	"SHAng" },
		/* MAC algorithms */
		{ CRYPT_ALGO_HMAC_SHA1,	"HMAC-SHA1" },
		{ CRYPT_ALGO_HMAC_SHA2,	"HMAC-SHA2" },
		{ CRYPT_ALGO_HMAC_SHAng, "HMAC-SHAng" },
		/* Generic secret algorithm.  This is a bit of an odd one because 
		   it's only visible internally, but we need to have some 
		   description available for it in case an attempt to create a 
		   context with it fails */
		{ CRYPT_IALGO_GENERIC_SECRET, "Generic Secret" },
		{ CRYPT_ALGO_NONE, "<Unknown>" }, { CRYPT_ALGO_NONE, "<Unknown>" },
		};

	REQUIRES_EXT( isConvAlgo( cryptAlgo ) || isPkcAlgo( cryptAlgo ) || \
				  isHashAlgo( cryptAlgo ) || isMacAlgo( cryptAlgo ) || \
				  isSpecialAlgo( cryptAlgo ), 
				  "<Unknown>" );

	return( getObjectName( objectNameInfo,
						   FAILSAFE_ARRAYSIZE( objectNameInfo, \
											   OBJECT_NAME_INFO ),
						   cryptAlgo ) ); 
	}

CHECK_RETVAL_PTR_NONNULL \
const char *getAlgoNameEx( IN_ALGO const CRYPT_ALGO_TYPE cryptAlgo,
						   IN_RANGE( 0, 100 ) const int cryptParam )
	{
	REQUIRES_EXT( isHashAlgo( cryptAlgo ) || \
				  isMacAlgo( cryptAlgo ), "<Unknown>" );
	REQUIRES_EXT( cryptParam >= 0 && cryptParam <= 100, "<Unknown>" );

	/* If it's not a parameterised algorithm or there's no parameter 
	   present, just return the base algorithm */
	if( !( isParameterisedHashAlgo( cryptAlgo ) || \
		   isParameterisedMacAlgo( cryptAlgo ) ) || cryptParam <= 0 )
		return( getAlgoName( cryptAlgo ) );

	/* The set of parameterised hash/MAC algorithms is small enough that 
	   we can just hardcode in the selection for now */
	if( isMacAlgo( cryptAlgo ) )
		{
		return( ( cryptParam == 32 ) ? "HMAC-SHA2-256" : \
				( cryptParam == 48 ) ? "HMAC-SHA2-384" : \
				( cryptParam == 64 ) ? "HMAC-SHA2-512" : "HMAC-SHA2" );
		}
	return( ( cryptParam == 32 ) ? "SHA2-256" : \
			( cryptParam == 48 ) ? "SHA2-384" : \
			( cryptParam == 64 ) ? "SHA2-512" : "SHA2" );
	}

CHECK_RETVAL_PTR_NONNULL \
const char *getModeName( IN_MODE const CRYPT_MODE_TYPE cryptMode )
	{
	static const OBJECT_NAME_INFO objectNameInfo[] = {
		{ CRYPT_MODE_ECB, "ECB" },
		{ CRYPT_MODE_CBC, "CBC" },
		{ CRYPT_MODE_CFB, "CFB" },
		{ CRYPT_MODE_GCM, "GCM" },
			{ CRYPT_MODE_NONE, "<Unknown>" }, 
			{ CRYPT_MODE_NONE, "<Unknown>" },
		};

	REQUIRES_EXT( cryptMode > CRYPT_MODE_NONE && \
				  cryptMode < CRYPT_MODE_LAST, "<Unknown>" );

	return( getObjectName( objectNameInfo,
						   FAILSAFE_ARRAYSIZE( objectNameInfo, \
											   OBJECT_NAME_INFO ),
						   cryptMode ) ); 
	}

CHECK_RETVAL_PTR_NONNULL \
const char *getKeyIDName( IN_KEYID const CRYPT_KEYID_TYPE keyIDtype )
	{
	static const OBJECT_NAME_INFO objectNameInfo[] = {
		{ CRYPT_KEYID_NAME, "name" },
		{ CRYPT_KEYID_URI, "email/URI" },
		{ CRYPT_IKEYID_KEYID, "keyID" },
		{ CRYPT_IKEYID_PGPKEYID, "PGP keyID" },
		{ CRYPT_IKEYID_CERTID, "certificate ID" },
		{ CRYPT_IKEYID_SUBJECTID, "subject DN ID" },
		{ CRYPT_IKEYID_ISSUERID, "issuerAndSerialNumber ID" },
		{ CRYPT_IKEYID_ISSUERANDSERIALNUMBER, "issuerAndSerialNumber" },
			{ CRYPT_KEYID_NONE, "<Unknown>" }, 
			{ CRYPT_KEYID_NONE, "<Unknown>" },
		};

	REQUIRES_EXT( isEnumRange( keyIDtype, CRYPT_KEYID ), "<Unknown>" );

	/* Return a description of the key ID.  These are meant to be used in 
	   strings of the form "$operation for given %s failed", so "key read "
	   "for given email/URI failed" */
	return( getObjectName( objectNameInfo,
						   FAILSAFE_ARRAYSIZE( objectNameInfo, \
											   OBJECT_NAME_INFO ),
						   keyIDtype ) ); 
	}

/* Get the holder name of a certificate, used to display information on 
   which certificate was used for a failed crypto operation like a signature 
   check.  The function returns a pointer to the passed-in buffer so that it
   can be used directly in print statements */

CHECK_RETVAL_PTR_NONNULL STDC_NONNULL_ARG( ( 2 ) ) \
const char *getCertHolderName( const CRYPT_CERTIFICATE iCryptCert,
							   OUT_BUFFER_FIXED( bufSize ) char *buffer,
							   IN_LENGTH_SHORT_MIN( 16 ) const int bufSize )
	{
	MESSAGE_DATA msgData;
	int value, status;

	REQUIRES_EXT( isHandleRangeValid( iCryptCert ), "<Unknown>" );
	REQUIRES_EXT( isShortIntegerRangeMin( bufSize, 16 ), "<Unknown>" );

	assert( isWritePtr( buffer, bufSize ) );

	/* Clear return value */
	memset( buffer, 0, min( bufSize, 16 ) );

	/* Read the holder name from the certificate and return it to the caller 
	   ready for use in a print statement */
	setMessageData( &msgData, buffer, bufSize );
	status = krnlSendMessage( iCryptCert, IMESSAGE_GETATTRIBUTE, 
							  &value, CRYPT_CERTINFO_CERTTYPE );
	if( cryptStatusOK( status ) )
		{
		status = krnlSendMessage( iCryptCert, IMESSAGE_GETATTRIBUTE_S, 
								  &msgData, CRYPT_IATTRIBUTE_HOLDERNAME );
		}
	else
		{
		/* We may have been passed a raw public-key context, in which case 
		   we try and return the label if it has one */
		status = krnlSendMessage( iCryptCert, IMESSAGE_GETATTRIBUTE_S, 
								  &msgData, CRYPT_CTXINFO_LABEL );
		}
	if( cryptStatusOK( status ) )
		sanitiseString( buffer, bufSize, msgData.length );
	else
		memcpy( buffer, "<Unknown>", 9 + 1 );	/* String + '\0' */

	return( buffer );
	}

/* Format hex data, typically a fingerprint (hash value) for display in an 
   error message */

STDC_NONNULL_ARG( ( 1, 3 ) ) \
void formatHexData( OUT_BUFFER_FIXED( hexTextMaxLen ) char *hexText, 
					IN_LENGTH_SHORT_MIN( 48 ) const int hexTextMaxLen,
					IN_BUFFER( hexDataLen ) const BYTE *hexData,
					IN_LENGTH_SHORT_MIN( 4 ) const int hexDataLen )
	{
	int length;

	assert( isWritePtr( hexText, hexTextMaxLen ) );
	assert( isReadPtr( hexData, hexDataLen ) );

	REQUIRES_V( isShortIntegerRangeMin( hexTextMaxLen, 48 ) );
	REQUIRES_V( isShortIntegerRangeMin( hexDataLen, 4 ) );

	/* Clear return value */
	REQUIRES_V( isShortIntegerRangeMin( hexTextMaxLen, 48 ) ); 
	memset( hexText, 0, min( 16, hexTextMaxLen ) );

	/* Format the hex data as ASCII hex.  If it's 10 bytes or less then we 
	   output the entire quantity for a maximum total of 30 bytes, well 
	   under the 48-byte minimum buffer size */
	if( hexDataLen <= 10 )
		{
		int offset = 0;
		LOOP_INDEX i;

		LOOP_SMALL( i = 0, i < hexDataLen - 1, i++ )
			{
			ENSURES_V( LOOP_INVARIANT_SMALL( i, 0, hexDataLen - 2 ) );

			REQUIRES_V( !checkOverflowSub( hexTextMaxLen, offset ) );
			REQUIRES_V( isShortIntegerRangeNZ( hexTextMaxLen - offset ) );
			length = sprintf_s( hexText + offset, hexTextMaxLen - offset, 
								"%02X ", byteToInt( hexData[ i ] ) );
			ENSURES_V( rangeCheck( length, 2, \
								   hexTextMaxLen - ( offset + 1 ) ) );
			REQUIRES_V( !checkOverflowAdd( offset, length ) );
			offset += length;
			}
		ENSURES_V( LOOP_BOUND_OK );
		ENSURES_V( rangeCheck( offset, 3 * 3, hexTextMaxLen - 1 ) );
							   /* 4 - 1 bytes, 3 chars output */

		REQUIRES_V( !checkOverflowSub( hexTextMaxLen, offset ) );
		REQUIRES_V( isShortIntegerRangeNZ( hexTextMaxLen - offset ) );
		length = sprintf_s( hexText + offset, hexTextMaxLen - offset, 
							"%02X", byteToInt( hexData[ i ] ) );
		ENSURES_V( rangeCheck( length, 2, \
							   hexTextMaxLen - ( offset + 1 ) ) );
		
		return;
		}
	ENSURES_V( hexDataLen > 10 );

	/* It's more than 10 bytes, only output the first 6 and last 4 bytes.  
	   The potential expansion factor is ( hexDataLen * 3 ) + 1 (+3 for the 
	   ellipses, but -2 for the absent spaces at the start and end of the 
	   string).  Since we limit the data quantity displayed to 10 bytes we 
	   never output more than 31 characters of text which again is well 
	   under the 48-byte minimum buffer size */
	length = sprintf_s( hexText, hexTextMaxLen, 
						"%02X %02X %02X %02X %02X %02X ... %02X %02X %02X %02X",
						hexData[ 0 ], hexData[ 1 ], hexData[ 2 ], 
						hexData[ 3 ], hexData[ 4 ], hexData[ 5 ],
						hexData[ hexDataLen - 4 ], hexData[ hexDataLen - 3 ],
						hexData[ hexDataLen - 2 ], hexData[ hexDataLen - 1 ] );
	ENSURES_V( rangeCheck( length, 30, hexTextMaxLen - 1 ) );
	}
#else

/****************************************************************************
*																			*
*						Minimal Error Reporting Functions					*
*																			*
****************************************************************************/

/* Even if we're not using extended error reporting there is one minimal 
   facility that we still need to support, which is the copying of an integer 
   error code from source to destination */

STDC_NONNULL_ARG( ( 1, 2 ) ) \
void copyErrorInfo( OUT_PTR ERROR_INFO *destErrorInfo, 
					IN_PTR const ERROR_INFO *srcErrorInfo )
	{
	assert( isWritePtr( destErrorInfo, sizeof( ERROR_INFO ) ) );
	assert( isReadPtr( srcErrorInfo, sizeof( ERROR_INFO ) ) );

	memset( destErrorInfo, 0, sizeof( ERROR_INFO ) );
	destErrorInfo->errorCode = srcErrorInfo->errorCode;
	}
#endif /* USE_ERRMSGS */
