/****************************************************************************
*																			*
*						cryptlib PKCS #15 Key Add Interface					*
*						Copyright Peter Gutmann 1996-2025					*
*																			*
****************************************************************************/

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

#ifdef USE_PKCS15

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

/* Determine the tags to use when encoding a given key type.  There isn't 
   any tag for Elgamal but the keys are the same as X9.42 DH keys and 
   cryptlib uses the OID rather than the tag to determine the key type so 
   the following sleight-of-hand works */

CHECK_RETVAL STDC_NONNULL_ARG( ( 4 ) ) \
int getKeyTypeTag( IN_HANDLE_OPT const CRYPT_CONTEXT cryptContext,
				   IN_ALGO_OPT const CRYPT_ALGO_TYPE cryptAlgo,
				   IN_ENUM( KEYTYPE_TAG ) const KEYTYPE_TAG_TYPE tagType,
				   OUT_TAG_Z int *tag )
	{
	static const MAP_TABLE pubTagMapTbl[] = {
		{ CRYPT_ALGO_RSA, 100 },
		{ CRYPT_ALGO_DH, CTAG_KP_DH },
		{ CRYPT_ALGO_ELGAMAL, CTAG_KP_DH },
		{ CRYPT_ALGO_DSA, CTAG_KP_DSA },
		{ CRYPT_ALGO_ECDSA, CTAG_KP_ECC },
		{ CRYPT_ALGO_ED25519, CTAG_KP_BERNSTEIN },
		{ CRYPT_ERROR, CRYPT_ERROR }, { CRYPT_ERROR, CRYPT_ERROR }
		};
	static const MAP_TABLE privTagExtMapTbl[] = {
		{ CRYPT_ALGO_RSA, CTAG_KR_RSA_EXT },
		{ CRYPT_ALGO_DH, CTAG_KR_DLP_EXT },
		{ CRYPT_ALGO_ELGAMAL, CTAG_KR_DLP_EXT },
		{ CRYPT_ALGO_DSA, CTAG_KR_DLP_EXT },
		{ CRYPT_ALGO_ECDSA, CTAG_KR_ECC_EXT },
		{ CRYPT_ALGO_ED25519, CTAG_KR_BERNSTEIN_EXT },
		{ CRYPT_ERROR, CRYPT_ERROR }, { CRYPT_ERROR, CRYPT_ERROR }
		};
	int keyCryptAlgo, status;

	REQUIRES( ( isHandleRangeValid( cryptContext ) && \
				cryptAlgo == CRYPT_ALGO_NONE ) || \
			  ( cryptContext == CRYPT_UNUSED && \
				isPkcAlgo( cryptAlgo ) ) );
	REQUIRES( isEnumRange( tagType, KEYTYPE_TAG ) );

	/* Clear return value */
	*tag = 0;

	/* If the caller hasn't already supplied the algorithm details, get them
	   from the context */
	if( cryptAlgo != CRYPT_ALGO_NONE )
		keyCryptAlgo = cryptAlgo;
	else
		{
		status = krnlSendMessage( cryptContext, IMESSAGE_GETATTRIBUTE,
								  &keyCryptAlgo, CRYPT_CTXINFO_ALGO );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* There's no original-format storage mechanism defined for the 
	   Bernstein algorithms, which postdate the introduction of the extended
	   format */
	REQUIRES( !( tagType == KEYTYPE_TAG_PRIVKEY && \
				 keyCryptAlgo == CRYPT_ALGO_ED25519 ) );

	/* Map the algorithm to the corresponding tag */
	switch( tagType )
		{
		case KEYTYPE_TAG_PUBKEY:
		case KEYTYPE_TAG_PRIVKEY:
			{
			int value;
			
			/* We have to be a bit careful with the tags because the out-of-
			   band special-case value DEFAULT_TAG looks like an error 
			   value, so we supply a dummy value of '100' for this tag and 
			   map it back to DEFAULT_TAG when we return it to the caller.
		   
			   In addition we use the public-key tag table for the non-
			   extended private-key tags because they have the same values */
			status = mapValue( keyCryptAlgo, &value, pubTagMapTbl, 
							   FAILSAFE_ARRAYSIZE( pubTagMapTbl, \
												   MAP_TABLE ) );
			if( cryptStatusOK( status ) )
				*tag = ( value == 100 ) ? DEFAULT_TAG : value;
			break;
			}

		case KEYTYPE_TAG_PRIVKEY_EXT:
			status = mapValue( keyCryptAlgo, tag, privTagExtMapTbl, 
							   FAILSAFE_ARRAYSIZE( privTagExtMapTbl, \
												   MAP_TABLE ) );
			break;
		
		default:
			retIntError();
		}
	ENSURES( cryptStatusOK( status ) );

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Add Miscellaneous Items							*
*																			*
****************************************************************************/

/* Add configuration data to a PKCS #15 collection.  The different data
   types are:

	IATTRIBUTE_USERID: ID for objects in user keysets.  All items in the 
		keyset (which will be the user object's private key and their 
		configuration information) are given this value as their ID.
	
	IATTRIBUTE_CONFIGDATA: ASN.1-encoded cryptlib configuration options.

	IATTRIBUTE_USERINDEX: ASN.1-encoded table mapping userIDs and names to 
		a unique index value that's used to locate the file or storage 
		location for that user's configuration data.

	IATTRIBUTE_USERINFO: ASN.1-encoded user information containing their 
		role, ID, name information, and any additional required information.

   The lookup process for a given user's information is to read the 
   IATTRIBUTE_USERINDEX from the user index keyset (typically index.p15) to 
   find the user's index value, and then use that to read the 
   IATTRIBUTE_USERINFO from the user keyset (typically u<index>.p15).  The 
   cryptlib-wide IATTRIBUTE_CONFIGDATA is stored in the cryptlib default 
   initialisation keyset, typically cryptlib.p15.

   If we're being sent empty data (corresponding to an empty SEQUENCE, so 
   dataLength < 8), it means that the caller wants to clear this entry */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 4 ) ) \
