/****************************************************************************
*																			*
*						  cryptlib Device Test Routines						*
*						Copyright Peter Gutmann 1997-2006					*
*																			*
****************************************************************************/

#include "cryptlib.h"
#include "test/test.h"

#if defined( __MVS__ ) || defined( __VMCMS__ )
  /* Suspend conversion of literals to ASCII. */
  #pragma convlit( suspend )
#endif /* IBM big iron */
#if defined( __ILEC400__ )
  #pragma convert( 0 )
#endif /* IBM medium iron */

/* Set the following to a nonzero value to test cryptlib's device init
   capability.  THIS WILL ZEROISE/ERASE THE DEVICE BEING TESTED AS A PART
   OF THE PROCESS.  All data contained in it will be destroyed */

#define TEST_INITIALISE_CARD	0

/* If the device is very slow (e.g. a smart card), clear the following to
   not test the keygen capabilities of the device.  You can set this once
   initially to generate the test keys and then re-clear it to use the
   initially-generated keys from then on */

#define TEST_KEYGEN				1
#if TEST_INITIALISE_CARD > 0
  #undef TEST_KEYGEN
  #define TEST_KEYGEN			1	/* Must be 1 if initialising card */
#endif /* TEST_INITIALISE_CARD */

/* When testing high-level functionality, it's useful to be able to disable
   the low-level algorithm tests and go straight to the high-level tests.
   The following define can be used to disable the algorithm tests */

#define TEST_ALGORITHMS			1

#ifdef TEST_DEVICE

/* The device code will produce a large number of warnings because of ASCII
   <-> Unicode issues, since there aren't any PKCS #11 drivers for WinCE
   it's not worth adding a mountain of special-case code to handle this so
   we no-op it out under WinCE */

#ifndef _WIN32_WCE

/****************************************************************************
*																			*
*								Device Information							*
*																			*
****************************************************************************/

/* Device information tables for PKCS #11 device types.  This lists all the
   devices we know about and can check for.  If you have a PKCS #11 device
   that isn't listed below, you need to add an entry with its name and a
   password and key object label usable for testing to the table, and also
   add the name of the driver as a CRYPT_OPTION_DEVICE_PKCS11_DVRxx entry so
   cryptlib can load the appropriate driver for it.  To add this, use the
   updateConfig() function in testlib.c, see the code comments there for more
   details.

   The ActivCard driver is so broken that it's incredible it works at all,
   it's more of a PKCS #11-like API that only works if you use it in exactly
   the same way as the single test case that ActivCard must have used to
   evaluate it.  The Telesec driver is even more broken than that (it's so
   bad that it doesn't even work with Netscape), it just fakes a PKCS #11
   API while doing something completely different.

   The SEIS EID cards name their private key objects slightly differently
   from the name used in the software-only eID driver, if you're using a
   card-based version you need to switch the commented lines below to the
   alternate name.

   The Rainbow iKey uses Datakey drivers, so the Datakey test below will work
   for both Datakey cards/keys and iKeys.  The newer Rainbow USB tokens
   (using DataKey 232 drivers) can't be usefully initialised via PKCS #11
   but have to be initialised using the vendor utility or operations fail in
   strange and illogical ways.  In addition the driver partially ignores
   user-specified key attributes such as encrypt-only or sign-only and uses
   its own internal defaults.  Finally, operations with these keys then fail
   with a key-type-inconsistent error even though there's nothing wrong with
   them.  The solution to these problems is to use the Datakey 201 drivers
   (intended for Entrust compatibility, which means they actually test them
   properly), which work properly.

   The iD2 driver implements multiple virtual slots, one for each key type,
   so the entry is given in the extended driver::slot name format to tell
   cryptlib which slot to use.

   To reset the Rainbow card after it locks up and stops responding to
   commands, run /samples/cryptoki20/sample.exe, enter 1 CR, 4 CR, 5 CR,
   7 CR 2 CR "rainbow" CR, g CR "test" CR q CR (you need to follow that
   sequence exactly for it to work).

   To (try to) get the Eracom 3.09 CProv to work, delete the /cryptoki
   directory (where it dumps all its config data, ignoring the settings in
   cryptoki.ini), run ctconf.exe to set up a new device config, then
   run ctconf -v -n0.  Then set TEST_INITIALISE_CARD to a nonzero value
   and things should run OK (as with the Rainbow tests, you need to follow
   this exactly for it to work).  Re-running the test with the initialised
   device, or trying to re-run the initialisation, both fail.  The re-init
   reports that no login is required for the token, returns an already-
   logged-in error if an attempt is made to log in, and returns a not-logged-
   in error of an attempt is made to do anything that needs a login.  The
   re-use of the initialised device fails with an invalid object handle
   for every object that's created (that is, it's possible to create any
   object, but any attempt to use it returns an invalid object handle
   error).  The Eracom 2.10 CProv works OK (it may be necessary to manually
   initialise the token using ctinit after the initial install).

   The Spyrus USB drivers don't get on with various drivers for other USB
   devices such as USB printers (basic devices like USB storage keys are
   OK).  To get the Spyrus driver to see the USB token, it may be necessary
   to completely remove (not just disable) other USB device drivers.  The
   Rosetta cards/USB tokens are very flaky, in general it's only possible
   to safely generate new keys and add certs to a freshly-initialised token,
   trying to add/update objects when there are already existing objects
   present tends to fail with internal errors, and deleting the existing
   objects isn't possible, the delete call succeeds but the object isn't
   touched.  In addition with most versions of the Spyrus drivers data
   isn't persisted to the token correctly, so some information (and even
   complete objects) are only visible for the duration of the session that
   created them.

   The Netscape soft-token (softokn3.dll, i.e. NSS) is a bit problematic to
   use, it requires the presence of three additional libraries (nspr4.dll,
   plc4.dll, and plds4.dll) in the same directory, and even then 
   C_Initialize() returns with CKR_ARGUMENTS_BAD.  The easiest way to 
   arrange to have all the files in the right place is to chdir to 
   "C:/Program Files/Mozilla Firefox" before loading the DLL.

   The presence of a device entry in this table doesn't necessarily mean
   that the PKCS #11 driver that it comes with functions correctly, or at
   all.  In particular the iButton driver is still in beta so it has some
   features unimplemented, and the Utimaco driver apparently has some really
   strange bugs, as well as screwing up Windows power management so that
   suspends either aren't possible any more or will crash apps.  At the
   other end of the scale the Datakey (before Rainbow got to them), Eracom
   (usually), iD2, and nCipher drivers are pretty good */

typedef struct {
	const char *name;
	const char *description;
	const char *password;
	const char *keyLabel;
	} DEVICE_CONFIG_INFO;

static const DEVICE_CONFIG_INFO pkcs11DeviceInfo[] = {
	{ "[Autodetect]", "Automatically detect device", "test", "Test user key" },
	{ "ActivCard Cryptoki Library", "ActivCard", "test", "Test user key" },
	{ "Chrystoki", "Chrysalis Luna", "test", "Test user key" },
	{ "CryptoFlex", "CryptoFlex", "ABCD1234", "012345678901234567890123456789ME" },
	{ "Cryptographic Token Interface", "AET SafeSign", "test", "Test user key" },
	{ "Cryptoki for CardMan API", "Utimaco", "test", "Test user key" },
	{ "Cryptoki for eID", "Nexus soft-token", "1234", "Private key" },
	{ "Cryptoki for eID", "Nexus signature token", "1234", "eID private nonrepudiation key" },
	{ "Cryptoki for eID", "Nexus signature token", "1234", "eID private key encipherment key" },
	{ "Cryptoki PKCS-11", "Gemplus", "test", "Test user key" },
	{ "CryptoKit Extended Version", "Eutron (via Cylink)", "12345678", "Test user key" },
	{ "Datakey Cryptoki DLL - NETSCAPE", "Datakey pre-4.1, post-4.4 driver", "test", "Test user key" },
	{ "Datakey Cryptoki DLL - Version", "Datakey 4.1-4.4 driver", "test", "Test user key" },
	{ "Eracom Cryptoki", "Eracom", "test", "Test user key" },
	{ "ERACOM Software Only", "Eracom 1.x soft-token", "test", "Test user key" },
	{ "Software Only", "Eracom 2.x soft-token", "test", "Test user key" },
	{ "eToken PKCS#11", "Aladdin eToken", "test", "Test user key" },
	{ "G&D PKCS#11 Library", "Giesecke and Devrient", "test", "Test user key" },
	{ "iButton", "Dallas iButton", "test", "Test user key" },
	{ "iD2 Cryptographic Library::iD2 Smart Card (PIN1)", "iD2 signature token::Slot 1", "1234", "Digital Signature" },
	{ "iD2 Cryptographic Library::iD2 Smart Card (PIN2)", "iD2 signature token::Slot 2", "5678", "Non Repudiation" },
	{ "ISG", "CryptoSwift HSM", "test", "Test user key" },
	{ "ISG Cryptoki API library", "CryptoSwift card", "test", "Test user key" },
	{ "Lynks/EES Token in SpyrusNATIVE", "Spyrus Lynks/EES", "test", "Test user key" },
	{ "NShield 75", "nCipher", "test", "Test user key" },
	{ "PKCS#11 Private Cryptoki", "GemSAFE", "1234", "Test user key" },
	{ "Safelayer PKCS#11", "Safelayer", "test", "Test user key" },
	{ "Schlumberger", "Schlumberger", "QWERTYUI", "Test user key" },
	{ "SignLite security module", "IBM SignLite", "test", "Test user key" },
	{ "Spyrus Rosetta", "Spyrus Rosetta", "test", "Test user key" },
	{ "Spyrus Lynks", "Spyrus Lynks", "test", "Test user key" },
	{ "Sun Metaslot", "nCipher on Solaris", "test", "Test user key" },
	{ "TCrypt", "Telesec", "123456", "Test user key" },
	{ "TrustCenter PKCS#11 Library", "GPKCS11", "12345678", "Test user key" },
	{ NULL, NULL, NULL }
	};

