/*
 * The Initial Developer of the Original Code is International
 * Business Machines Corporation. Portions created by IBM
 * Corporation are Copyright (C) 2005 International Business
 * Machines Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Common Public License as published by
 * IBM Corporation; either version 1 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Common Public License for more details.
 *
 * You should have received a copy of the Common Public License
 * along with this program; if not, a copy can be viewed at
 * http://www.opensource.org/licenses/cpl1.0.php.
 */

#include "data_import.h"
#include "data_common.h"

#include <tpm_pkcs11.h>
#include <tpm_utils.h>

#include <stdlib.h>
#include <unistd.h>
#define _GNU_SOURCE
#include <getopt.h>
#include <errno.h>

#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/asn1.h>
#include <openssl/evp.h>
#include <openssl/err.h>

#include "openssl_compat.h"

/*
 * Global variables
 */
char *g_pszFile   = NULL;		// Object import file name
char *g_pszIdFile = NULL;		// Object identification file name
char *g_pszType   = NULL;		// Object import type
int   g_bPublic   = FALSE;		// Public object specifier
int   g_bYes      = FALSE;		// Yes/No prompt reply
char *g_pszToken  = NULL;		// Token label to be used

int       g_bAttrsValid  = FALSE;
CK_BYTE  *g_pchSubject   = NULL;	// SUBJECT attribute value
CK_LONG   g_subjectLen = 0;
CK_BYTE  *g_pchId        = NULL;	// ID attribute value
CK_ULONG  g_ulIdLen      = 0;
CK_BYTE  *g_pchName      = NULL;	// LABEL attribute value
CK_ULONG  g_ulNameLen    = 0;

/*
 * parseCallback
 *   Process the command specific options.
 */
int
parseCallback( int         a_iOpt,
               const char *a_pszOptArg ) {

	switch ( a_iOpt ) {
		// File with object to be used to obtain subject/id
		case 'i':
			if ( !a_pszOptArg )
				return -1;

			g_pszIdFile = strdup( a_pszOptArg );
			break;

		// Use the specified token label when finding the token
		case 'k':
			if ( !a_pszOptArg )
				return -1;

			g_pszToken = strdup( a_pszOptArg );
			break;

		// Name to use as the LABEL attribute value
		case 'n':
			if ( !a_pszOptArg )
				return -1;

			g_pchName = (CK_BYTE *)strdup( a_pszOptArg );
			g_ulNameLen = strlen( a_pszOptArg );
			break;

		// Make the object public
		case 'p':
			g_bPublic = TRUE;
			break;

		// Only import the specified object type
		case 't':
			if ( !a_pszOptArg )
				return -1;

			if ( ( strcmp( a_pszOptArg, TOKEN_OBJECT_KEY )  != 0 ) &&
			     ( strcmp( a_pszOptArg, TOKEN_OBJECT_CERT ) != 0 ) )
				return -1;

			g_pszType = strdup( a_pszOptArg );
			break;

		// Reply "yes" to any yes/no prompts
		case 'y':
			g_bYes = TRUE;
			break;
	}

	return 0;
}

/*
 * usageCallback
 *   Display command usage information.
 */
void
usageCallback( const char *a_pszCmd ) {

	char *pszArgs[2];
	char *pszArgsDesc[2];

	pszArgs[ 0 ] = "FILE";
	pszArgsDesc[ 0 ] = _("Import the PEM formatted RSA key and/or X.509 certificate object contained in FILE");
	pszArgs[ 1 ] = NULL;
	pszArgsDesc[ 1 ] = NULL;

	logCmdHelpEx( a_pszCmd, pszArgs, pszArgsDesc );
	logCmdOption( "-i, --idfile FILE",
			_("Use FILE as the PEM formatted X.509 certificate input used to obtain the subject and id attributes") );
	logCmdOption( "-k, --token STRING",
			_("Use STRING to identify the label of the PKCS#11 token to be used") );
	logCmdOption( "-n, --name STRING",
			_("Use STRING as the label for the imported object(s)") );
	logCmdOption( "-p, --public",
			_("Import the object(s) as a public object") );
	logCmdOption( "-t, --type key|cert",
			_("Import only the specified object type") );
	logCmdOption( "-y, --yes",
			_("Assume yes as the answer to any confirmation prompt") );
}

/*
 * parseCmd
 *   Parse the command line options.
 */