int addConfigData( IN_ARRAY( noPkcs15objects ) PKCS15_INFO *pkcs15info, 
				   IN_LENGTH_SHORT const int noPkcs15objects, 
				   IN_ATTRIBUTE const CRYPT_ATTRIBUTE_TYPE dataType,
				   IN_BUFFER( dataLength ) const char *data, 
				   IN_LENGTH_SHORT const int dataLength )
	{
	PKCS15_INFO *pkcs15infoPtr = NULL;
	const BOOLEAN isDataClear = ( dataLength < 8 ) ? TRUE : FALSE;
	void *newData;
	LOOP_INDEX i;

	assert( isWritePtrDynamic( pkcs15info, \
							   sizeof( PKCS15_INFO ) * noPkcs15objects ) );
	assert( isReadPtrDynamic( data, dataLength ) );

	REQUIRES( isShortIntegerRangeNZ( noPkcs15objects ) );
	REQUIRES( dataType == CRYPT_IATTRIBUTE_CONFIGDATA || \
			  dataType == CRYPT_IATTRIBUTE_USERINDEX || \
			  dataType == CRYPT_IATTRIBUTE_USERID || \
			  dataType == CRYPT_IATTRIBUTE_USERINFO );
	REQUIRES( isShortIntegerRangeNZ( dataLength ) );

	/* If it's a user ID, set all object IDs to this value.  This is needed
	   for user keysets where there usually isn't any key ID present (there
	   is one for SO keysets that have public/private keys attached to them 
	   but they're not identified by key ID so it's not much use).  In this 
	   case the caller has to explicitly set an ID, which is the user ID */
	if( dataType == CRYPT_IATTRIBUTE_USERID )
		{
		const int length = min( dataLength, CRYPT_MAX_HASHSIZE );

		REQUIRES( dataLength == KEYID_SIZE );

		LOOP_MED( i = 0, i < noPkcs15objects, i++ )
			{
			ENSURES( LOOP_INVARIANT_MED( i, 0, noPkcs15objects - 1 ) );

			REQUIRES( rangeCheck( length, 1, CRYPT_MAX_HASHSIZE ) );
			memcpy( pkcs15info[ i ].iD, data, length );
  			pkcs15info[ i ].iDlength = length;
			}
		ENSURES( LOOP_BOUND_OK );
		return( CRYPT_OK );
		}

	/* Find an entry that contains data identical to what we're adding now 
	   (which we'll replace with the new data) or failing that, the first 
	   free entry */
	LOOP_MED( i = 0, i < noPkcs15objects, i++ )
		{
		ENSURES( LOOP_INVARIANT_MED( i, 0, noPkcs15objects - 1 ) );

		if( pkcs15info[ i ].type == PKCS15_SUBTYPE_DATA && \
			pkcs15info[ i ].dataType == dataType )
			{
			pkcs15infoPtr = &pkcs15info[ i ];
			break;
			}
		}
	ENSURES( LOOP_BOUND_OK );
	if( pkcs15infoPtr == NULL )
		{
		/* If we're trying to delete an existing entry then not finding what
		   we want to delete is an error */
		ENSURES( !isDataClear );

		/* We couldn't find an existing entry to update, add a new entry */
		pkcs15infoPtr = findFreeEntry( pkcs15info, noPkcs15objects, NULL );
		}
	if( pkcs15infoPtr == NULL )
		{
		/* The appropriate error value to return here is a 
		   CRYPT_ERROR_OVERFLOW because we always try to add a new entry if
		   we can't find an existing one, so the final error status is 
		   always an overflow */
		return( CRYPT_ERROR_OVERFLOW );
		}

	/* If we're clearing an existing entry, we're done */
	if( isDataClear )
		{
		pkcs15freeEntry( pkcs15infoPtr );
		return( CRYPT_OK );
		}

	/* If we're adding new data and there's no existing storage available, 
	   allocate storage for it */
	if( pkcs15infoPtr->dataData == NULL || \
		dataLength > pkcs15infoPtr->dataDataSize )
		{
		REQUIRES( rangeCheck( dataLength, 1, MAX_BUFFER_SIZE ) );
		newData = clAlloc( "addConfigData", dataLength );
		if( newData == NULL )
			return( CRYPT_ERROR_MEMORY );

		/* If there's existing data present, clear and free it */
		if( pkcs15infoPtr->dataData != NULL )
			{
			REQUIRES_PTR( isShortIntegerRangeNZ( pkcs15infoPtr->dataDataSize ),
						  newData ); 
			zeroise( pkcs15infoPtr->dataData, pkcs15infoPtr->dataDataSize );
			clFree( "addConfigData", pkcs15infoPtr->dataData );
			pkcs15infoPtr->dataData = NULL;
			}
		}
	else
		{
		/* There's existing data present and the new data will fit into its
		   storage, re-use the existing storage */
		newData = pkcs15infoPtr->dataData;
		}

	/* Remember the pre-encoded configuration data */
	pkcs15infoPtr->dataData = newData;
	memcpy( pkcs15infoPtr->dataData, data, dataLength );
	pkcs15infoPtr->dataDataSize = dataLength;

	/* Set the type information for the data */
	pkcs15infoPtr->type = PKCS15_SUBTYPE_DATA;
	pkcs15infoPtr->dataType = dataType;

	return( CRYPT_OK );
	}