/* Device information for Fortezza cards */

#define FORTEZZA_ZEROISE_PIN		"ZeroizedCard"
#define FORTEZZA_SSO_DEFAULT_PIN	"Mosaic"
#define FORTEZZA_SSO_PIN			"test"
#define FORTEZZA_USER_PIN			"test"

static const DEVICE_CONFIG_INFO fortezzaDeviceInfo = \
	{ "[Autodetect]", "Automatically detect device", FORTEZZA_USER_PIN, "Test user key" };

/* Device information for CryptoAPI */

static const DEVICE_CONFIG_INFO capiDeviceInfo[] = {
	{ "[Autodetect]", "Automatically detect device", "", "Encryption key" },
	{ "Microsoft Base Cryptographic Provider v1.0::MY", "Microsoft Base Cryptographic Provider", "", "Encryption key" },
	{ NULL, NULL, NULL }
	};

/* Data used to create certs in the device */

static const CERT_DATA paaCertData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, "NZ" },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, "Honest Dave's PAA" },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, "Certification Policy Division" },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, "Dave the PAA" },

	/* Self-signed X.509v3 CA certificate */
	{ CRYPT_CERTINFO_SELFSIGNED, IS_NUMERIC, TRUE },
	{ CRYPT_CERTINFO_CA, IS_NUMERIC, TRUE },
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC,
	  CRYPT_KEYUSAGE_KEYCERTSIGN },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

static const CERT_DATA cACertData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, "NZ" },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, "Dave's Wetaburgers and CA" },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, "Certification Division" },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, "Dave Himself" },

	/* Self-signed X.509v3 CA certificate */
	{ CRYPT_CERTINFO_SELFSIGNED, IS_NUMERIC, TRUE },
	{ CRYPT_CERTINFO_CA, IS_NUMERIC, TRUE },
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC,
	  CRYPT_KEYUSAGE_KEYCERTSIGN | CRYPT_KEYUSAGE_CRLSIGN },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

static const CERT_DATA userCertData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, "NZ" },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, "Dave's Wetaburgers" },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, "Dave's key" },
	{ CRYPT_CERTINFO_EMAIL, IS_STRING, 0, TEXT( "dave@wetaburgers.com" ) },
	{ CRYPT_CERTINFO_SUBJECTNAME, IS_NUMERIC, CRYPT_UNUSED },	/* Re-select subject DN */

	/* X.509v3 general-purpose certificate */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC,
	  CRYPT_KEYUSAGE_DIGITALSIGNATURE | CRYPT_KEYUSAGE_KEYENCIPHERMENT },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

static const CERT_DATA userSigOnlyCertData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, "NZ" },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, "Dave's Wetaburgers" },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, "Dave's signing key" },

	/* X.509v3 signature-only certificate */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC, CRYPT_KEYUSAGE_DIGITALSIGNATURE },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

static const CERT_DATA userKeyAgreeCertData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, "NZ" },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, "Dave's Wetaburgers" },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, "Dave's key agreement key" },

	/* X.509v3 key agreement certificate */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC, CRYPT_KEYUSAGE_KEYAGREEMENT },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

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

/* Delete leftover keys created during testing */

static void deleteTestKey( const CRYPT_DEVICE cryptDevice,
						   const C_STR keyName, const char *keyDescription )
	{
	if( cryptDeleteKey( cryptDevice, CRYPT_KEYID_NAME, keyName ) == CRYPT_OK )
		printf( "(Deleted a %s key object, presumably a leftover from a "
				"previous run).\n", keyDescription );
	}

/* Create a key and certificate in a device */