int
parseCmd( int    a_iArgc,
          char **a_pszArgv ) {

	int  rc;

	char          *pszShortOpts  = "i:k:n:pt:y";
	struct option  stLongOpts[]  = {
			{ "idfile", required_argument, NULL, 'i' },
			{ "name", required_argument, NULL, 'n' },
			{ "public", no_argument, NULL, 'p' },
			{ "token", required_argument, NULL, 'k' },
			{ "type", required_argument, NULL, 't' },
			{ "yes", no_argument, NULL, 'y' },
		};
	int  iNumLongOpts = sizeof( stLongOpts ) / sizeof( struct option );

	rc = genericOptHandler( a_iArgc, a_pszArgv,
		pszShortOpts, stLongOpts, iNumLongOpts,
		parseCallback, usageCallback );
	if ( rc == -1 )
		return -1;

	if ( optind >= a_iArgc ) {
		logMsg( TOKEN_FILE_ERROR );
		usageCallback( a_pszArgv[ 0 ] );
		return -1;
	}

	g_pszFile = strdup( a_pszArgv[ optind ] );

	return 0;
}

/*
 * findExistingObjects
 *   Search for objects of the supplied type that have a SUBJECT
 *   and ID attribute equal to the values of the object to be
 *   imported.
 */
int
findExistingObjects( CK_SESSION_HANDLE   a_hSession,
                     CK_ATTRIBUTE       *a_tAttr,
                     CK_ULONG            a_ulAttrCount,
                     CK_OBJECT_HANDLE  **a_phObject,
                     CK_ULONG           *a_pulObjectCount ) {

	CK_RV     rv;
	CK_BBOOL  bTrue = TRUE;

	// Set up default search attributes
	CK_ATTRIBUTE  tDefaultAttr[]     = {
			{ CKA_TOKEN, &bTrue, sizeof( bTrue ) },
			{ CKA_SUBJECT, g_pchSubject, g_subjectLen },
			{ CKA_ID, g_pchId, g_ulIdLen },
		};
	CK_ULONG      ulDefaultAttrCount = sizeof( tDefaultAttr ) / sizeof( CK_ATTRIBUTE );

	CK_ATTRIBUTE  tAttr[ ulDefaultAttrCount + a_ulAttrCount ];
	CK_ULONG      ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	*a_phObject = NULL;
	*a_pulObjectCount = 0;

	// Apply attributes and search
	memcpy( tAttr, tDefaultAttr, ulDefaultAttrCount * sizeof( CK_ATTRIBUTE ) );
	if ( a_ulAttrCount )
		memcpy( tAttr + ulDefaultAttrCount, a_tAttr, a_ulAttrCount * sizeof( CK_ATTRIBUTE ) );

	rv = findObjects( a_hSession, tAttr, ulAttrCount, a_phObject, a_pulObjectCount );

	return ( rv == CKR_OK ) ? 0 : -1;
}

/*
 * checkExistingObjects
 *   Use findExistingObjects to determine if objects of the
 *   supplied type currently exist.  If so, prompt the user as
 *   to whether to replace the existing objects.
 */
int
checkExistingObjects( CK_SESSION_HANDLE  a_hSession,
                      CK_ATTRIBUTE      *a_tAttr,
                      CK_ULONG           a_ulAttrCount,
                      const char        *a_pszObject ) {

	int  rc = -1;

	CK_OBJECT_HANDLE *phObject      = NULL;
	CK_ULONG          ulObjectCount = 0;

	char  szPrompt[ strlen( TOKEN_ID_PROMPT ) + strlen( a_pszObject ) + 1 ];
	char *pszReply = NULL;

	if ( g_bAttrsValid ) {
		// Search for existing objects
		if ( findExistingObjects( a_hSession, a_tAttr, a_ulAttrCount, &phObject, &ulObjectCount ) == -1 )
			goto out;

		if ( ulObjectCount > 0 ) {
			// One or more objects exists
			if ( !g_bYes ) {
				// Prompt for whether to replace the existing objects
				sprintf( szPrompt, TOKEN_ID_PROMPT, a_pszObject );
				pszReply = getReply( szPrompt, 1 );
				if ( !pszReply ||
					( strlen( pszReply ) == 0 ) ||
					( strcasecmp( pszReply, TOKEN_ID_NO ) == 0 ) ) {
					goto out;
				}
			}
		}
	}

	rc = 0;

out:
	free( phObject );
	free( pszReply );

	return rc;
}

/*
 * destroyExistingObjects
 *   Use findExistingObjects to locate all objects of the
 *   supplied type that currently exists and destroy them.
 */
int
destroyExistingObjects( CK_SESSION_HANDLE  a_hSession,
                        CK_ATTRIBUTE      *a_tAttr,
                        CK_ULONG           a_ulAttrCount ) {

	int  rc = -1;

	CK_RV             rv;
	CK_OBJECT_HANDLE *phObject      = NULL;
	CK_ULONG          ulObjectCount = 0;

	if ( g_bAttrsValid ) {
		// Search for existing objects
		if ( findExistingObjects( a_hSession, a_tAttr, a_ulAttrCount, &phObject, &ulObjectCount ) == -1 )
			goto out;

		// Destroy each object found
		while ( ulObjectCount > 0 ) {
			rv = destroyObject( a_hSession, phObject[ --ulObjectCount ] );
			if ( rv != CKR_OK )
				goto out;
		}
	}

	rc = 0;

out:
	free( phObject );

	return rc;
}