/* Add a secret key to a PKCS #15 collection.  This functionality is 
   currently unused so returns an error after the initial checks */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int addSecretKey( IN_ARRAY( noPkcs15objects ) PKCS15_INFO *pkcs15info, 
				  IN_LENGTH_SHORT const int noPkcs15objects,
				  IN_HANDLE const CRYPT_HANDLE iCryptContext )
	{
	PKCS15_INFO *pkcs15infoPtr = NULL;
	MESSAGE_DATA msgData;
	char label[ CRYPT_MAX_TEXTSIZE + 8 ];
	int status;

	assert( isWritePtrDynamic( pkcs15info, \
							   sizeof( PKCS15_INFO ) * noPkcs15objects ) );

	REQUIRES( isShortIntegerRangeNZ( noPkcs15objects ) );
	REQUIRES( isHandleRangeValid( iCryptContext ) );

	/* Check the object and make sure that the label of what we're adding
	   doesn't duplicate the label of an existing object */
	status = krnlSendMessage( iCryptContext, IMESSAGE_CHECK, NULL,
							  MESSAGE_CHECK_CRYPT );
	if( cryptStatusError( status ) )
		{
		return( ( status == CRYPT_ARGERROR_OBJECT ) ? \
				CRYPT_ARGERROR_NUM1 : status );
		}
	setMessageData( &msgData, label, CRYPT_MAX_TEXTSIZE );
	status = krnlSendMessage( iCryptContext, IMESSAGE_GETATTRIBUTE_S,
							  &msgData, CRYPT_CTXINFO_LABEL );
	if( cryptStatusError( status ) )
		return( status );
	if( findEntry( pkcs15info, noPkcs15objects, CRYPT_KEYID_NAME, 
				   msgData.data, msgData.length, 
				   KEYMGMT_FLAG_NONE, FALSE ) != NULL )
		return( CRYPT_ERROR_DUPLICATE );

	/* Find out where we can add the new key data */
	pkcs15infoPtr = findFreeEntry( pkcs15info, noPkcs15objects, NULL );
	if( pkcs15infoPtr == NULL )
		return( CRYPT_ERROR_OVERFLOW );

	/* This functionality is currently unused */
	retIntError();
	}