static BOOLEAN createKey( const CRYPT_DEVICE cryptDevice,
						  const CRYPT_ALGO_TYPE cryptAlgo,
						  const char *description, const char *dumpName,
						  const CRYPT_CONTEXT signingKey )
	{
	CRYPT_CONTEXT cryptContext;
	CRYPT_CERTIFICATE cryptCert;
	BYTE certBuffer[ BUFFER_SIZE ], labelBuffer[ CRYPT_MAX_TEXTSIZE ];
	const BOOLEAN isCA = ( signingKey == CRYPT_UNUSED ) ? TRUE : FALSE;
	const CERT_DATA *certData = ( isCA ) ? cACertData : \
			( cryptAlgo == CRYPT_ALGO_RSA ) ? userCertData : \
			( cryptAlgo == CRYPT_ALGO_DSA ) ? userSigOnlyCertData : \
			userKeyAgreeCertData;
	int certificateLength, status;

	sprintf( labelBuffer, "Test %s key", description );

	/* Generate a key in the device */
	printf( "Generating a %s key in the device...", description );
	status = cryptDeviceCreateContext( cryptDevice, &cryptContext,
									   cryptAlgo );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptDeviceCreateContext() failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_LABEL, labelBuffer,
							 strlen( labelBuffer ) );
	status = cryptGenerateKey( cryptContext );
	if( cryptStatusError( status ) )
		{
		cryptDestroyContext( cryptContext );
		printf( "\ncryptGenerateKey() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	/* Create a certificate for the key */
	printf( "Generating a certificate for the key..." );
	cryptCreateCert( &cryptCert, CRYPT_UNUSED, ( isCA ) ? \
					 CRYPT_CERTTYPE_CERTIFICATE : CRYPT_CERTTYPE_CERTCHAIN );
	status = cryptSetAttribute( cryptCert,
						CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, cryptContext );
	if( cryptStatusOK( status ) && \
		!addCertFields( cryptCert, certData, __LINE__ ) )
		return( FALSE );
	if( cryptStatusOK( status ) )
		status = cryptSignCert( cryptCert, isCA ? cryptContext : signingKey );
	cryptDestroyContext( cryptContext );
	if( cryptStatusError( status ) )
		{
		cryptDestroyCert( cryptCert );
		printf( "\nCreation of certificate failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	/* Dump the resulting cert for debugging */
	if( dumpName != NULL )
		{
		status = cryptExportCert( certBuffer, BUFFER_SIZE, &certificateLength,
				isCA ? CRYPT_CERTFORMAT_CERTIFICATE : CRYPT_CERTFORMAT_CERTCHAIN,
				cryptCert );
		if( cryptStatusOK( status ) )
			debugDump( dumpName, certBuffer, certificateLength );
		}

	/* Update the key with the cert */
	printf( "Updating device with certificate..." );
	status = cryptAddPublicKey( cryptDevice, cryptCert );
	cryptDestroyCert( cryptCert );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptAddPublicKey() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	return( TRUE );
	}

/****************************************************************************
*																			*
*							Device Logon/Initialisation						*
*																			*
****************************************************************************/

/* Print information about a device and log in if necessary */

static const DEVICE_CONFIG_INFO *checkLogonDevice( const CRYPT_DEVICE cryptDevice,
												   const DEVICE_CONFIG_INFO *deviceInfo,
												   const BOOLEAN isAutoDetect,
												   const BOOLEAN willInitialise )
	{
	char tokenLabel[ CRYPT_MAX_TEXTSIZE + 1 ];
	int loggedOn, tokenLabelSize, status;

	/* Tell the user what we're talking to */
	status = cryptGetAttributeString( cryptDevice, CRYPT_DEVINFO_LABEL,
									  tokenLabel, &tokenLabelSize );
	if( cryptStatusError( status ) )
		puts( "(Device doesn't appear to have a label)." );
	else
		{
		tokenLabel[ tokenLabelSize ] = '\0';
		printf( "Device label is '%s'.\n", tokenLabel );
		}

	/* Check whether the device corresponds to a known device.  We do this
	   because some devices require specific test passwords and whatnot in
	   order to work */
	if( isAutoDetect )
		{
		int i;

		for( i = 1; pkcs11DeviceInfo[ i ].name != NULL; i++ )
			if( tokenLabelSize == \
							( int ) strlen( pkcs11DeviceInfo[ i ].name ) && \
				!memcmp( pkcs11DeviceInfo[ i ].name, tokenLabel,
						 tokenLabelSize ) )
				{
				printf( "Found a match for pre-defined device '%s',\n"
						"  using pre-set parameters.\n",
						pkcs11DeviceInfo[ i ].description );
				deviceInfo = &pkcs11DeviceInfo[ i ];
				break;
				}
			}

	/* See if we need to authenticate ourselves */
	status = cryptGetAttribute( cryptDevice, CRYPT_DEVINFO_LOGGEDIN,
								&loggedOn );
	if( cryptStatusError( status ) )
		{
		puts( "Couldn't obtain device login status." );
		return( NULL );
		}
	if( loggedOn )
		{
		/* Device may not require a login, or has already been logged in
		   via a keypad or similar mechanism */
		puts( "Device is already logged in, skipping login." );
		return( deviceInfo );
		}

	/* Try and log in */
	printf( "Logging on to the device..." );
	status = cryptSetAttributeString( cryptDevice,
							CRYPT_DEVINFO_AUTHENT_USER, deviceInfo->password,
							strlen( deviceInfo->password ) );
	if( status == CRYPT_ERROR_INITED )
		{
		/* Some devices may not require any login, in which case we're
		   done */
		puts( " device is already logged in." );
		return( deviceInfo );
		}
	if( status == CRYPT_ERROR_NOTINITED )
		{
		/* It's an uninitialised device, tell the user and exit */
		puts( " device needs to be initialised." );
		if( willInitialise )
			return( deviceInfo );
		printf( "cryptlib will not automatically initialise the device "
				"during the self-test\n  in case it contains data that "
				"needs to be preserved or requires special\n  steps to be "
				"taken before the initialisation is performed.  If you want "
				"to\n  initialise it, set TEST_INITIALISE_CARD at the top "
				"of\n  " __FILE__ " to a nonzero value.\n" );
		return( NULL );
		}
	if( cryptStatusError( status ) )
		{
		printf( "\nDevice %s failed with error code %d, line %d.\n",
				( status == CRYPT_ERROR_WRONGKEY ) ? \
				"login" : "initialisation/setup", status, __LINE__ );
		if( status == CRYPT_ERROR_WRONGKEY && willInitialise )
			{
			/* If we're going to initialise the card, being in the wrong (or
			   even totally uninitialised) state isn't an error */
			puts( "This may be because the device isn't in the user-"
				  "initialised state, in which\n  case the standard user "
				  "PIN can't be used to log on to it." );
			return( deviceInfo );
			}
		if( isAutoDetect )
			puts( "This may be because the auto-detection test uses a fixed "
				  "login value rather\n  than one specific to the device "
				  "type." );
		return( NULL );
		}
	puts( " succeeded." );
	return( deviceInfo );
	}

/* Initialise a device.  Note that when doing this with a Fortezza card,
   these operations have to be done in a more or less continuous sequence
   (i.e. without an intervening device open call) because it's not possible
   to escape from some of the states if the card is closed and reopened in
   between.  In addition the PKCS #11 interface maps some of the
   initialisation steps differently than the CI interface, so we have to
   special-case this below */

static BOOLEAN initialiseDevice( const CRYPT_DEVICE cryptDevice,
								 const CRYPT_DEVICE_TYPE deviceType,
								 const DEVICE_CONFIG_INFO *deviceInfo )
	{
	const char *defaultSSOPIN = ( deviceType == CRYPT_DEVICE_FORTEZZA ) ? \
								FORTEZZA_SSO_DEFAULT_PIN : \
								deviceInfo->password;
	const char *ssoPIN = ( deviceType == CRYPT_DEVICE_FORTEZZA ) ? \
						 FORTEZZA_SSO_PIN : deviceInfo->password;
	const char *userPIN = deviceInfo->password;
	int status;

	/* PKCS #11 doesn't distinguish between zeroisation and initialisation,
	   so we only perform the zeroise test if it's a Fortezza card */
	if( deviceType == CRYPT_DEVICE_FORTEZZA )
		{
		printf( "Zeroising device..." );
		status = cryptSetAttributeString( cryptDevice,
						CRYPT_DEVINFO_ZEROISE, FORTEZZA_ZEROISE_PIN,
						strlen( FORTEZZA_ZEROISE_PIN ) );
		if( cryptStatusError( status ) )
			{
			printf( "\nZeroise failed with error code %d, line %d.\n",
					status, __LINE__ );
			return( FALSE );
			}
		puts( " succeeded." );
		}

	/* Initialise the device and set the SO PIN.   */
	printf( "Initialising device..." );
	status = cryptSetAttributeString( cryptDevice, CRYPT_DEVINFO_INITIALISE,
									  defaultSSOPIN, strlen( defaultSSOPIN ) );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't initialise device, status = %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );
	printf( "Setting SO PIN to '%s'...", ssoPIN );
	status = cryptSetAttributeString( cryptDevice,
									  CRYPT_DEVINFO_SET_AUTHENT_SUPERVISOR,
									  ssoPIN, strlen( ssoPIN ) );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't set SO PIN, status = %d, line %d.\n", status,
				__LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	/* If it's a Fortezza card, create a CA root key and install its cert.
	   We have to do it at this point because the operation is only allowed
	   in the SSO initialised state.  In addition we can't use the card for
	   this operation because cert slot 0 is a data-only slot (that is, it
	   can't correspond to a key held on the card), so we create a dummy
	   external cert and use that */
	if( deviceType == CRYPT_DEVICE_FORTEZZA )
		{
		CRYPT_CERTIFICATE cryptCert;
		CRYPT_CONTEXT signContext;

		printf( "Loading PAA certificate..." );
		if( !loadDSAContexts( CRYPT_UNUSED, &signContext, NULL ) )
			return( FALSE );
		cryptCreateCert( &cryptCert, CRYPT_UNUSED,
						 CRYPT_CERTTYPE_CERTIFICATE );
		status = cryptSetAttribute( cryptCert,
						CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, signContext );
		if( cryptStatusOK( status ) && \
			!addCertFields( cryptCert, paaCertData, __LINE__ ) )
			return( FALSE );
		if( cryptStatusOK( status ) )
			status = cryptSignCert( cryptCert, signContext );
		cryptDestroyContext( signContext );
		if( cryptStatusError( status ) )
			{
			cryptDestroyCert( cryptCert );
			printf( "\nCreation of certificate failed with error code %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		status = cryptAddPublicKey( cryptDevice, cryptCert );
		cryptDestroyCert( cryptCert );
		if( cryptStatusError( status ) )
			{
			printf( "\ncryptAddPublicKey() failed with error code %d, line "
					"%d.\n", status, __LINE__ );
			return( FALSE );
			}
		puts( " succeeded." );
		}

	/* Set the user PIN and log on as the user */
	printf( "Setting user PIN to '%s'...", userPIN );
	status = cryptSetAttributeString( cryptDevice,
									  CRYPT_DEVINFO_SET_AUTHENT_USER,
									  userPIN, strlen( userPIN ) );
	if( cryptStatusOK( status ) )
		{
		int loggedOn;

		/* Some devices automatically log the user in when they set the user
		   password, so we check to see if it's necessary to log in before we
		   actually do it */
		status = cryptGetAttribute( cryptDevice, CRYPT_DEVINFO_LOGGEDIN,
									&loggedOn );
		if( cryptStatusError( status ) )
			{
			puts( "Couldn't obtain device login status." );
			return( FALSE );
			}
		if( !loggedOn )
			status = cryptSetAttributeString( cryptDevice,
											  CRYPT_DEVINFO_AUTHENT_USER,
											  userPIN, strlen( userPIN ) );
		}
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't set user PIN/log on as user, status = %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	return( TRUE );
	}

/****************************************************************************
*																			*
*									Device Tests							*
*																			*
****************************************************************************/

/* Test the general capabilities of a device */

static BOOLEAN testDeviceCapabilities( const CRYPT_DEVICE cryptDevice,
									   const char *deviceName,
									   const BOOLEAN isWriteProtected )
	{
	CRYPT_ALGO_TYPE cryptAlgo;
	int testCount = 0, failCount = 0;

	printf( "Checking %s capabilities...\n", deviceName );
	for( cryptAlgo = CRYPT_ALGO_FIRST_CONVENTIONAL;
		 cryptAlgo <= CRYPT_ALGO_LAST; cryptAlgo++ )
		if( cryptStatusOK( cryptDeviceQueryCapability( cryptDevice,
													   cryptAlgo, NULL ) ) )
			{
			testCount++;
			if( !testLowlevel( cryptDevice, cryptAlgo, isWriteProtected ) )
				/* The test failed, we don't exit at this point but only
				   remember that there was a problem since we want to test
				   every possible algorithm */
				failCount++;
			}

	if( isWriteProtected )
		puts( "No tests were performed since the device is write-protected." );
	else
		if( failCount )
			printf( "%d of %d test%s failed.\n", failCount, testCount,
					( testCount > 1 ) ? "s" : "" );
		else
			puts( "Device capabilities test succeeded." );

	return( ( failCount == testCount ) ? FALSE : TRUE );
	}

/* Test the high-level functionality provided by a device */

static BOOLEAN testPersistentObject( const CRYPT_DEVICE cryptDevice )
	{
	CRYPT_ALGO_TYPE cryptAlgo = CRYPT_ALGO_HMAC_SHA1;
	CRYPT_CONTEXT cryptContext;
	int status;

	printf( "Loading a persistent symmetric key into the device..." );

	/* Find an encryption algorithm that we can use and create a context in
	   the device */
	status = cryptDeviceQueryCapability( cryptDevice, cryptAlgo, NULL );
	if( cryptStatusError( status ) )
		{
		cryptAlgo = CRYPT_ALGO_DES;
		status = cryptDeviceQueryCapability( cryptDevice, cryptAlgo, NULL );
		}
	if( cryptStatusOK( status ) )
		status = cryptDeviceCreateContext( cryptDevice, &cryptContext, 
										   cryptAlgo );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't create conventional-encryption context in "
				"device, status = %d, line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Make it a persistent object and load a key */
	cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_LABEL, 
							 SYMMETRIC_KEY_LABEL, 
							 strlen( SYMMETRIC_KEY_LABEL ) );
	status = cryptSetAttribute( cryptContext, CRYPT_CTXINFO_PERSISTENT, 
								TRUE );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't make device context persistent, status = %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	status = cryptGenerateKey( cryptContext );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't load key into persistent context, status = %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	/* Destroy the context.  Since it's persistent, it'll still be present 
	   in the device */
	cryptDestroyContext( cryptContext );

	/* Recreate the object from the device */
	printf( "Reading back symmetric key..." );
	status = cryptGetKey( cryptDevice, &cryptContext, CRYPT_KEYID_NAME,
						  SYMMETRIC_KEY_LABEL, NULL );
	if( cryptStatusError( status ) )
		{
		printf( "\nRead of symmetric key failed, status = %d, line %d.\n", 
				status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	/* Re-destroy it and make sure that it's still there afterwards.  This 
	   tests the persistence of read-back objects vs. created objects */
	cryptDestroyContext( cryptContext );
	printf( "Re-reading back symmetric key..." );
	status = cryptGetKey( cryptDevice, &cryptContext, CRYPT_KEYID_NAME,
						  SYMMETRIC_KEY_LABEL, NULL );
	if( cryptStatusError( status ) )
		{
		printf( "\nRe-read of symmetric key failed, status = %d, line %d.\n", 
				status, __LINE__ );
		return( FALSE );
		}
	puts( " succeeded." );

	/* Perform an encrypt/decrypt test with the recreated object */
	printf( "Performing encryption test with recovered key..." );
	status = testCrypt( cryptContext, cryptContext, NULL, TRUE, FALSE );
	if( cryptStatusError( status ) )
		return( FALSE );
	puts( " succeeded." );

	/* Clean up */		
	cryptDestroyContext( cryptContext );
	return( TRUE );
	}

static BOOLEAN testDeviceHighlevel( const CRYPT_DEVICE cryptDevice,
									const CRYPT_DEVICE_TYPE deviceType,
									const char *keyLabel,
									const char *password,
									const BOOLEAN isWriteProtected )
	{
	CRYPT_CONTEXT pubKeyContext, privKeyContext, sigKeyContext;
	int status;

	if( !isWriteProtected && TEST_KEYGEN )
		{
		const CRYPT_ALGO_TYPE cryptAlgo = \
						( deviceType == CRYPT_DEVICE_FORTEZZA ) ? \
						CRYPT_ALGO_DSA : CRYPT_ALGO_RSA;

		if( deviceType != CRYPT_DEVICE_CRYPTOAPI )
			{
			/* Create a CA key in the device */
			if( !createKey( cryptDevice, cryptAlgo, "CA",
							( deviceType == CRYPT_DEVICE_PKCS11 ) ? \
							"dp_cacert" : "df_cacert", CRYPT_UNUSED ) )
				return( FALSE );

			/* Read back the CA key for use in generating end entity certs */
			status = cryptGetPrivateKey( cryptDevice, &sigKeyContext,
										 CRYPT_KEYID_NAME,
										 TEXT( "Test CA key" ), NULL );
			}
		else
			/* CryptoAPI can only store one private key per provider so we
			   can't have both a CA key and user key in the same "device".
			   Because of this we have to use the fixed CA key to issue the
			   cert */
			status = getPrivateKey( &sigKeyContext, CA_PRIVKEY_FILE,
									CA_PRIVKEY_LABEL, TEST_PRIVKEY_PASSWORD );
		if( cryptStatusError( status ) )
			{
			printf( "\nRead of CA key failed with error code %d, line %d.\n",
					status, __LINE__ );
			return( FALSE );
			}

		/* Create end-entity certificate(s) for keys using the previously-
		   generated CA key.  If it's a Fortezza card and we're using KEA we
		   have to generate two sets of keys/certs, one for signing and one
		   for encryption */
		status = createKey( cryptDevice, cryptAlgo, "user",
							( deviceType == CRYPT_DEVICE_PKCS11 ) ? \
							"dp_usrcert" : "df_usrcert", sigKeyContext );
#ifdef USE_KEA
		if( status && deviceType == CRYPT_DEVICE_FORTEZZA )
			status = createKey( cryptDevice, CRYPT_ALGO_KEA, "KEA",
								"df_keacert", sigKeyContext );
#endif /* USE_KEA */
		cryptDestroyContext( sigKeyContext );
		if( !status )
			return( FALSE );
		}
	else
		puts( "Skipping key generation test, this assumes that the device "
			  "contains pre-\n  existing keys." );

	/* See whether there are any existing keys or certs.  Some tokens have
	   these built in and don't allow anything new to be created, after this
	   point the handling is somewhat special-case but we can at least report
	   their presence.  Although generally we can re-use a private key
	   context for both public and private operations, some devices or drivers
	   (and by logical extension thereof the cryptlib kernel) don't allow
	   public-key ops with private keys so we have to eplicitly handle public
	   and private keys.  This gets somewhat messy because some devices don't
	   have public keys but allow public-key ops with their private keys,
	   while others separate public and private keys and don't allow the
	   private key to do public-key ops */
	status = cryptGetPublicKey( cryptDevice, &pubKeyContext,
								CRYPT_KEYID_NAME, keyLabel );
	if( cryptStatusOK( status ) )
		{
		int value;

		puts( "Found a public key in the device, details follow..." );
		printCertChainInfo( pubKeyContext );
		if( cryptStatusOK( \
				cryptGetAttribute( pubKeyContext,
								   CRYPT_CERTINFO_SELFSIGNED, &value ) ) && \
			value )
			{
			/* It's a self-signed cert/cert chain, make sure that it's
			   valid.  Because it's probably not trusted, we make it
			   temporarily implicitly trusted in order for the sig.check to
			   succeed */
			status = cryptGetAttribute( pubKeyContext,
								CRYPT_CERTINFO_TRUSTED_IMPLICIT, &value );
			if( cryptStatusOK( status ) )
				status = cryptSetAttribute( pubKeyContext,
									CRYPT_CERTINFO_TRUSTED_IMPLICIT, 1 );
			if( cryptStatusOK( status ) )
				status = cryptCheckCert( pubKeyContext, CRYPT_UNUSED );
			if( cryptStatusError( status ) )
				{
				printf( "Signature on certificate is invalid, status %d, "
						"line %d.\n", status, __LINE__ );
				return( FALSE );
				}
			cryptSetAttribute( pubKeyContext,
							   CRYPT_CERTINFO_TRUSTED_IMPLICIT, value );
			}
		}
	else
		{
		puts( "Error: Couldn't locate public key in device." );
		pubKeyContext = CRYPT_UNUSED;
		}
	status = cryptGetPrivateKey( cryptDevice, &privKeyContext,
								 CRYPT_KEYID_NAME, keyLabel, NULL );
	if( cryptStatusOK( status ) )
		{
		puts( "Found a private key in the device, details follow..." );
		printCertChainInfo( privKeyContext );
		if( pubKeyContext == CRYPT_UNUSED )
			{
			/* No explicit public key found, try using the private key for
			   both key types */
			puts( "No public key found, attempting to continue using the "
				  "private key as both a\n  public and a private key." );
			pubKeyContext = privKeyContext;
			}
		}
	else
		{
		puts( "Error: Couldn't locate private key in device." );
		privKeyContext = CRYPT_UNUSED;
		}
	sigKeyContext = privKeyContext;
	if( deviceType == CRYPT_DEVICE_FORTEZZA )
		{
		cryptDestroyContext( pubKeyContext );	/* pubK is sig.only */
		status = cryptGetPrivateKey( cryptDevice, &privKeyContext,
									 CRYPT_KEYID_NAME, "Test KEA key", NULL );
		if( cryptStatusOK( status ) )
			{
			puts( "Found a key agreement key in the device, details follow..." );
			printCertChainInfo( privKeyContext );
			pubKeyContext = privKeyContext;		/* Fortezza allows both uses */
			}
		else
			{
			pubKeyContext = CRYPT_UNUSED;
			privKeyContext = CRYPT_UNUSED;
			}
		}

	/* If we got something, try some simple operations with it */
	if( pubKeyContext != CRYPT_UNUSED )
		{
		char emailAddress[ CRYPT_MAX_TEXTSIZE + 1 ];
		int length;

		if( !testCMSEnvelopePKCCryptEx( pubKeyContext, cryptDevice, 
										password, NULL ) )
			return( FALSE );
		cryptSetAttribute( pubKeyContext, CRYPT_ATTRIBUTE_CURRENT, 
						   CRYPT_CERTINFO_SUBJECTALTNAME );
		status = cryptGetAttributeString( pubKeyContext, CRYPT_CERTINFO_EMAIL,
										  emailAddress, &length );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't read recipient address from certificate, "
					"status %d, line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		emailAddress[ length ] = '\0';
		if( !testCMSEnvelopePKCCryptEx( CRYPT_UNUSED, cryptDevice, 
										password, emailAddress ) )
			return( FALSE );
		}
	else
		puts( "Public-key enveloping tests skipped because no key was "
			  "available.\n" );
	if( sigKeyContext != CRYPT_UNUSED )
		{
		if( !testCMSEnvelopeSignEx( sigKeyContext ) )
			return( FALSE );
		}
	else
		puts( "Signed enveloping tests skipped because no key was "
			  "available." );

	/* Test persistent (non-public-key) object creation */
	if( !isWriteProtected && TEST_KEYGEN )
		{
		if( !testPersistentObject( cryptDevice ) )
			return( FALSE );
		}

	/* Clean up */
	if( pubKeyContext == CRYPT_UNUSED && sigKeyContext == CRYPT_UNUSED )
		return( FALSE );
	if( privKeyContext != CRYPT_UNUSED )
		cryptDestroyContext( privKeyContext );
	if( sigKeyContext != CRYPT_UNUSED && privKeyContext != sigKeyContext )
		cryptDestroyContext( sigKeyContext );
	if( pubKeyContext != CRYPT_UNUSED && pubKeyContext != privKeyContext )
		cryptDestroyContext( pubKeyContext );
	return( TRUE );
	}

/* General device test routine */

static int testCryptoDevice( const CRYPT_DEVICE_TYPE deviceType,
							 const char *deviceName,
							 const DEVICE_CONFIG_INFO *deviceInfo )
	{
	CRYPT_DEVICE cryptDevice;
	BOOLEAN isWriteProtected = FALSE, isAutoDetect = FALSE;
	BOOLEAN testResult = FALSE, partialSuccess = FALSE;
	int status;

	/* Open a connection to the device */
	if( deviceType == CRYPT_DEVICE_PKCS11 || \
		deviceType == CRYPT_DEVICE_CRYPTOAPI )
		{
		if( !memcmp( deviceInfo->name, "[A", 2 ) )
			{
			printf( "\nTesting %s with autodetection...\n", deviceName );
			isAutoDetect = TRUE;
			}
		else
			printf( "\nTesting %s %s...\n", deviceInfo->name, deviceName );
		status = cryptDeviceOpen( &cryptDevice, CRYPT_UNUSED, deviceType,
								  deviceInfo->name );
		}
	else
		{
		printf( "\nTesting %s...\n", deviceName );
		status = cryptDeviceOpen( &cryptDevice, CRYPT_UNUSED, deviceType,
								  deviceName );
		}
	if( status == CRYPT_ERROR_PARAM2 )
		{
		puts( "Support for this device type isn't enabled in this build of "
			  "cryptlib." );
		return( CRYPT_ERROR_NOTAVAIL );	/* Device access not available */
		}
	if( cryptStatusError( status ) )
		{
		if( status == CRYPT_ERROR_PARAM3 || status == CRYPT_ERROR_NOTFOUND )
			puts( "Crypto device not detected, skipping test." );
		else
			printf( "cryptDeviceOpen() failed with error code %d, line %d.\n",
					status, __LINE__ );
		return( FALSE );
		}

	/* If it's one of the smarter classes of device, authenticate ourselves to
	   the device, which is usually required in order to allow it to be used
	   fully */
	if( deviceType == CRYPT_DEVICE_PKCS11 || deviceType == CRYPT_DEVICE_FORTEZZA )
		{
		deviceInfo = checkLogonDevice( cryptDevice, deviceInfo, isAutoDetect, 
									   TEST_INITIALISE_CARD );
		if( deviceInfo == NULL )
			{
			cryptDeviceClose( cryptDevice );
			return( FALSE );
			}
		}

	/* Write-protected devices won't allow contexts to be created in them,
	   before we try the general device capabilities test we make sure that
	   we can actually perform the operation */
	if( deviceType == CRYPT_DEVICE_PKCS11 )
		{
		CRYPT_CONTEXT cryptContext;

		/* Try and create a DES object.  The following check for read-only
		   devices always works because the device object ACL is applied at
		   a much higher level than any device capability checking, the
		   device will never even see the create object message if it's
		   write-protected so all we have to do is make sure that whatever
		   we create is ephemeral */
		status = cryptDeviceCreateContext( cryptDevice, &cryptContext,
										   CRYPT_ALGO_DES );
		if( cryptStatusOK( status ) )
			cryptDestroyContext( cryptContext );
		if( status == CRYPT_ERROR_PERMISSION )
			isWriteProtected = TRUE;
		}

	/* To force the code not to try to create keys and certs in a writeable
	   device, uncomment the following line of code.  This requires that keys/
	   certs of the required type are already present in the device */
/*	KLUDGE_WARN( "write-protect status" );
	isWriteProtected = TRUE; */
	if( !isWriteProtected && TEST_KEYGEN )
		{
		/* If it's a device that we can initialise, go through a full
		   initialisation */
		if( deviceType != CRYPT_DEVICE_CRYPTOAPI && TEST_INITIALISE_CARD )
			{
			status = initialiseDevice( cryptDevice, deviceType,
									   deviceInfo );
			if( status == FALSE )
				{
				cryptDeviceClose( cryptDevice );
				return( FALSE );
				}
			}
		else
			{
			/* There may be test keys lying around from an earlier run, in
			   which case we try to delete them to make sure they won't
			   interfere with the current one */
			deleteTestKey( cryptDevice, "Test CA key", "CA" );
			deleteTestKey( cryptDevice, deviceInfo->keyLabel, "user" );
			if( deviceType == CRYPT_DEVICE_PKCS11 )
				{
				deleteTestKey( cryptDevice, RSA_PUBKEY_LABEL, "RSA public" );
				deleteTestKey( cryptDevice, RSA_PRIVKEY_LABEL, "RSA private" );
				deleteTestKey( cryptDevice, DSA_PUBKEY_LABEL, "DSA public" );
				deleteTestKey( cryptDevice, DSA_PRIVKEY_LABEL, "DSA private" );
				deleteTestKey( cryptDevice, SYMMETRIC_KEY_LABEL, "symmetric" );
				}
			if( deviceType == CRYPT_DEVICE_FORTEZZA )
				deleteTestKey( cryptDevice, "Test KEA key", "KEA" );
			if( deviceType == CRYPT_DEVICE_CRYPTOAPI )
				{
				deleteTestKey( cryptDevice, "Encryption key", "RSA private" );
				deleteTestKey( cryptDevice, "Signature key", "secondary RSA private" );
				}
			}
		}

#ifdef TEST_DH
	return( testLowlevel( cryptDevice, CRYPT_ALGO_DH, FALSE ) );
#endif /* TEST_DH */

	/* Report what the device can do.  This is intended mostly for simple
	   crypto accelerators and may fail with for devices that work only
	   with the higher-level functions centered around certificates,
	   signatures,and key wrapping, so we skip the tests for devices that
	   allow only high-level access */
#if TEST_ALGORITHMS
	if( deviceType != CRYPT_DEVICE_FORTEZZA )
		testResult = testDeviceCapabilities( cryptDevice, deviceName,
											 isWriteProtected );
#else
	puts( "Skipping device algorithm tests." );
#endif /* TEST_ALGORITHMS */

	/* If it's a smart device, try various device-specific operations */
	if( deviceType == CRYPT_DEVICE_FORTEZZA || \
		deviceType == CRYPT_DEVICE_PKCS11 || \
		deviceType == CRYPT_DEVICE_CRYPTOAPI )
		partialSuccess = testDeviceHighlevel( cryptDevice, deviceType,
								deviceInfo->keyLabel, deviceInfo->password,
								isWriteProtected );

	/* Clean up */
	status = cryptDeviceClose( cryptDevice );
	if( cryptStatusError( status ) )
		{
		printf( "cryptDeviceClose() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	if( !testResult && !partialSuccess )
		return( FALSE );
	if( testResult && partialSuccess )
		printf( "%s tests succeeded.\n\n", deviceName );
	else
		printf( "Some %s tests succeeded.\n\n", deviceName );
	return( TRUE );
	}

int testDevices( void )
	{
	int i, status;

	/* Test Fortezza devices */
#if 0
	status = testCryptoDevice( CRYPT_DEVICE_FORTEZZA, "Fortezza card",
							   &fortezzaDeviceInfo );
	if( cryptStatusError( status ) && status != CRYPT_ERROR_NOTAVAIL )
		return( status );
#endif /* 0 */

	/* Test PKCS #11 devices */
#if 1
	for( i = 0; pkcs11DeviceInfo[ i ].name != NULL; i++ )
		{
		status = testCryptoDevice( CRYPT_DEVICE_PKCS11, "PKCS #11 crypto token",
								   &pkcs11DeviceInfo[ i ] );
		if( cryptStatusError( status ) && \
			!( status == CRYPT_ERROR_NOTAVAIL || \
			   ( i == 0 && status == CRYPT_ERROR_WRONGKEY ) ) )
			return( status );
		}
#endif /* 0 */

#if 0	/* For test purposes only to check CAPI data, don't use the CAPI code */
#ifdef __WINDOWS__
	for( i = 0; capiDeviceInfo[ i ].name != NULL; i++ )
		{
		status = testCryptoDevice( CRYPT_DEVICE_CRYPTOAPI, "Microsoft CryptoAPI",
								   &capiDeviceInfo[ i ] );
		if( cryptStatusError( status ) && \
			!( status == CRYPT_ERROR_NOTAVAIL || \
			   ( i == 0 && status == CRYPT_ERROR_WRONGKEY ) ) )
			return( status );
		}
#endif /* __WINDOWS__ */
#endif /* 0 */
	putchar( '\n' );
	return( TRUE );
	}

/****************************************************************************
*																			*
*						Full Device Initialisation Test						*
*																			*
****************************************************************************/

/* The following code takes two unitialised devices and turns one into a
   fully initialised CA device, which then runs a PnP PKI session that turns
   the other into a fully initialised user device.

   The following configuration options can be used to change the beaviour of
   the self-test, for example to run it on the local machine in loopback mode
   vs. running on two distinct machines.  Defining an IP address (or host
   name) for SERVER_MACHINE_ADDRESS will have the client connect to that
   address instead of running a local loopback test */

#if 0
  #define SERVER_MACHINE_ADDRESS	"161.5.99.22"
  #define SERVER_MACHINE_PORT		4080
  #define CLIENT_DEVICE_TYPE		CRYPT_DEVICE_FORTEZZA
  #define SERVER_DEVICE_TYPE		CRYPT_DEVICE_FORTEZZA
  #define CLIENT_ID					"25CHS-UDQBU-BPASM"
  #define CLIENT_AUTHENTICATOR		"5ZCJ8-34A5C-YSXRD-C9EME"
  #define CLIENT_TOKEN_SLOT			CRYPT_USE_DEFAULT
  #define NET_TIMEOUT				300
#else
  #define SERVER_MACHINE_ADDRESS	"localhost"
  #define SERVER_MACHINE_PORT		80
  #define CLIENT_DEVICE_TYPE		CRYPT_DEVICE_FORTEZZA
  #define SERVER_DEVICE_TYPE		CRYPT_DEVICE_FORTEZZA
  #define CLIENT_TOKEN_SLOT			1
  #define NET_TIMEOUT				CRYPT_USE_DEFAULT
#endif /* Loopback vs. general test */

/* Default PIN values */

#if CLIENT_DEVICE_TYPE == CRYPT_DEVICE_FORTEZZA
  #define DEFAULT_SSO_PIN	FORTEZZA_SSO_DEFAULT_PIN
#else
  #define DEFAULT_SSO_PIN	"0000"
#endif /* Fortezza vs. PKCS #11 default SSO PINs */
#define SSO_PIN			"0000"
#define USER_PIN		"0000"

/* Set up a client/server to connect locally (usually) or to a custom
   address and port if we're running on distinct machines.  For the client
   this simply tells it where to connect, for the server in loopback mode
   this binds it to the local address so that we don't inadvertently open
   up outside ports */

static BOOLEAN setConnectInfo( const CRYPT_SESSION cryptSession,
							   const char *address, const int port,
							   const int timeout )
	{
	int status;

	status = cryptSetAttributeString( cryptSession,
									  CRYPT_SESSINFO_SERVER_NAME,
									  address,  strlen( address ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_SERVER_PORT, port );
	if( cryptStatusOK( status ) && timeout != CRYPT_USE_DEFAULT )
		{
		cryptSetAttribute( cryptSession, CRYPT_OPTION_NET_READTIMEOUT,
						   NET_TIMEOUT );
		status = cryptSetAttribute( cryptSession,
									CRYPT_OPTION_NET_WRITETIMEOUT,
									NET_TIMEOUT );
		}
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttribute/AttributeString() failed with error code "
				"%d, line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	return( TRUE );
	}

/* Run a persistent server session, recycling the connection if the client
   kept the link open */

static int activatePersistentServerSession( const CRYPT_SESSION cryptSession )
	{
	BOOLEAN connectionActive = FALSE;
	int status;

	puts( "Running (persistent) CMP server session." );
	do
		{
		/* Activate the connection */
		status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE,
									TRUE );
		if( status == CRYPT_ERROR_READ && connectionActive )
			/* The other side closed the connection after a previous
			   successful transaction, this isn't an error */
			return( CRYPT_OK );

		/* Print connection info */
		if( !connectionActive )
			{
			time_t theTime;
			char serverName[ 128 ];
			int serverNameLength, serverPort, nameStatus;

			time( &theTime );
			nameStatus = cryptGetAttributeString( cryptSession,
											CRYPT_SESSINFO_CLIENT_NAME,
											serverName, &serverNameLength );
			if( cryptStatusOK( nameStatus ) )
				{
				serverName[ serverNameLength ] = '\0';
				cryptGetAttribute( cryptSession, CRYPT_SESSINFO_CLIENT_PORT,
								   &serverPort );
				printf( "Connect attempt from %s, port %d, on %s",
						serverName, serverPort, ctime( &theTime ) );
				}
			}
		if( cryptStatusOK( status ) )
			{
			char userID[ CRYPT_MAX_TEXTSIZE ];
			int userIDsize, requestType;

			status = cryptGetAttribute( cryptSession,
										CRYPT_SESSINFO_CMP_REQUESTTYPE,
										&requestType );
			if( cryptStatusOK( status ) )
				status = cryptGetAttributeString( cryptSession,
											CRYPT_SESSINFO_USERNAME,
											userID, &userIDsize );
			if( cryptStatusError( status ) )
				printf( "cryptGetAttribute/AttributeString() failed with "
						"error code %d, line %d.\n", status, __LINE__ );
			else
				{
				userID[ userIDsize ] = '\0';
				printf( "CA operation type was %d, user '%s'.\n",
						requestType, userID );
				}
			}

		/* Check whether the connection is still active.  If it is, we
		   recycle the session so that we can process another request */
		cryptGetAttribute( cryptSession, CRYPT_SESSINFO_CONNECTIONACTIVE,
						   &connectionActive );
		}
	while( cryptStatusOK( status ) && connectionActive );
	puts( "CMP server session completed." );

	return( status );
	}

/* Create a CA cert in a device */

static const CERT_DATA rootCACertData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, "AT" },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, "IAEA" },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, "SGTIE" },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, "IAEA CA root" },

	/* Self-signed X.509v3 CA certificate */
	{ CRYPT_CERTINFO_SELFSIGNED, IS_NUMERIC, TRUE },
	{ CRYPT_CERTINFO_CA, IS_NUMERIC, TRUE },
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC,
	  CRYPT_KEYUSAGE_KEYCERTSIGN | CRYPT_KEYUSAGE_CRLSIGN },

	/* Access information */
	{ CRYPT_ATTRIBUTE_CURRENT, IS_NUMERIC, CRYPT_CERTINFO_AUTHORITYINFO_RTCS },
	{ CRYPT_CERTINFO_UNIFORMRESOURCEIDENTIFIER, IS_STRING, 0, "http://localhost" },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

static int createCACert( const CRYPT_DEVICE cryptDevice )
	{
	CRYPT_CONTEXT cryptContext;
	CRYPT_CERTIFICATE cryptCert;
	int status;

	/* Generate a key in the device */
	printf( "Generating a CA key in the device..." );
	status = cryptDeviceCreateContext( cryptDevice, &cryptContext,
						( SERVER_DEVICE_TYPE == CRYPT_DEVICE_FORTEZZA ) ? \
							CRYPT_ALGO_DSA : CRYPT_ALGO_RSA );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptDeviceCreateContext() failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_LABEL,
									  "CA key", strlen( "CA key" ) );
	if( cryptStatusOK( status ) )
		status = cryptGenerateKey( cryptContext );
	if( cryptStatusError( status ) )
		{
		cryptDestroyContext( cryptContext );
		printf( "\ncryptGenerateKey() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );

	/* Create a certificate for the key */
	printf( "Generating a CA certificate for the key..." );
	cryptCreateCert( &cryptCert, CRYPT_UNUSED, CRYPT_CERTTYPE_CERTIFICATE );
	status = cryptSetAttribute( cryptCert,
						CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, cryptContext );
	if( cryptStatusOK( status ) && \
		!addCertFields( cryptCert, rootCACertData, __LINE__ ) )
		return( FALSE );
	if( cryptStatusOK( status ) )
		status = cryptSignCert( cryptCert, cryptContext );
	cryptDestroyContext( cryptContext );
	if( cryptStatusError( status ) )
		{
		cryptDestroyCert( cryptCert );
		printf( "\nCreation of certificate failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );

	/* Update the key with the CA cert */
	printf( "Updating device with certificate..." );
	status = cryptAddPublicKey( cryptDevice, cryptCert );
	cryptDestroyCert( cryptCert );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptAddPublicKey() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );

	return( TRUE );
	}

/* Connect to a device */

static int connectDevice( CRYPT_DEVICE *cryptDevice, CRYPT_DEVICE_TYPE type,
						  const int slotNo )
	{
	char buffer[ 128 ];
	int status;

	/* Clear return value */
	*cryptDevice = -1;

	/* Connect to the device */
	if( slotNo == CRYPT_USE_DEFAULT )
		{
		printf( "Connecting to crypto device in default slot..." );
		strcpy( buffer, "[Autodetect]" );
		}
	else
		{
		printf( "Connecting to crypto device in slot %d...", slotNo );
		sprintf( buffer, "[Autodetect]::%d", slotNo );
		}
	status = cryptDeviceOpen( cryptDevice, CRYPT_UNUSED, type, buffer );
	if( cryptStatusError( status ) )
		{
		if( status == CRYPT_ERROR_PARAM3 || status == CRYPT_ERROR_NOTFOUND )
			puts( "\nDevice not detected, skipping test." );
		else
			printf( "\ncryptDeviceOpen() failed with error code %d, line "
					"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );

	return( TRUE );
	}

/* Log on to a device */

static int logonDevice( const CRYPT_DEVICE cryptDevice, const char *userPIN )
	{
	char tokenLabel[ CRYPT_MAX_TEXTSIZE + 1 ];
	int loggedOn, tokenLabelSize, status;

	/* Tell the user what we're talking to */
	status = cryptGetAttributeString( cryptDevice, CRYPT_DEVINFO_LABEL,
									  tokenLabel, &tokenLabelSize );
	if( cryptStatusError( status ) )
		puts( "(Device doesn't appear to have a label)." );
	else
		{
		tokenLabel[ tokenLabelSize ] = '\0';
		printf( "Device label is '%s'.\n", tokenLabel );
		}

	/* See if we need to authenticate ourselves */
	status = cryptGetAttribute( cryptDevice, CRYPT_DEVINFO_LOGGEDIN,
								&loggedOn );
	if( cryptStatusError( status ) )
		{
		puts( "Couldn't obtain device login status." );
		return( FALSE );
		}
	if( loggedOn )
		{
		/* Device may not require a login, or has already been logged in
		   via a keypad or similar mechanism */
		puts( "Device is already logged in, skipping login." );
		return( TRUE );
		}

	/* Try and log in */
	printf( "Logging on to the device..." );
	status = cryptSetAttributeString( cryptDevice,
								CRYPT_DEVINFO_AUTHENT_USER, userPIN,
								strlen( userPIN ) );
	if( status == CRYPT_ERROR_INITED )
		{
		/* Some devices may not require any login, in which case we're
		   done */
		puts( " device is already logged in." );
		return( TRUE );
		}
	if( status == CRYPT_ERROR_NOTINITED )
		{
		/* It's an uninitialised device, tell the user and exit */
		puts( " device needs to be initialised." );
		return( FALSE );
		}
	if( cryptStatusError( status ) )
		{
		printf( "\nDevice login failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );
	return( TRUE );
	}

/* Initialise a device */

static int initDevice( const CRYPT_DEVICE cryptDevice,
					   const char *defaultSSOPIN, const char *ssoPIN,
					   const char *userPIN )
	{
	int status;

	/* PKCS #11 doesn't distinguish between zeroisation and initialisation,
	   so we need to zeroise the device if it's a Fortezza card */
	if( CLIENT_DEVICE_TYPE == CRYPT_DEVICE_FORTEZZA )
		{
		printf( "Zeroising device..." );
		status = cryptSetAttributeString( cryptDevice,
						CRYPT_DEVINFO_ZEROISE, FORTEZZA_ZEROISE_PIN,
						strlen( FORTEZZA_ZEROISE_PIN ) );
		if( cryptStatusError( status ) )
			{
			printf( "\nCouldn't zeroise device, status = %d, line %d.\n",
					status, __LINE__ );
			return( FALSE );
			}
		puts( " done." );
		}

	/* Initialise the device and set the SO PIN */
	printf( "Initialising device with default SO PIN '%s'...",
			defaultSSOPIN );
	status = cryptSetAttributeString( cryptDevice, CRYPT_DEVINFO_INITIALISE,
									  defaultSSOPIN, strlen( defaultSSOPIN ) );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't initialise device, status = %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );
	printf( "Setting SO PIN to '%s'...", ssoPIN );
	status = cryptSetAttributeString( cryptDevice,
									  CRYPT_DEVINFO_SET_AUTHENT_SUPERVISOR,
									  ssoPIN, strlen( ssoPIN ) );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't set SO PIN, status = %d, line %d.\n", status,
				__LINE__ );
		return( FALSE );
		}
	puts( " done." );

	/* If it's a Fortezza card, create a dummy PAA key and install its cert.
	   We have to do it at this point because the operation is only allowed
	   in the SSO initialised state.  In addition we can't use the card for
	   this operation because cert slot 0 is a data-only slot (that is, it
	   can't correspond to a key held on the card), so we create a dummy
	   external cert and use that */
	if( CLIENT_DEVICE_TYPE == CRYPT_DEVICE_FORTEZZA )
		{
		CRYPT_CERTIFICATE cryptCert;
		CRYPT_CONTEXT signContext;

		printf( "Loading PAA certificate..." );
		if( !loadDSAContexts( CRYPT_UNUSED, &signContext, NULL ) )
			return( FALSE );
		cryptCreateCert( &cryptCert, CRYPT_UNUSED,
						 CRYPT_CERTTYPE_CERTIFICATE );
		status = cryptSetAttribute( cryptCert,
						CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, signContext );
		if( cryptStatusOK( status ) && \
			!addCertFields( cryptCert, paaCertData, __LINE__ ) )
			return( FALSE );
		if( cryptStatusOK( status ) )
			status = cryptSignCert( cryptCert, signContext );
		cryptDestroyContext( signContext );
		if( cryptStatusError( status ) )
			{
			cryptDestroyCert( cryptCert );
			printf( "\nCreation of certificate failed with error code %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		status = cryptAddPublicKey( cryptDevice, cryptCert );
		cryptDestroyCert( cryptCert );
		if( cryptStatusError( status ) )
			{
			printf( "\ncryptAddPublicKey() failed with error code %d, line "
					"%d.\n", status, __LINE__ );
			return( FALSE );
			}
		puts( " done." );
		}

	/* Set the user PIN and log on as the user.  Some devices automatically
	   log the user in when they set the user password, however, for some
	   devices this is a pseudo-login for which any subsequent operations
	   fail with a not-logged-in error or something similar, so rather than
	   relying on the existing login we always (re-)log in, which performs
	   an explicit logoff if we're already logged in at the time */
	printf( "Setting user PIN to '%s'...", userPIN );
	status = cryptSetAttributeString( cryptDevice,
									  CRYPT_DEVINFO_SET_AUTHENT_USER,
									  userPIN, strlen( userPIN ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptDevice,
										  CRYPT_DEVINFO_AUTHENT_USER,
										  userPIN, strlen( userPIN ) );
	if( cryptStatusError( status ) )
		{
		printf( "\nCouldn't set user PIN/log on as user, status = %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );

	return( TRUE );
	}

/* Open a cert store */

static int openCertStore( CRYPT_KEYSET *cryptCertStore )
	{
	int status;

	/* Clear return value */
	*cryptCertStore = -1;

	/* Open the cert store */
	printf( "Opening CA certificate store..." );
	status = cryptKeysetOpen( cryptCertStore, CRYPT_UNUSED,
							  CRYPT_KEYSET_ODBC_STORE, "testcertstore",
							  CRYPT_KEYOPT_CREATE );
	if( status == CRYPT_ERROR_DUPLICATE )
		status = cryptKeysetOpen( cryptCertStore, CRYPT_UNUSED,
								  CRYPT_KEYSET_ODBC_STORE, "testcertstore",
								  CRYPT_KEYOPT_NONE );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptKeysetOpen() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( " done." );

	return( TRUE );
	}

/* Add a PKI user to a cert store */

static int initUserInfo( const CRYPT_KEYSET cryptCertStore,
						 const char *userName )
	{
	CRYPT_CERTIFICATE cryptPKIUser;
	int length, status;

	/* Create the PKI user object and add the user's identification
	   information */
	printf( "Creating PKI user..." );
	status = cryptCreateCert( &cryptPKIUser, CRYPT_UNUSED,
							  CRYPT_CERTTYPE_PKIUSER );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptCreateCert() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttributeString( cryptPKIUser,
									  CRYPT_CERTINFO_COMMONNAME, userName,
									  strlen( userName ) );
	if( cryptStatusError( status ) )
		return( attrErrorExit( cryptPKIUser, "cryptSetAttributeString()",
							   status, __LINE__ ) );
	puts( " done." );

	/* Add the user info to the cert store */
	printf( "Adding PKI user to CA certificate store..." );
	status = cryptCAAddItem( cryptCertStore, cryptPKIUser );
	if( status == CRYPT_ERROR_DUPLICATE )
		{
		char userCN[ CRYPT_MAX_TEXTSIZE + 1 ];

		/* The PKI user info is already present from a previous run, get the
		   existing info */
		printf( "\nPKI user information is already present from a previous "
				"run, re-using existing\n  PKI user data..." );
		status = cryptGetAttributeString( cryptPKIUser,
										  CRYPT_CERTINFO_COMMONNAME,
										  userCN, &length );
		if( cryptStatusError( status ) )
			return( attrErrorExit( cryptPKIUser, "cryptGetAttribute()",
								   status, __LINE__ ) );
		userCN[ length ] = '\0';
		cryptDestroyCert( cryptPKIUser );
		status = cryptCAGetItem( cryptCertStore, &cryptPKIUser,
								 CRYPT_CERTTYPE_PKIUSER, CRYPT_KEYID_NAME,
								 userCN );
		}
	if( cryptStatusError( status ) )
		return( extErrorExit( cryptCertStore, "cryptCAAddItem()", status,
							  __LINE__ ) );
	puts( " done." );

	/* Display the information for the user */
	if( !printCertInfo( cryptPKIUser ) )
		return( FALSE );

	/* Clean up */
	cryptDestroyCert( cryptPKIUser );
	return( TRUE );
	}

/* Get details for a PKI user */

static int getUserInfo( char *userID, char *issuePW )
	{
#ifndef CLIENT_ID
	CRYPT_KEYSET cryptCertStore;
	CRYPT_CERTIFICATE cryptPKIUser;
	int length, status;

	/* Get the PKIUser object from the cert store */
	status = cryptKeysetOpen( &cryptCertStore, CRYPT_UNUSED,
							  CRYPT_KEYSET_ODBC_STORE, "testcertstore",
							  CRYPT_KEYOPT_NONE );
	if( cryptStatusError( status ) )
		{
		printf( "cryptKeysetOpen() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	status = cryptCAGetItem( cryptCertStore, &cryptPKIUser,
							 CRYPT_CERTTYPE_PKIUSER, CRYPT_KEYID_NAME,
							 "Test PKI user" );
	cryptKeysetClose( cryptCertStore );
	if( cryptStatusError( status ) )
		return( extErrorExit( cryptCertStore, "cryptCAGetItem()", status,
							  __LINE__ ) );

	/* Extract the information from the PKIUser object */
	status = cryptGetAttributeString( cryptPKIUser,
									  CRYPT_CERTINFO_PKIUSER_ID,
									  userID, &length );
	if( cryptStatusOK( status ) )
		{
		userID[ length ] = '\0';
		status = cryptGetAttributeString( cryptPKIUser,
									CRYPT_CERTINFO_PKIUSER_ISSUEPASSWORD,
									issuePW, &length );
		}
	if( cryptStatusOK( status ) )
		issuePW[ length ] = '\0';
	cryptDestroyCert( cryptPKIUser );
	if( cryptStatusError( status ) )
		return( attrErrorExit( cryptPKIUser, "cryptGetAttribute()", status,
							   __LINE__ ) );
#else
	strcpy( userID, CLIENT_ID );
	strcpy( issuePW, CLIENT_AUTHENTICATOR );
#endif /* CLIENT_ID */

	/* We've got what we need, tell the user what we're doing */
	printf( "Using user name %s, password %s.\n", userID, issuePW );
	return( TRUE );
	}

/* Run the PnP PKI server */

static int pnpServer( const CRYPT_DEVICE cryptDevice,
					  const CRYPT_KEYSET cryptCertStore )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_CONTEXT cryptPrivateKey;
	int caCertTrusted, status;

	/* Perform a cleanup action to remove any leftover requests from
	   previous runs */
	cryptCACertManagement( NULL, CRYPT_CERTACTION_CLEANUP, cryptCertStore,
						   CRYPT_UNUSED, CRYPT_UNUSED );

	/* Get the CA's key from the device and make it trusted for PKIBoot
	   functionality.  If we're running the test in loopback mode with the
	   Fortezza interface we can't have both the client and server using
	   Fortezza cards due to Spyrus driver bugs, and also can't have the
	   client and server as PKCS #11 and Fortezza due to other driver bugs,
	   so we have to fake the CA key using a software-only implementation */
	printf( "Making CA cert trusted for PKIBoot..." );
#ifndef CLIENT_ID
	if( CLIENT_DEVICE_TYPE == CRYPT_DEVICE_FORTEZZA )
		status = getPrivateKey( &cryptPrivateKey, CA_PRIVKEY_FILE,
								CA_PRIVKEY_LABEL, TEST_PRIVKEY_PASSWORD );
	else
#endif /* CLIENT_ID */
		status = cryptGetPrivateKey( cryptDevice, &cryptPrivateKey,
									 CRYPT_KEYID_NAME, "CA key", NULL );
	if( cryptStatusError( status ) )
		{
		printf( "\nCA private key read failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	cryptGetAttribute( cryptPrivateKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT,
					   &caCertTrusted );
	cryptSetAttribute( cryptPrivateKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT, 1 );
	puts( " done." );

	/* Create the CMP session and add the CA key and cert store */
	printf( "Creating CMP server session..." );
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_CMP_SERVER );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptCreateSession() failed with error code %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttribute( cryptSession,
							CRYPT_SESSINFO_PRIVATEKEY, cryptPrivateKey );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession,
							CRYPT_SESSINFO_KEYSET, cryptCertStore );
	if( cryptStatusError( status ) )
		return( attrErrorExit( cryptSession, "cryptSetAttribute()",
							   status, __LINE__ ) );
	if( !setConnectInfo( cryptSession, SERVER_MACHINE_ADDRESS,
						 SERVER_MACHINE_PORT, NET_TIMEOUT ) )
		return( FALSE );
	puts( " done." );

	/* Activate the session */
	status = activatePersistentServerSession( cryptSession );
	if( cryptStatusError( status ) )
		return( extErrorExit( cryptSession, "Attempt to activate CMP "
							  "server session", status, __LINE__ ) );

	/* Clean up */
	cryptDestroySession( cryptSession );
	if( !caCertTrusted )
		cryptSetAttribute( cryptPrivateKey,
						   CRYPT_CERTINFO_TRUSTED_IMPLICIT, 0 );

	return( TRUE );
	}

/* Run the PnP PKI client */

static int pnpClient( const CRYPT_DEVICE cryptDevice, const char *userID,
					  const char *issuePW )
	{
	CRYPT_SESSION cryptSession;
	int status;

	/* Create the CMP session and set up the information we need for the
	   plug-and-play PKI process */
	printf( "Creating CMP client session..." );
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_CMP );
	if( cryptStatusError( status ) )
		{
		printf( "\ncryptCreateSession() failed with error code %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttributeString( cryptSession,
									  CRYPT_SESSINFO_USERNAME, userID,
									  paramStrlen( userID ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_PASSWORD,
										  issuePW, paramStrlen( issuePW ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_CMP_PRIVKEYSET,
									cryptDevice );
	if( cryptStatusError( status ) )
		{
		printf( "\nAddition of session information failed with error code "
				"%d, line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	if( !setConnectInfo( cryptSession, SERVER_MACHINE_ADDRESS,
						 SERVER_MACHINE_PORT, NET_TIMEOUT ) )
		return( FALSE );
	puts( " done." );

	/* Activate the session */
	printf( "Obtaining keys and certs..." );
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "\nAttempt to activate plug-and-play "
					   "PKI client session", status, __LINE__ );
		cryptDestroySession( cryptSession );
		return( FALSE );
		}
	puts( " done." );

	/* Clean up */
	cryptDestroySession( cryptSession );
	return( TRUE );
	}

/* Client/server test harness */

static int testServer( void )
	{
	CRYPT_KEYSET cryptCertStore;
	CRYPT_DEVICE cryptDevice;
	int status;

	/* Open the device and cert store */
	status = connectDevice( &cryptDevice, SERVER_DEVICE_TYPE, 0 );
	if( status )
		status = openCertStore( &cryptCertStore );
	if( !status )
		return( FALSE );

	/* Create a CA key in the device.  Due to Spyrus bugs, it's necessary
	   to disconnect from the device and then re-connect after initialising
	   it, or crypto operations fail in various ways.  In older (and even
	   buggier) devices, crypto ops simply fail after initialisation, and
	   it may be necessary to eject and re-insert the card before they'll
	   work properly */
#if 0
	status = initDevice( cryptDevice, DEFAULT_SSO_PIN, SSO_PIN, USER_PIN );
	cryptDeviceClose( cryptDevice );
	if( status )
		status = connectDevice( &cryptDevice, SERVER_DEVICE_TYPE, 0 );
	if( status )
		status = logonDevice( cryptDevice, USER_PIN );
	if( status )
		status = createCACert( cryptDevice );
#else
	status = logonDevice( cryptDevice, USER_PIN );
#endif /* 0 */
	if( !status )
		return( FALSE );

	/* Init the PKI user */
	status = initUserInfo( cryptCertStore, "Test user #1" );
	if( !status )
		return( FALSE );

	/* Run the PnP PKI server */
	status = pnpServer( cryptDevice, cryptCertStore );

	/* Clean up */
	cryptDeviceClose( cryptDevice );
	cryptKeysetClose( cryptCertStore );

	return( status );
	}

static int testClient( void )
	{
	CRYPT_DEVICE cryptDevice;
	char userID[ CRYPT_MAX_TEXTSIZE + 1 ], issuePW[ CRYPT_MAX_TEXTSIZE + 1 ];
	int status;

	/* Open the device and get the user ID and password from the cert store.
	   Normally this would be communicated directly, for our test purposes
	   we cheat and get it from the cert store */
	status = connectDevice( &cryptDevice, CLIENT_DEVICE_TYPE,
							CLIENT_TOKEN_SLOT );
	if( status )
		status = getUserInfo( userID, issuePW );
	if( !status )
		return( FALSE );

	/* Initialise the device */
#if 1
	status = initDevice( cryptDevice, DEFAULT_SSO_PIN, SSO_PIN, USER_PIN );
	cryptDeviceClose( cryptDevice );
	if( status )
		status = connectDevice( &cryptDevice, CLIENT_DEVICE_TYPE,
								CLIENT_TOKEN_SLOT );
	if( status )
		status = logonDevice( cryptDevice, USER_PIN );
#else
	status = logonDevice( cryptDevice, USER_PIN );
#endif /* 0 */
	if( !status )
		return( FALSE );

	/* Run the PnP PKI client */
	status = pnpClient( cryptDevice, userID, issuePW );

	/* Clean up */
	cryptDeviceClose( cryptDevice );

	return( status );
	}

/* Perform a client/server loopback test */

#ifdef WINDOWS_THREADS

unsigned __stdcall serverThread( void *dummy )
	{
	testServer();
	_endthreadex( 0 );
	return( 0 );
	}

int testDeviceLifeCycle( void )
	{
#ifdef CLIENT_ID
#if 1
	return( testClient() );
#else
	return( testServer() );
#endif /* 0 */
#else
	HANDLE hThread;
	unsigned threadID;
	int status;

	/* Start the server and wait 15s for it to initialise */
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, serverThread,
										 NULL, 0, &threadID );
	Sleep( 15000 );

	/* Connect to the local server with PKIBoot enabled */
	status = testClient();
	if( WaitForSingleObject( hThread, 15000 ) == WAIT_TIMEOUT )
		{
		puts( "Warning: Server thread is still active due to session "
			  "negotiation failure,\n         this will cause an error "
			  "condition when cryptEnd() is called due\n         to "
			  "resources remaining allocated.  Press a key to continue." );
		getchar();
		}
	CloseHandle( hThread );

	return( status );
#endif /* CLIENT_ID */
	}
#endif /* WINDOWS_THREADS */
#endif /* WinCE */

#endif /* TEST_DEVICE */

/****************************************************************************
*																			*
*							User Management Routines Test					*
*																			*
****************************************************************************/

#ifdef TEST_USER

int testUser( void )
	{
	CRYPT_USER cryptUser;
	int status;

	puts( "Testing (minimal) user management functions..." );

	/* Perform a zeroise.  This currently isn't done because (a) it would
	   zeroise all user data whenever anyone runs the self-test and (b) the
	   external API to trigger this isn't defined yet */
/*	status = cryptZeroise( ... ); */

	/* Log in as primary SO using the zeroisation password.  Because of the
	   above situation this currently performs an implicit zeroise */
	status = cryptLogin( &cryptUser, TEXT( "Security officer" ),
						 TEXT( "zeroised" ) );
	if( cryptStatusError( status ) )
		{
		printf( "cryptLogin() (Primary SO) failed with error code %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Set the SO password */
	status = cryptSetAttributeString( cryptUser, CRYPT_USERINFO_PASSWORD,
									  TEXT( "password" ),
									  paramStrlen( TEXT( "password" ) ) );
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttributeString() failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Log out and log in again with the new password.  At the moment it's
	   possible to use any password until the PKCS #15 attribute situation
	   is resolved */
	status = cryptLogout( cryptUser );
	if( cryptStatusError( status ) )
		{
		printf( "cryptLogout() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	status = cryptLogin( &cryptUser, TEXT( "Security officer" ),
						 TEXT( "password" ) );
	if( cryptStatusError( status ) )
		{
		printf( "cryptLogin() (SO) failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Clean up */
	cryptLogout( cryptUser );
	puts( "User management tests succeeded.\n" );
	return( TRUE );
	}

#endif /* TEST_USER */