/*
 * readX509Cert
 *   Use the OpenSSL library to read a PEM formatted X509 certificate.
 */
int
readX509Cert( const char  *a_pszFile,
              int          a_bCheckKey,
              X509       **a_pX509 ) {

	int rc = -1;

	FILE     *pFile = stdin;
	X509     *pX509 = NULL;
	EVP_PKEY *pKey  = NULL;

	*a_pX509 = NULL;

	// Open the file to be read
	if ( a_pszFile ) {
		errno = 0;
		pFile = fopen( a_pszFile, "r" );
		if ( !pFile ) {
			logError( TOKEN_FILE_OPEN_ERROR, a_pszFile, strerror( errno ) );
			goto out;
		}
	}

	// Read the X509 certificate
	pX509 = PEM_read_X509( pFile, NULL, NULL, NULL );
	if ( !pX509 ) {
		unsigned long  ulError = ERR_get_error( );

		// Not necessarily an error if the file doesn't contain the cert
		if ( ( ERR_GET_LIB( ulError ) == ERR_R_PEM_LIB ) &&
		     ( ERR_GET_REASON( ulError ) == PEM_R_NO_START_LINE ) ) {
			logInfo( TOKEN_OPENSSL_ERROR, ERR_error_string( ulError, NULL ) );
			rc = 0;
		}
		else
			logError( TOKEN_OPENSSL_ERROR, ERR_error_string( ulError, NULL ) );

		goto out;
	}

	// Make sure the certificate uses an RSA key
	if ( !a_bCheckKey ) {
		rc = 0;
		goto out;
	}

	pKey = X509_get_pubkey( pX509 );
	if ( !pKey ) {
		logInfo( TOKEN_OPENSSL_ERROR,
			ERR_error_string( ERR_get_error( ), NULL ) );

		X509_free( pX509 );
		pX509 = NULL;
		goto out;
	}

	if ( EVP_PKEY_base_id( pKey ) != EVP_PKEY_RSA ) {
		logError( TOKEN_RSA_KEY_ERROR );

		X509_free( pX509 );
		pX509 = NULL;
		goto out;
	}

	rc = 0;

out:
	*a_pX509 = pX509;

	if ( a_pszFile && pFile )
		fclose( pFile );

	return rc;
}

/*
 * checkX509Cert
 *   Use checkExistingObjects to search for X_509 objects
 *   that match the attributes of the X_509 object to be imported.
 */
int
checkX509Cert( CK_SESSION_HANDLE  a_hSession ) {

	CK_CERTIFICATE_TYPE  tX509       = CKC_X_509;
	CK_ATTRIBUTE         tAttr[]     = {
			{ CKA_CERTIFICATE_TYPE, &tX509, sizeof( tX509 ) },
		};
	CK_ULONG             ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	return checkExistingObjects( a_hSession, tAttr, ulAttrCount, TOKEN_ID_X509_CERT );
}

/*
 * destroyX509CertObject
 *   Use destroyExistingObjects to destroy X_509 objects
 *   that match the attributes of the X_509 object to be imported.
 */
int
destroyX509CertObject( CK_SESSION_HANDLE  a_hSession ) {

	CK_CERTIFICATE_TYPE  tX509       = CKC_X_509;
	CK_ATTRIBUTE         tAttr[]     = {
			{ CKA_CERTIFICATE_TYPE, &tX509, sizeof( tX509 ) },
		};
	CK_ULONG             ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	return destroyExistingObjects( a_hSession, tAttr, ulAttrCount );
}

/*
 * createX509CertObject
 *   Create an X_509 object.
 */