/****************************************************************************
*																			*
*							External Add-a-Key Interface					*
*																			*
****************************************************************************/

/* Add a key to a PKCS #15 collection.  The strategy for adding items is:

										Existing
		New		|	None	| Priv+Pub	| Priv+Cert	|	Cert	|
	------------+-----------+-----------+-----------+-----------+
	Priv + Pub	|	Add		|	----	|	----	|	Add		|
				|			|			|			|			|
	Priv + Cert	|	Add		| Repl.pubk	| Add cert	| Add cert	|
				|			| with cert	| if newer	| if newer	|
	Cert		| If trusted|	Add		| Add cert	| Add cert	|
				|			|			| if newer	| if newer	|
	------------+-----------+-----------+-----------+-----------+ */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 11 ) ) \
int pkcs15AddKey( INOUT_PTR PKCS15_INFO *pkcs15infoPtr, 
				  IN_HANDLE const CRYPT_HANDLE iCryptHandle,
				  IN_BUFFER_OPT( passwordLength ) const void *password, 
				  IN_LENGTH_NAME_Z const int passwordLength,
				  IN_HANDLE const CRYPT_USER iOwnerHandle, 
				  IN_BOOL const BOOLEAN privkeyPresent, 
				  IN_BOOL const BOOLEAN certPresent, 
				  IN_BOOL const BOOLEAN doAddCert, 
				  IN_BOOL const BOOLEAN pkcs15keyPresent,
				  IN_BOOL const BOOLEAN isStorageObject, 
				  INOUT_PTR ERROR_INFO *errorInfo )
	{
	BYTE pubKeyAttributes[ KEYATTR_BUFFER_SIZE + 8 ];
	BYTE privKeyAttributes[ KEYATTR_BUFFER_SIZE + 8 ];
	int pubKeyAttributeSize = 0, privKeyAttributeSize = 0;
	int modulusSize DUMMY_INIT, pkcCryptAlgo, status;

	assert( isWritePtr( pkcs15infoPtr, sizeof( PKCS15_INFO ) ) );
	assert( ( privkeyPresent && \
			  isReadPtrDynamic( password, passwordLength ) ) || \
			( ( !privkeyPresent || isStorageObject ) && \
			  password == NULL && passwordLength == 0 ) );

	REQUIRES( ( privkeyPresent && password != NULL && \
				passwordLength >= MIN_NAME_LENGTH && \
				passwordLength < MAX_ATTRIBUTE_SIZE ) || \
			  ( ( !privkeyPresent || isStorageObject ) && \
				password == NULL && passwordLength == 0 ) );
	REQUIRES( isHandleRangeValid( iCryptHandle ) );
	REQUIRES( iOwnerHandle == DEFAULTUSER_OBJECT_HANDLE || \
			  isHandleRangeValid( iOwnerHandle ) );
	REQUIRES( isBooleanValue( privkeyPresent ) );
	REQUIRES( isBooleanValue( certPresent ) );
	REQUIRES( isBooleanValue( doAddCert ) );
	REQUIRES( isBooleanValue( pkcs15keyPresent ) );
	REQUIRES( isBooleanValue( isStorageObject ) );
	REQUIRES( errorInfo != NULL );

	/* Get information from the context */
	status = krnlSendMessage( iCryptHandle, IMESSAGE_GETATTRIBUTE, 
							  &pkcCryptAlgo, CRYPT_CTXINFO_ALGO );
	if( cryptStatusOK( status ) )
		{
		status = krnlSendMessage( iCryptHandle, IMESSAGE_GETATTRIBUTE, 
								  &modulusSize, CRYPT_CTXINFO_KEYSIZE );
		}
	if( cryptStatusError( status ) )
		return( status );
	ANALYSER_HINT( modulusSize >= MIN_PKCSIZE && \
				   modulusSize <= CRYPT_MAX_PKCSIZE );

	/* Write the attribute information.  We have to rewrite the key
	   information when we add a non-standalone certificate even if we don't 
	   change the key because adding a certificate can affect key 
	   attributes */
	if( ( certPresent && pkcs15keyPresent ) ||		/* Updating existing */
		( privkeyPresent && !pkcs15keyPresent ) )	/* Adding new */
		{
		/* If we're adding a raw private key then we have to write the
		   identifiers that are normally written for the certificate, for
		   the private key (specified by the last function parameter) */
		status = writeKeyAttributes( privKeyAttributes, KEYATTR_BUFFER_SIZE,
									 &privKeyAttributeSize,
									 pubKeyAttributes, KEYATTR_BUFFER_SIZE,
									 &pubKeyAttributeSize, pkcs15infoPtr,
									 iCryptHandle,
									 !certPresent && privkeyPresent && \
										!pkcs15keyPresent );
		if( cryptStatusError( status ) )
			{
			retExt( status, 
					( status, errorInfo, 
					  "Couldn't write PKCS #15 key attributes" ) );
			}
		}

	/* Write the certificate if necessary.  We do this one first because 
	   it's the easiest to back out of */
	if( certPresent && doAddCert )
		{
		/* Select the leaf certificate in case it's a certificate chain */
		status = krnlSendMessage( iCryptHandle, IMESSAGE_SETATTRIBUTE,
								  MESSAGE_VALUE_CURSORFIRST,
								  CRYPT_CERTINFO_CURRENT_CERTIFICATE );
		if( cryptStatusError( status ) )
			return( status );

		/* Write the certificate information.  There may be further 
		   certificates in the chain but we don't try and do anything with 
		   these at this level, the addition of supplemental certificates is 
		   handled by the caller */
		if( pkcs15keyPresent )
			{
			status = pkcs15AddCert( pkcs15infoPtr, iCryptHandle, 
									privKeyAttributes, privKeyAttributeSize, 
									CERTADD_UPDATE_EXISTING, errorInfo );
			}
		else
			{
			status = pkcs15AddCert( pkcs15infoPtr, iCryptHandle, NULL, 0,
									( privkeyPresent || isStorageObject ) ? \
										CERTADD_NORMAL : \
										CERTADD_STANDALONE_CERT, errorInfo );
			}
		if( cryptStatusError( status ) )
			return( status );

		/* If there's no public/private-key context to add, exit */
		if( !privkeyPresent || pkcs15keyPresent )
			return( CRYPT_OK );
		}

	/* Add the public key information if the information hasn't already been 
	   added via a certificate */
	if( !certPresent )
		{
		ENSURES( privkeyPresent && !pkcs15keyPresent );

		ENSURES( isShortIntegerRangeNZ( pubKeyAttributeSize ) );
				 /* Implies pubKeyAttributes initialised */
		status = pkcs15AddPublicKey( pkcs15infoPtr, iCryptHandle, 
									 pubKeyAttributes, pubKeyAttributeSize, 
									 pkcCryptAlgo, modulusSize, isStorageObject,
									 errorInfo );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Add the private key information */
	ENSURES( isShortIntegerRangeNZ( privKeyAttributeSize ) );
			 /* Implies privKeyAttributes initialised */
	return( pkcs15AddPrivateKey( pkcs15infoPtr, iCryptHandle, iOwnerHandle,
								 password, passwordLength, privKeyAttributes,
								 privKeyAttributeSize, pkcCryptAlgo,
								 modulusSize, isStorageObject, errorInfo ) );
	}
#endif /* USE_PKCS15 */