int
createX509CertObject( X509              *a_pX509,
                      CK_SESSION_HANDLE  a_hSession ) {

	int  rc = -1;

	CK_RV  rv;

	CK_BBOOL  bTrue = TRUE;

	X509_NAME    *pIssuer    = NULL;
	ASN1_INTEGER *pSerialNum = NULL;

	CK_BYTE  *pchIssuer      = NULL;
	CK_LONG  issuerLen    = 0;
	CK_BYTE  *pchSerialNum   = NULL;
	CK_LONG  serialNumLen = 0;
	CK_BYTE  *pchCert        = NULL;
	CK_LONG  certLen      = 0;

	CK_OBJECT_CLASS      clCertClass = CKO_CERTIFICATE;
	CK_CERTIFICATE_TYPE  tCertType   = CKC_X_509;
	CK_BBOOL             bPrivate    = ( !g_bPublic ) ? TRUE : FALSE;

	// The issuer, serial number, and value attributes must be completed
	// before the object is created
	CK_ATTRIBUTE  tCertAttr[] = {
			{ CKA_CLASS, &clCertClass, sizeof( clCertClass ) },
			{ CKA_TOKEN, &bTrue, sizeof( bTrue ) },
			{ CKA_PRIVATE, &bPrivate, sizeof( bPrivate ) },
			{ CKA_MODIFIABLE, &bTrue, sizeof( bTrue ) },
			{ CKA_LABEL, g_pchName, g_ulNameLen },
			{ CKA_CERTIFICATE_TYPE, &tCertType, sizeof( tCertType ) },
			{ CKA_SUBJECT, g_pchSubject, g_subjectLen },
			{ CKA_ID, g_pchId, g_ulIdLen },
			{ CKA_ISSUER, NULL, 0 },
			{ CKA_SERIAL_NUMBER, NULL, 0 },
			{ CKA_VALUE, NULL, 0 },
		};
	CK_ULONG  ulCertAttrCount = sizeof( tCertAttr ) / sizeof( CK_ATTRIBUTE );

	CK_OBJECT_HANDLE  hObject;

	// Get the issuer name from the X509 certificate
	pIssuer = X509_get_issuer_name( a_pX509 );
	if ( !pIssuer ) {
		logError( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}
	issuerLen = i2d_X509_NAME( pIssuer, &pchIssuer );
	if ( issuerLen < 0 ) {
		logError( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}

	// Get the serial number from the X509 certificate
	pSerialNum = X509_get_serialNumber( a_pX509 );
	if ( !pSerialNum ) {
		logError( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}
	serialNumLen = i2d_ASN1_INTEGER( pSerialNum, &pchSerialNum );
	if ( serialNumLen < 0 ) {
		logError( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}

	// Get a DER encoded format of the X509 certificate
	certLen = i2d_X509( a_pX509, &pchCert );
	if ( certLen < 0 ) {
		logError( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}

	// Set the attribute values
	tCertAttr[ 8 ].pValue = pchIssuer;
	tCertAttr[ 8 ].ulValueLen = issuerLen;
	tCertAttr[ 9 ].pValue = pchSerialNum;
	tCertAttr[ 9 ].ulValueLen = serialNumLen;
	tCertAttr[ 10 ].pValue = pchCert;
	tCertAttr[ 10 ].ulValueLen = certLen;

	// Create the X509 certificate object
	rv = createObject( a_hSession, tCertAttr, ulCertAttrCount, &hObject );
	if ( rv != CKR_OK )
		goto out;

	rc = 0;

out:
	OPENSSL_free( pchIssuer );
	OPENSSL_free( pchCert );
	OPENSSL_free( pchSerialNum );

	return rc;
}

/*
 * doX509Cert
 *   Process an X509 certificate for import.
 */
int
doX509Cert( X509              *a_pX509,
            CK_SESSION_HANDLE  a_hSession ) {

	int  rc = -1;

	if ( destroyX509CertObject( a_hSession ) == -1 )
		goto out;

	if ( createX509CertObject( a_pX509, a_hSession ) == -1 )
		goto out;

	rc = 0;

out:
	return rc;
}

/*
 * readRsaKey
 *   Use the OpenSSL library to read a PEM formatted RSA key.
 */
int
readRsaKey( const char  *a_pszFile,
            RSA        **a_pRsa ) {

	int  rc = -1;

	FILE *pFile = stdin;
	RSA  *pRsa  = NULL;

	*a_pRsa = NULL;

	// Open the file to be read
	if ( a_pszFile ) {
		errno = 0;
		pFile = fopen( a_pszFile, "r" );
		if ( !pFile ) {
			logError( TOKEN_FILE_OPEN_ERROR, a_pszFile, strerror( errno ) );
			goto out;
		}
	}

	// Read the RSA key
	//   This reads the public key also, not just the private key
	pRsa = PEM_read_RSAPrivateKey( pFile, NULL, NULL, NULL );
	if ( !pRsa ) {
		unsigned long  ulError = ERR_get_error( );

		// Not necessarily an error if the file doesn't contain the key
		if ( ( ERR_GET_LIB( ulError ) == ERR_R_PEM_LIB ) &&
		     ( ERR_GET_REASON( ulError ) == PEM_R_NO_START_LINE ) ) {
			logInfo( TOKEN_OPENSSL_ERROR, ERR_error_string( ulError, NULL ) );
			rc = 0;
		}
		else
			logError( TOKEN_OPENSSL_ERROR, ERR_error_string( ulError, NULL ) );

		goto out;
	}

	rc = 0;

out:
	if ( a_pszFile && pFile )
		fclose( pFile );

	*a_pRsa = pRsa;

	return rc;
}

/*
 * checkRsaPubKey
 *   Use checkExistingObjects to search for RSA public key objects
 *   that match the attributes of the X509's RSA public key object
 *   to be imported.
 */
int
checkRsaPubKey( CK_SESSION_HANDLE  a_hSession ) {

	CK_OBJECT_CLASS  tPubKey  = CKO_PUBLIC_KEY;
	CK_KEY_TYPE      tRsa     = CKK_RSA;
	CK_ATTRIBUTE     tAttr[]  = {
			{ CKA_CLASS, &tPubKey, sizeof( tPubKey ) },
			{ CKA_KEY_TYPE, &tRsa, sizeof( tRsa ) },
		};
	CK_ULONG         ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	return checkExistingObjects( a_hSession, tAttr, ulAttrCount, TOKEN_ID_RSA_PUBKEY );
}

/*
 * checkRsaKey
 *   Use checkExistingObjects to search for RSA objects
 *   that match the attributes of the RSA object to be imported.
 */
int
checkRsaKey( CK_SESSION_HANDLE  a_hSession ) {

	CK_KEY_TYPE   tRsa        = CKK_RSA;
	CK_ATTRIBUTE  tAttr[]     = {
			{ CKA_KEY_TYPE, &tRsa, sizeof( tRsa ) },
		};
	CK_ULONG      ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	return checkExistingObjects( a_hSession, tAttr, ulAttrCount, TOKEN_ID_RSA_KEY );
}

/*
 * destroyRsaKeyObject
 *   Use destroyExistingObjects to destroy RSA objects
 *   that match the attributes of the RSA object to be imported.
 */
int
destroyRsaPubKeyObject( CK_SESSION_HANDLE  a_hSession ) {

	CK_OBJECT_CLASS  tPubKey     = CKO_PUBLIC_KEY;
	CK_KEY_TYPE      tRsa        = CKK_RSA;
	CK_ATTRIBUTE     tAttr[]     = {
			{ CKA_CLASS, &tPubKey, sizeof( tPubKey ) },
			{ CKA_KEY_TYPE, &tRsa, sizeof( tRsa ) },
		};
	CK_ULONG         ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	return destroyExistingObjects( a_hSession, tAttr, ulAttrCount );
}

/*
 * destroyRsaKeyObject
 *   Use destroyExistingObjects to destroy RSA objects
 *   that match the attributes of the RSA object to be imported.
 */
int
destroyRsaKeyObject( CK_SESSION_HANDLE  a_hSession ) {

	CK_KEY_TYPE   tRsa        = CKK_RSA;
	CK_ATTRIBUTE  tAttr[]     = {
			{ CKA_KEY_TYPE, &tRsa, sizeof( tRsa ) },
		};
	CK_ULONG      ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	return destroyExistingObjects( a_hSession, tAttr, ulAttrCount );
}

/*
 * createRsaPubKeyObject
 *   Create an RSA public key object.
 */
int
createRsaPubKeyObject( RSA               *a_pRsa,
                       CK_SESSION_HANDLE  a_hSession,
                       CK_OBJECT_HANDLE  *a_hObject ) {

	int  rc = -1;

	const BIGNUM *rsa_n, *rsa_e;
	RSA_get0_key( a_pRsa, &rsa_n, &rsa_e, NULL );

	int  nLen = BN_num_bytes( rsa_n );
	int  eLen = BN_num_bytes( rsa_e );

	CK_RV  rv;

	CK_BBOOL  bTrue  = TRUE;
	CK_BBOOL  bFalse = FALSE;

	CK_BYTE *n = malloc( nLen );
	CK_BYTE *e = malloc( eLen );

	CK_OBJECT_CLASS  clPubClass  = CKO_PUBLIC_KEY;
	CK_KEY_TYPE      tKeyType    = CKK_RSA;
	CK_BBOOL         bPrivate    = ( !g_bPublic ) ? TRUE : FALSE;

	CK_ATTRIBUTE  tAttr[] = {
			{ CKA_CLASS, &clPubClass, sizeof( clPubClass ) },
			{ CKA_TOKEN, &bTrue, sizeof( bTrue ) },
			{ CKA_PRIVATE, &bPrivate, sizeof( bPrivate ) },
			{ CKA_MODIFIABLE, &bTrue, sizeof( bTrue ) },
			{ CKA_LABEL, g_pchName, g_ulNameLen },
			{ CKA_KEY_TYPE, &tKeyType, sizeof( tKeyType ) },
			{ CKA_ID, g_pchId, g_ulIdLen },
			{ CKA_SUBJECT, g_pchSubject, g_subjectLen },
			{ CKA_ENCRYPT, &bTrue, sizeof( bTrue ) },
			{ CKA_VERIFY, &bTrue, sizeof( bTrue ) },
			{ CKA_VERIFY_RECOVER, &bFalse, sizeof( bFalse ) },
			{ CKA_WRAP, &bFalse, sizeof( bFalse ) },
			{ CKA_MODULUS, n, nLen },
			{ CKA_PUBLIC_EXPONENT, e, eLen },
		};
	CK_ULONG  ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	*a_hObject = 0;

	if ( !n || !e ) {
		logError( TOKEN_MEMORY_ERROR );
		goto out;
	}

	// Get binary representations of the RSA key information
	BN_bn2bin( rsa_n, n );
	BN_bn2bin( rsa_e, e );

	// Create the RSA public key object
	rv = createObject( a_hSession, tAttr, ulAttrCount, a_hObject );
	if ( rv != CKR_OK )
		goto out;

	rc = 0;

out:
	free( n );
	free( e );

	return rc;
}

/*
 * createRsaPrivKeyObject
 *   Create an RSA private key object.
 */
int
createRsaPrivKeyObject( RSA               *a_pRsa,
                        CK_SESSION_HANDLE  a_hSession,
                        CK_OBJECT_HANDLE  *a_hObject ) {

	int  rc = -1;

	const BIGNUM *rsa_n, *rsa_e, *rsa_d;
	const BIGNUM *rsa_p, *rsa_q;
	const BIGNUM *rsa_dmp1, *rsa_dmq1, *rsa_iqmp;

	RSA_get0_key( a_pRsa, &rsa_n, &rsa_e, &rsa_d );
	RSA_get0_factors( a_pRsa, &rsa_p, &rsa_q );
	RSA_get0_crt_params( a_pRsa, &rsa_dmp1, &rsa_dmq1, &rsa_iqmp );

	int  nLen = BN_num_bytes( rsa_n );
	int  eLen = BN_num_bytes( rsa_e );
	int  dLen = BN_num_bytes( rsa_d );
	int  pLen = BN_num_bytes( rsa_p );
	int  qLen = BN_num_bytes( rsa_q );
	int  dmp1Len = BN_num_bytes( rsa_dmp1 );
	int  dmq1Len = BN_num_bytes( rsa_dmq1 );
	int  iqmpLen = BN_num_bytes( rsa_iqmp );

	CK_RV  rv;

	CK_BBOOL  bTrue  = TRUE;
	CK_BBOOL  bFalse = FALSE;

	CK_BYTE *n = malloc( nLen );
	CK_BYTE *e = malloc( eLen );
	CK_BYTE *d = malloc( dLen );
	CK_BYTE *p = malloc( pLen );
	CK_BYTE *q = malloc( qLen );
	CK_BYTE *dmp1 = malloc( dmp1Len );
	CK_BYTE *dmq1 = malloc( dmq1Len );
	CK_BYTE *iqmp = malloc( iqmpLen );

	CK_OBJECT_CLASS  clPrivClass = CKO_PRIVATE_KEY;
	CK_KEY_TYPE      tKeyType    = CKK_RSA;
	CK_BBOOL         bPrivate    = ( !g_bPublic ) ? TRUE : FALSE;

	CK_ATTRIBUTE  tAttr[] = {
			{ CKA_CLASS, &clPrivClass, sizeof( clPrivClass ) },
			{ CKA_TOKEN, &bTrue, sizeof( bTrue ) },
			{ CKA_PRIVATE, &bPrivate, sizeof( bPrivate ) },
			{ CKA_MODIFIABLE, &bTrue, sizeof( bTrue ) },
			{ CKA_LABEL, g_pchName, g_ulNameLen },
			{ CKA_KEY_TYPE, &tKeyType, sizeof( tKeyType ) },
			{ CKA_ID, g_pchId, g_ulIdLen },
			{ CKA_SUBJECT, g_pchSubject, g_subjectLen },
			{ CKA_SENSITIVE, &bTrue, sizeof( bTrue ) },
			{ CKA_DECRYPT, &bTrue, sizeof( bTrue ) },
			{ CKA_SIGN, &bTrue, sizeof( bTrue ) },
			{ CKA_SIGN_RECOVER, &bFalse, sizeof( bFalse ) },
			{ CKA_UNWRAP, &bFalse, sizeof( bFalse ) },
			{ CKA_EXTRACTABLE, &bFalse, sizeof( bFalse ) },
			{ CKA_MODULUS, n, nLen },
			{ CKA_PUBLIC_EXPONENT, e, eLen },
			{ CKA_PRIVATE_EXPONENT, d, dLen },
			{ CKA_PRIME_1, p, pLen },
			{ CKA_PRIME_2, q, qLen },
			{ CKA_EXPONENT_1, dmp1, dmp1Len },
			{ CKA_EXPONENT_2, dmq1, dmq1Len },
			{ CKA_COEFFICIENT, iqmp, iqmpLen },
		};
	CK_ULONG  ulAttrCount = sizeof( tAttr ) / sizeof( CK_ATTRIBUTE );

	*a_hObject = 0;

	if ( !n || !e || !d || !p || !q || !dmp1 || !dmq1 || !iqmp ) {
		logError( TOKEN_MEMORY_ERROR );
		goto out;
	}

	// Get binary representations of the RSA key information
	BN_bn2bin( rsa_n, n );
	BN_bn2bin( rsa_e, e );
	BN_bn2bin( rsa_d, d );
	BN_bn2bin( rsa_p, p );
	BN_bn2bin( rsa_q, q );
	BN_bn2bin( rsa_dmp1, dmp1 );
	BN_bn2bin( rsa_dmq1, dmq1 );
	BN_bn2bin( rsa_iqmp, iqmp );

	// Create the RSA private key object
	rv = createObject( a_hSession, tAttr, ulAttrCount, a_hObject );
	if ( rv != CKR_OK )
		goto out;

	rc = 0;

out:
	free( n );
	free( e );
	free( d );
	free( p );
	free( q );
	free( dmp1 );
	free( dmq1 );
	free( iqmp );

	return rc;
}

/*
 * createRsaKeyObject
 *   Create an RSA key object (both public and private).
 */
int
createRsaKeyObject( RSA               *a_pRsa,
                    CK_SESSION_HANDLE  a_hSession ) {

	int  rc = -1;

	CK_OBJECT_HANDLE  hPubObject;
	CK_OBJECT_HANDLE  hPrivObject;

	// Create the RSA public key object
	if ( createRsaPubKeyObject( a_pRsa, a_hSession, &hPubObject ) == -1 )
		goto out;

	// Create the RSA private key object
	if ( createRsaPrivKeyObject( a_pRsa, a_hSession, &hPrivObject ) == -1 ) {
		// Private key object creation failed, destroy the public
		// key object just created
		destroyObject( a_hSession, hPubObject );
		goto out;
	}

	rc = 0;

out:
	return rc;
}

/*
 * doRsaPubKey
 *   Process an RSA public key for import.
 */
int
doRsaPubKey( RSA               *a_pRsa,
             CK_SESSION_HANDLE  a_hSession ) {

	int  rc = -1;

	CK_OBJECT_HANDLE  hObject;

	if ( destroyRsaPubKeyObject( a_hSession ) == -1 )
		goto out;

	if ( createRsaPubKeyObject( a_pRsa, a_hSession, &hObject ) == -1 )
		goto out;

	rc = 0;

out:
	return rc;
}

/*
 * doRsaKey
 *   Process an RSA key for import.
 */
int
doRsaKey( RSA               *a_pRsa,
          CK_SESSION_HANDLE  a_hSession ) {

	int  rc = -1;

	if ( destroyRsaKeyObject( a_hSession ) == -1 )
		goto out;

	if ( createRsaKeyObject( a_pRsa, a_hSession ) == -1 )
		goto out;

	rc = 0;

out:
	return rc;
}

/*
 * getSubjectId
 *   Extract the subject name and key identifier from an
 *   X509 certificate for use as the SUBJECT and ID attributes.
 */
int
getSubjectId( X509 *a_pX509 ) {

	int  rc = -1;

	char *pszReply = NULL;

	X509              *pX509    = a_pX509;
	X509_NAME         *pSubject = NULL;
	ASN1_OCTET_STRING *pSkid    = NULL;

	// Use the Id input file if specified
	if ( g_pszIdFile )
		if ( readX509Cert( g_pszIdFile, FALSE, &pX509 ) == -1 )
			goto out;

	if ( !pX509 ) {
		// Prompt the user about creating without it.
		if ( !g_bYes ) {
			// Prompt for whether to import without the attributes
			pszReply = getReply( TOKEN_ID_MISSING_PROMPT, 1 );
			if ( !pszReply ||
				( strlen( pszReply ) == 0 ) ||
				( strcasecmp( pszReply, TOKEN_ID_NO ) == 0 ) ) {
				goto out;
			}
		}

		rc = 0;
		goto out;
	}

	// Get the subject name from the X509 certificate
	pSubject = X509_get_subject_name( pX509 );
	if ( !pSubject ) {
		logInfo( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}

	// Get the DER encoded format of the subject name
	g_subjectLen = i2d_X509_NAME( pSubject, &g_pchSubject );
	if ( g_subjectLen < 0 ) {
		logInfo( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}

	// Get the subject key identifier from the X509 certficate
	pSkid = X509_get_ext_d2i( pX509, NID_subject_key_identifier, NULL, NULL );
	if ( !pSkid ) {
		logInfo( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}

	// Get the ASCII string format of the subject key identifier
	g_pchId = (CK_BYTE *)i2s_ASN1_OCTET_STRING( NULL, pSkid );
	if ( !g_pchId ) {
		logInfo( TOKEN_OPENSSL_ERROR,
				ERR_error_string( ERR_get_error( ), NULL ) );
		goto out;
	}
	g_ulIdLen = strlen( (char *)g_pchId );

	g_bAttrsValid = TRUE;

	rc = 0;

out:
	// Free the structure if it was created for this function
	if ( pX509 && ( pX509 != a_pX509 ) )
		X509_free( pX509 );

	ASN1_OCTET_STRING_free( pSkid );
	free( pszReply );

	return rc;
}

int
main( int    a_iArgc,
      char **a_pszArgv ) {

	int  rc = 1;

	char *pszPin    = NULL;

	CK_RV              rv        = CKR_OK;
	CK_SESSION_HANDLE  hSession  = 0;

	X509 *pX509   = NULL;
	RSA  *pPubRsa = NULL;
	RSA  *pRsa    = NULL;

	// Set up i18n
	initIntlSys( );

	// Initialize OpenSSL
	OpenSSL_add_all_algorithms( );
	ERR_load_crypto_strings( );

	// Parse the command
	if ( parseCmd( a_iArgc, a_pszArgv ) == -1 )
		goto out;

	// Open the PKCS#11 TPM Token
	rv = openToken( g_pszToken );
	if ( rv != CKR_OK )
		goto out;

	// Make sure the token is initialized
	if ( !isTokenInitialized( ) ) {
		logMsg( TOKEN_NOT_INIT_ERROR );
		goto out;
	}

	// Create the structures based on the input
	if ( !g_pszType ) {
		if ( readX509Cert( g_pszFile, TRUE, &pX509 ) == -1 )
			goto out;
		if ( readRsaKey( g_pszFile, &pRsa ) == -1 )
			goto out;
		if ( !pX509 && !pRsa ) {
			logError( TOKEN_OBJECT_ERROR );
			goto out;
		}
	}
	else if ( strcmp( g_pszType, TOKEN_OBJECT_CERT ) == 0 ) {
		if ( readX509Cert( g_pszFile, TRUE, &pX509 ) == -1 )
			goto out;
		if ( !pX509 ) {
			logError( TOKEN_OBJECT_ERROR );
			goto out;
		}
	}
	else if ( strcmp( g_pszType, TOKEN_OBJECT_KEY ) == 0 ) {
		if ( readRsaKey( g_pszFile, &pRsa ) == -1 )
			goto out;
		if ( !pRsa ) {
			logError( TOKEN_OBJECT_ERROR );
			goto out;
		}
	}

	// Open a session
	rv = openTokenSession( CKF_RW_SESSION, &hSession );
	if ( rv != CKR_OK )
		goto out;

	// Check the scope of the request, which will determine the login
	// requirements:
	//   Public  = no password, no login
	//   Private = user password, user login (default)
	if ( !g_bPublic ) {
		pszPin = getPlainPasswd( TOKEN_USER_PIN_PROMPT, FALSE );
		if ( !pszPin )
			goto out;

		// Login to the token
		rv = loginToken( hSession, CKU_USER, pszPin );
		if ( rv != CKR_OK )
			goto out;
	}

	// Obtain the subject name and id, these are used to
	// uniquely identify the certificate/key relation
	if ( getSubjectId( pX509 ) == -1 ) {
		logError( TOKEN_ID_ERROR );
		goto out;
	}

	// Now check for existing objects that may get replaced
	// prior to processing the request(s)
	if ( pX509 ) {
		if ( checkX509Cert( hSession ) == -1 ) {
			goto out;
		}

		// If we are not importing any RSA keys, use the
		// public key from the certificate
		if ( !pRsa ) {
			if ( checkRsaPubKey( hSession ) == -1 ) {
				goto out;
			}
		}

		pPubRsa = EVP_PKEY_get1_RSA( X509_get_pubkey( pX509 ) );
	}
	if ( pRsa ) {
		if ( checkRsaKey( hSession ) == -1 ) {
			goto out;
		}
	}

	// Process the request(s)
	if ( pX509 ) {
		if ( doX509Cert( pX509, hSession ) == -1 )
			goto out;

		// If we are not importing any RSA keys, use the
		// public key from the certificate
		if ( !pRsa ) {
			if ( doRsaPubKey( pPubRsa, hSession ) == -1 )
				goto out;
		}
	}
	if ( pRsa ) {
		if ( doRsaKey( pRsa, hSession ) == -1 )
			goto out;
	}

	rc = 0;

out:
	shredPasswd( pszPin );

	if ( hSession )
		closeTokenSession( hSession );

	closeToken( );

	free( g_pszFile );
	free( g_pszIdFile );
	free( g_pszType );
	X509_free( pX509 );
	RSA_free( pRsa );
	OPENSSL_free( g_pchSubject );
	OPENSSL_free( g_pchId );
	free( g_pchName );

	EVP_cleanup( );

	if ( rc == 0 )
		logInfo( TOKEN_CMD_SUCCESS, a_pszArgv[ 0 ] );
	else
		logInfo( TOKEN_CMD_FAILED, a_pszArgv[ 0 ] );

	return rc;
}
