/****************************************************************************
*																			*
*				cryptlib Cert Management Session Test Routines				*
*						Copyright Peter Gutmann 1998-2005					*
*																			*
****************************************************************************/

#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 */

/* If we're running the test with a crypto device, we have to set an extended
   timeout because of the long time it takes many devices to generate keys */

#define NET_TIMEOUT		180

#ifdef TEST_SESSION

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

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

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

	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 and check whether the connection is still
		   active.  If it is, we recycle the session so that we can process
		   another request */
		printConnectInfo( cryptSession );
		if( cryptStatusOK( status ) && showOperationType )
			{
			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( "SVR: Operation type was %d, user '%s'.\n",
						requestType, userID );
				}
			}
		cryptGetAttribute( cryptSession, CRYPT_SESSINFO_CONNECTIONACTIVE,
						   &connectionActive );
		}
	while( cryptStatusOK( status ) && connectionActive );

	return( status );
	}

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

static int addPKIUser( const CRYPT_KEYSET cryptCertStore,
					   const CERT_DATA *pkiUserData,
					   const BOOLEAN isSCEP )
	{
	CRYPT_CERTIFICATE cryptPKIUser;
	CRYPT_SESSION cryptSession;
	C_CHR userID[ CRYPT_MAX_TEXTSIZE + 1 ], issuePW[ CRYPT_MAX_TEXTSIZE + 1 ];
	int length, status;

	/* Create the PKI user object and add the user's identification
	   information */
	status = cryptCreateCert( &cryptPKIUser, CRYPT_UNUSED,
							  CRYPT_CERTTYPE_PKIUSER );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateCert() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	if( !addCertFields( cryptPKIUser, pkiUserData, __LINE__ ) )
		return( FALSE );

	/* Add the user info to the cert store */
	status = cryptCAAddItem( cryptCertStore, cryptPKIUser );
	if( status == CRYPT_ERROR_DUPLICATE )
		{
		C_CHR userCN[ CRYPT_MAX_TEXTSIZE + 1 ];

		/* Get the name of the duplicate user */
		status = cryptGetAttributeString( cryptPKIUser,
										  CRYPT_CERTINFO_COMMONNAME,
										  userCN, &length );
		if( cryptStatusError( status ) )
			return( attrErrorExit( cryptPKIUser, "cryptGetAttribute()",
								   status, __LINE__ ) );
#ifdef UNICODE_STRINGS
		length /= sizeof( wchar_t );
#endif /* UNICODE_STRINGS */
		userCN[ length ] = TEXT( '\0' );

		/* The PKI user info was already present, for SCEP this isn't a
		   problem since we can just re-use the existing info, but for CMP
		   we can only authorise a single cert issue per user so we have
		   to delete the existing user info and try again */
		if( isSCEP )
			{
			/* The PKI user info is already present from a previous run, get
			   the existing info */
			puts( "PKI user information is already present from a previous "
				  "run, reusing existing\n  PKI user data..." );
			cryptDestroyCert( cryptPKIUser );
			status = cryptCAGetItem( cryptCertStore, &cryptPKIUser,
									 CRYPT_CERTTYPE_PKIUSER, CRYPT_KEYID_NAME,
									 userCN );
			}
		else
			{
			puts( "PKI user information is already present from a previous "
				  "run, deleting existing\n  PKI user data..." );
			status = cryptCADeleteItem( cryptCertStore, CRYPT_CERTTYPE_PKIUSER,
										CRYPT_KEYID_NAME, userCN );
			if( cryptStatusError( status ) )
				return( extErrorExit( cryptCertStore, "cryptCADeleteItem()",
									  status, __LINE__ ) );
			status = cryptCAAddItem( cryptCertStore, cryptPKIUser );
			}
		}
	if( cryptStatusError( status ) )
		return( extErrorExit( cryptCertStore, "cryptCAAdd/GetItem()", status,
							  __LINE__ ) );

	/* Display the information for the new user and make sure the error-
	   checking in the user information works.  We have to check both
	   passwords to reduce false positives since it's just a simple integrity
	   check meant to catch typing errors rather than a cryptographically
	   strong check */
	if( !printCertInfo( cryptPKIUser ) )
		return( FALSE );
	status = cryptGetAttributeString( cryptPKIUser,
									  CRYPT_CERTINFO_PKIUSER_ID,
									  userID, &length );
	if( cryptStatusOK( status ) )
		{
#ifdef UNICODE_STRINGS
		length /= sizeof( wchar_t );
#endif /* UNICODE_STRINGS */
		userID[ length ] = '\0';
		status = cryptGetAttributeString( cryptPKIUser,
									CRYPT_CERTINFO_PKIUSER_ISSUEPASSWORD,
									issuePW, &length );
		}
	if( cryptStatusOK( status ) )
		{
#ifdef UNICODE_STRINGS
		length /= sizeof( wchar_t );
#endif /* UNICODE_STRINGS */
		issuePW[ length ] = '\0';
		}
	else
		return( attrErrorExit( cryptPKIUser, "cryptGetAttribute()", status,
							   __LINE__ ) );
	cryptCreateSession( &cryptSession, CRYPT_UNUSED, CRYPT_SESSION_CMP );
	if( userID[ 2 ] >= TEXT( 'A' ) && userID[ 2 ] < TEXT( 'Z' ) )
		userID[ 2 ]++;
	else
		userID[ 2 ] = TEXT( 'A' );
	if( issuePW[ 8 ] >= TEXT( 'A' ) && issuePW[ 8 ] < TEXT( 'Z' ) )
		issuePW[ 8 ]++;
	else
		issuePW[ 8 ] = TEXT( 'A' );
	status = cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_USERNAME,
									  userID, paramStrlen( userID ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession, CRYPT_SESSINFO_PASSWORD,
										  issuePW, paramStrlen( issuePW ) );
	if( cryptStatusOK( status ) )
		{
		puts( "Integrity check of user ID and password failed to catch "
			  "errors in the data.\n(This check isn't foolproof and is "
			  "intended only to catch typing errors when\nentering the "
			  "data.  Try running the test again to see if the problem "
			  "still\noccurs)." );
		return( FALSE );
		}
	cryptDestroySession( cryptSession );

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

/* Get information on a PKI user */

static int getPkiUserInfo( C_STR userID, C_STR issuePW, C_STR revPW,
						   C_STR userName )
	{
	CRYPT_KEYSET cryptCertStore;
	CRYPT_CERTIFICATE cryptPKIUser;
	int length, status;

	/* cryptlib implements per-user (rather than shared interop) IDs and
	   passwords so we need to read the user ID and password information
	   before we can perform any operations.  First we get the PkiUser
	   object */
	status = cryptKeysetOpen( &cryptCertStore, CRYPT_UNUSED,
							  CERTSTORE_KEYSET_TYPE, CERTSTORE_KEYSET_NAME,
							  CRYPT_KEYOPT_NONE );
	if( status == CRYPT_ERROR_PARAM3 )
		{
		/* This type of keyset access isn't available, return a special error
		   code to indicate that the test wasn't performed, but that this
		   isn't a reason to abort processing */
		puts( "No certificate store available, aborting CMP test.\n" );
		return( CRYPT_ERROR_NOTAVAIL );
		}
	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,
							 userName );
	cryptKeysetClose( cryptCertStore );
	if( cryptStatusError( status ) )
		{
		/* Only report error info if it's not a basic presence check */
		if( userID != NULL )
			extErrorExit( cryptCertStore, "cryptCAGetItem()", status, __LINE__ );
		return( FALSE );
		}

	/* If it's a presence check only, we're done */
	if( userID == NULL )
		{
		cryptDestroyCert( cryptPKIUser );
		return( TRUE );
		}

	/* Then we 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';
	if( cryptStatusOK( status ) && revPW != NULL )
		{
		status = cryptGetAttributeString( cryptPKIUser,
									CRYPT_CERTINFO_PKIUSER_REVPASSWORD,
									revPW, &length );
		if( cryptStatusOK( status ) )
			revPW[ length ] = '\0';
		}
	cryptDestroyCert( cryptPKIUser );
	if( cryptStatusError( status ) )
		{
		attrErrorExit( cryptPKIUser, "cryptGetAttribute()", status,
					   __LINE__ );
		return( FALSE );
		}

	/* 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 );
	}

/* Set up objects and information needed by a server-side PKI session */

static int serverInit( CRYPT_CONTEXT *cryptPrivateKey,
					   CRYPT_KEYSET *cryptCertStore, const C_STR keyFileName,
					   const C_STR keyLabel, const CERT_DATA *pkiUserData,
					   const CERT_DATA *pkiUserCAData,
					   const char *protocolName )
	{
	int status;

	/* Get the cert store to use with the session.  Before we use the store
	   we perform a cleanup action to remove any leftover requests from
	   previous runs */
	status = cryptKeysetOpen( cryptCertStore, CRYPT_UNUSED,
							  CERTSTORE_KEYSET_TYPE, CERTSTORE_KEYSET_NAME,
							  CRYPT_KEYOPT_CREATE );
	if( status == CRYPT_ERROR_PARAM3 )
		{
		/* This type of keyset access isn't available, return a special error
		   code to indicate that the test wasn't performed, but that this
		   isn't a reason to abort processing */
		printf( "SVR: No certificate store available, aborting %s server "
				"test.\n\n", protocolName );
		return( CRYPT_ERROR_NOTAVAIL );
		}
	if( status == CRYPT_ERROR_DUPLICATE )
		status = cryptKeysetOpen( cryptCertStore, CRYPT_UNUSED,
								  CERTSTORE_KEYSET_TYPE, CERTSTORE_KEYSET_NAME,
								  CRYPT_KEYOPT_NONE );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: cryptKeysetOpen() failed with error code %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	cryptCACertManagement( NULL, CRYPT_CERTACTION_CLEANUP, *cryptCertStore,
						   CRYPT_UNUSED, CRYPT_UNUSED );

	/* Create the EE and CA PKI users */
	puts( "Creating PKI user..." );
	if( !addPKIUser( *cryptCertStore, pkiUserData,
					 !strcmp( protocolName, "SCEP" ) ? TRUE : FALSE ) )
		return( FALSE );
	if( pkiUserCAData != NULL && \
		!addPKIUser( *cryptCertStore, pkiUserCAData,
					 !strcmp( protocolName, "SCEP" ) ? TRUE : FALSE ) )
		return( FALSE );

	/* Get the CA's private key */
	status = getPrivateKey( cryptPrivateKey, keyFileName,
							keyLabel, TEST_PRIVKEY_PASSWORD );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: CA private key read failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	return( TRUE );
	}

/****************************************************************************
*																			*
*								SCEP Routines Test							*
*																			*
****************************************************************************/

/* There are various SCEP test servers available, the following mappings
   can be used to test different ones.  Implementation peculiarities:

	#1 - cryptlib: None.

	#2 - SSH (www.ssh.com/support/testzone/pki.html): Invalid CA certs.

	#3 - OpenSCEP (openscep.othello.ch): Seems to be permanently unavailable.

	#4 - Entrust (freecerts.entrust.com/vpncerts/cep.htm): Only seems to be
			set up to handle Cisco gear.

	#5 - EJBCA: */

#define SCEP_NO		1

typedef struct {
	const char FAR_BSS *name;
	const C_CHR FAR_BSS *url, FAR_BSS *user, FAR_BSS *password, FAR_BSS *caCertUrl;
	} SCEP_INFO;

static const SCEP_INFO FAR_BSS scepInfo[] = {
	{ NULL },	/* Dummy so index == SCEP_NO */
	{ /*1*/ "cryptlib", TEXT( "http://localhost/pkiclient.exe" ), NULL, NULL, NULL },
	{ /*2*/ "SSH", TEXT( "http://pki.ssh.com:8080/scep/pkiclient.exe" ), TEXT( "ssh" ), TEXT( "ssh" ),
			TEXT( "http://pki.ssh.com:8080/scep/pkiclient.exe?operation=GetCACert&message=test-ca1.ssh.com" ) },
	{ /*3*/ "OpenSCEP", TEXT( "http://openscep.othello.ch/pkiclient.exe" ), TEXT( "????" ), TEXT( "????" ), NULL },
	{ /*4*/ "Entrust", TEXT( "http://vpncerts.entrust.com/pkiclient.exe" ), TEXT( "????" ), TEXT( "????" ), NULL },
	{ /*5*/ "EJBCA", TEXT( "http://q-rl-xp:8080/ejbca/publicweb/apply/scep/pkiclient.exe" ),
			TEXT("test2"), TEXT("test2"),
			TEXT( "http://q-rl-xp:8080/ejbca/publicweb/webdist/certdist?cmd=nscacert&issuer=O=Test&+level=1" ) },
	};

/* Cert request data for the cert from the SCEP server.  Note that we have
   to set the CN to the PKI user CN, for CMP ir's we just omit the DN
   entirely and have the server provide it for us but since SCEP uses PKCS
   #10 requests we need to provide a DN, and since we provide it it has to
   match the PKI user DN */

static const CERT_DATA FAR_BSS scepRequestData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Test SCEP PKI user" ) },

	/* Subject altName */
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave@wetas-r-us.com" ) },
	{ CRYPT_CERTINFO_UNIFORMRESOURCEIDENTIFIER, IS_STRING, 0, TEXT( "http://www.wetas-r-us.com" ) },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

/* PKI user data to authorise the issuing of the various certs */

static const CERT_DATA FAR_BSS scepPkiUserData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Test SCEP PKI user" ) },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

/* Get an SCEP CA cert */

static int getScepCACert( const C_STR caCertUrl,
						  CRYPT_CERTIFICATE *cryptCACert )
	{
	CRYPT_KEYSET cryptKeyset;
	int status;

	status = cryptKeysetOpen( &cryptKeyset, CRYPT_UNUSED, CRYPT_KEYSET_HTTP,
							  caCertUrl, CRYPT_KEYOPT_READONLY );
	if( cryptStatusOK( status ) )
		{
		status = cryptGetPublicKey( cryptKeyset, cryptCACert, CRYPT_KEYID_NAME,
									TEXT( "[None]" ) );
		cryptKeysetClose( cryptKeyset );
		}
	if( cryptStatusError( status ) )
		return( extErrorExit( cryptKeyset, "cryptGetPublicKey()",
							  status, __LINE__ ) );

	return( TRUE );
	}

/* Perform an SCEP test */

static int connectSCEP( void )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_CERTIFICATE cryptRequest, cryptResponse, cryptCACert;
	CRYPT_CONTEXT cryptContext;
#if ( SCEP_NO == 1 )
	C_CHR userID[ 64 ], password[ 64 ];
#endif /* cryptlib SCEP_NO == 1 */
	const C_STR userPtr = scepInfo[ SCEP_NO ].user;
	const C_STR passwordPtr = scepInfo[ SCEP_NO ].password;
	int status;

	printf( "Testing %s SCEP session...\n", scepInfo[ SCEP_NO ].name );

#if ( SCEP_NO == 1 )
	/* If we're doing a loopback test, make sure that the required user info
	   is present.  If it isn't, the CA auditing will detect a request from
	   a nonexistant user and refuse to issue a certificate */
	if( !getPkiUserInfo( NULL, NULL, NULL, TEXT( "Test SCEP PKI user" ) ) )
		{
		puts( "CA certificate store doesn't contain the PKI user "
			  "information needed to\nauthenticate certificate issue "
			  "operations, can't perform SCEP test.\n" );
		return( CRYPT_ERROR_NOTAVAIL );
		}
#endif /* cryptlib SCEP_NO == 1 */

	/* Get the issuing CA's cert */
	if( scepInfo[ SCEP_NO ].caCertUrl != NULL )
		{
		if( !getScepCACert( scepInfo[ SCEP_NO ].caCertUrl, &cryptCACert ) )
			return( FALSE );
		}
	else
		{
		status = importCertFromTemplate( &cryptCACert, SCEP_CA_FILE_TEMPLATE,
										 SCEP_NO );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't get SCEP CA certificate, status = %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		}

	/* cryptlib implements per-user (rather than shared interop) IDs and
	   passwords so we need to read the user ID and password information
	   before we can perform any operations */
#if ( SCEP_NO == 1 )
	status = getPkiUserInfo( userID, password, NULL,
							 TEXT( "Test SCEP PKI user" ) );
	if( status == CRYPT_ERROR_NOTAVAIL )
		{
		/* Cert store operations aren't available, exit but continue with
		   other tests */
		cryptDestroyCert( cryptCACert );
		return( TRUE );
		}
	else
		if( !status )
			{
			cryptDestroyCert( cryptCACert );
			return( FALSE );
			}
	userPtr = userID;
	passwordPtr = password;
#endif /* cryptlib SCEP_NO == 1 */

	/* Create the SCEP session */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_SCEP );
	if( status == CRYPT_ERROR_PARAM3 )	/* SCEP session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Set up the user and server information */
	status = cryptSetAttributeString( cryptSession,
									  CRYPT_SESSINFO_USERNAME,
									  userPtr, paramStrlen( userPtr ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_PASSWORD,
										  passwordPtr, paramStrlen( passwordPtr ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
									CRYPT_SESSINFO_SERVER_NAME,
									scepInfo[ SCEP_NO ].url,
									paramStrlen( scepInfo[ SCEP_NO ].url ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_CACERTIFICATE,
									cryptCACert );
	cryptDestroyCert( cryptCACert );
	if( cryptStatusError( status ) )
		{
		printf( "Addition of session information failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Create the (unsigned) PKCS #10 request */
#if ( SCEP_NO == 1 )
	cryptCreateContext( &cryptContext, CRYPT_UNUSED, CRYPT_ALGO_RSA );
	cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_LABEL,
							 USER_PRIVKEY_LABEL,
							 paramStrlen( USER_PRIVKEY_LABEL ) );
	status = cryptGenerateKey( cryptContext );
	if( cryptStatusOK( status ) )
#else
	loadRSAContextsEx( CRYPT_UNUSED, NULL, &cryptContext, NULL,
					   USER_PRIVKEY_LABEL );
#endif /* cryptlib SCEP_NO == 1 */
	status = cryptCreateCert( &cryptRequest, CRYPT_UNUSED,
							  CRYPT_CERTTYPE_CERTREQUEST );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptRequest,
							CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, cryptContext );
	if( cryptStatusOK( status ) && \
		!addCertFields( cryptRequest, scepRequestData, __LINE__ ) )
		status = CRYPT_ERROR_FAILED;
#if 0
	if( cryptStatusOK( status ) )
		status = cryptSignCert( cryptRequest, cryptContext );
#endif
	if( cryptStatusError( status ) )
		{
		printf( "Creation of PKCS #10 request failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Set up the private key and request, and activate the session */
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_PRIVATEKEY,
								cryptContext );
	cryptDestroyContext( cryptContext );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_REQUEST,
									cryptRequest );
	cryptDestroyCert( cryptRequest );
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttribute() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "Attempt to activate SCEP client "
					   "session", status, __LINE__ );
		cryptDestroySession( cryptSession );
		if( status == CRYPT_ERROR_OPEN || status == CRYPT_ERROR_READ )
			{
			/* These servers are constantly appearing and disappearing so if
			   we get a straight connect error we don't treat it as a serious
			   failure */
			puts( "  (Server could be down, faking it and continuing...)\n" );
			return( CRYPT_ERROR_FAILED );
			}
		return( FALSE );
		}

	/* Obtain the response information */
	status = cryptGetAttribute( cryptSession, CRYPT_SESSINFO_RESPONSE,
								&cryptResponse );
	cryptDestroySession( cryptSession );
	if( cryptStatusError( status ) )
		{
		printf( "cryptGetAttribute() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
#if ( SCEP_NO != 1 )
	puts( "Returned certificate details are:" );
	printCertInfo( cryptResponse );
#endif /* Keep the cryptlib results on one screen */

	/* Clean up */
	cryptDestroyCert( cryptResponse );
	puts( "SCEP client session succeeded.\n" );
	return( TRUE );
	}

int testSessionSCEP( void )
	{
	return( connectSCEP() );
	}

int testSessionSCEPServer( void )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_CONTEXT cryptCAKey;
	CRYPT_KEYSET cryptCertStore;
	int status;

	puts( "SVR: Testing SCEP server session ..." );

	/* Perform a test create of a SCEP server session to verify that we can
	   do this test */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_SCEP_SERVER );
	if( status == CRYPT_ERROR_PARAM3 )	/* SCEP session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: cryptCreateSession() failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	cryptDestroySession( cryptSession );

	/* Set up the server-side objects */
	if( !serverInit( &cryptCAKey, &cryptCertStore, SCEPCA_PRIVKEY_FILE,
					 USER_PRIVKEY_LABEL, scepPkiUserData, NULL, "SCEP" ) )
		return( FALSE );

	/* Create the SCEP session and add the CA key and cert store */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_SCEP_SERVER );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: cryptCreateSession() failed with error code %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttribute( cryptSession,
							CRYPT_SESSINFO_PRIVATEKEY, cryptCAKey );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession,
							CRYPT_SESSINFO_KEYSET, cryptCertStore );
	if( cryptStatusError( status ) )
		return( attrErrorExit( cryptSession, "SVR: cryptSetAttribute()",
							   status, __LINE__ ) );

	/* Activate the session */
	status = activatePersistentServerSession( cryptSession, FALSE );
	if( cryptStatusError( status ) )
		{
		cryptKeysetClose( cryptCertStore );
		cryptDestroyContext( cryptCAKey );
		return( extErrorExit( cryptSession, "SVR: Attempt to activate SCEP "
							  "server session", status, __LINE__ ) );
		}

	/* Clean up */
	cryptDestroySession( cryptSession );
	cryptKeysetClose( cryptCertStore );
	cryptDestroyContext( cryptCAKey );

	puts( "SVR: SCEP session succeeded.\n" );
	return( TRUE );
	}

/* Perform a client/server loopback test */

#ifdef WINDOWS_THREADS

unsigned __stdcall scepServerThread( void *dummy )
	{
	testSessionSCEPServer();
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionSCEPClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

#if ( SCEP_NO != 1 )
	/* Because the code has to handle so many CA-specific peculiarities, we
	   can only perform this test when the CA being used is the cryptlib
	   CA */
	puts( "Error: The local SCEP session test only works with SCEP_NO == 1." );
	return( FALSE );
#endif /* cryptlib CA */

	/* Start the server and wait for it to initialise (this takes a bit
	   longer than the other servers because we have to work with a cert
	   store so we wait a bit longer than usual) */
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, scepServerThread,
										 NULL, 0, &threadID );
	Sleep( 3000 );

	/* Connect to the local server */
	status = connectSCEP();
	waitForThread( hThread );
	return( status );
	}
#endif /* WINDOWS_THREADS */

/****************************************************************************
*																			*
*								CMP Routines Test							*
*																			*
****************************************************************************/

/* There are various CMP test CAs available, the following mappings can be
   used to test different ones.  Implementation peculiarities:

	#1 - cryptlib: Implicitly revokes cert being replaced during a kur (this
			is a requirement for maintaining cert store consistency).
			Tested: ir, cr/kur, rr
	#2 - cryptlib with PKIBoot/PnP PKI functionality, otherwise as for #1.
	#3 - Certicom: Requires signature for revocation rather than MAC,
			requires that all certs created after the ir one have the same
			DN as the ir cert.
			Tested: ir, cr/kur, rr
	#4 - ssh old: None (recently re-issued their CA cert which is broken, CA
			couldn't be re-tested.  In addition since CMP identifies the
			sender by DN the new cert can't be distinguished from the old
			one, causing all sig checks to fail).
			Tested (late 2000): ir, cr/kur, rr
	#5 - ssh new:
	#6 - Entrust: Won't allow altNames, changes sender and request DN,
			returns rejected response under an altered DN belonging to a
			completely different EE for anything but ir.
			Tested: ir
	#7 - Trustcenter: Requires HTTPS and pre-existing trusted private key
			distributed as PKCS #12 file, couldn't be tested.
	#8 - Baltimore: Server unavailable for testing.
			Tested: -
	#9 - Initech: Needs DN cn=CryptLIB EE 1,o=INITECH,c=KR.
			Tested: ir, cr/kur, rr
	#10 - RSA labs: Rejects signed requests, couldn't be tested beyond initial
			(MACd) ir.  Attempt to revoke newly-issued cert with MACd rr
			returns error indicating that the cert is already revoked.
			Tested: ir
	#11 - Cylink: Invalid CA root cert, requires use of DN from RA rather
			than CA when communicating with server.
			Tested: - */

#define CA_CRYPTLIB				1
#define CA_CRYPTLIB_PNPPKI		2

#define CA_NO					CA_CRYPTLIB

typedef struct {
	const char FAR_BSS *name;
	const C_CHR FAR_BSS *url, FAR_BSS *user, FAR_BSS *password;
	} CA_INFO;

static const CA_INFO FAR_BSS caInfo[] = {
	{ NULL },	/* Dummy so index == CA_NO */
	{ /*1*/ "cryptlib", TEXT( "http://localhost" ), TEXT( "interop" ), TEXT( "interop" ) },
	{ /*2*/	"cryptlib/PKIBoot", /*"_pkiboot._tcp.cryptoapps.com"*/TEXT( "http://localhost" ), TEXT( "interop" ), TEXT( "interop" ) },
	{ /*3*/ "Certicom", TEXT( "cmp://gandalf.trustpoint.com:8081" ), TEXT( "interop" ), TEXT( "interop" ) },
	{ /*4*/ "ssh", TEXT( "cmp://interop-ca.ssh.com:8290" ), TEXT( "123456" ), TEXT( "interop" ) },
	{ /*5*/ "ssh", TEXT( "http://pki.ssh.com:8080/pkix/" ), TEXT( "62154" ), TEXT( "ssh" ) },
	{ /*6*/ "Entrust", TEXT( "cmp://204.101.128.45:829" ), TEXT( "39141091" ), TEXT( "ABCDEFGHIJK" ) },
	{ /*7*/ "Trustcenter", TEXT( "cmp://demo.trustcenter.de/cgi-bin/cmp:829" ), TEXT( "interop" ), TEXT( "interop" ) },
	{ /*8*/ "Baltimore", TEXT( "cmp://hip.baltimore.ie:8290" ), TEXT( "pgutmann" ), TEXT( "the-magical-land-near-oz" ) },
	{ /*9*/ "Initech", TEXT( "cmp://61.74.133.49:8290" ), TEXT( "interop" ), TEXT( "interop" ) },
	{ /*A*/ "RSA", TEXT( "cmp://ca1.kcspilot.com:32829" ), TEXT( "interop" ), TEXT( "interop" ) },
	{ /*B*/ "Cylink", TEXT( "cmp://216.252.217.227:8082" ), TEXT( "3986" ), TEXT( "11002" ) /* "3987", "6711" */ }
	};

/* Enable additional tests if we're using cryptlib as the server */

#if ( CA_NO == CA_CRYPTLIB ) || ( CA_NO == CA_CRYPTLIB_PNPPKI )
  #define SERVER_IS_CRYPTLIB
  #if ( CA_NO == CA_CRYPTLIB_PNPPKI )
	#define SERVER_PKIBOOT
  #endif /* cryptlib PKIBoot server */
#endif /* Extra tests for cryptib CA */

/* Define the following to work around CA bugs/quirks */

#if ( CA_NO == 3 )			/* Certicom */
  #define SERVER_IR_DN
#endif /* CA that requires same DN in cr as ir */
#if ( CA_NO == 6 )			/* Entrust */
  #define SERVER_NO_ALTNAMES
#endif /* CAs that won't allow altNames in requests */
#if ( CA_NO == 9 )			/* Initech */
  #define SERVER_FIXED_DN
#endif /* CAs that require a fixed DN in requests */

/* The following defines can be used to selectively enable or disable some
   of the test (for example to do an ir + rr, or ir + kur + rr) */

#ifdef SERVER_IS_CRYPTLIB
  #define TEST_IR
/*#define TEST_DUP_IR */
  #define TEST_KUR
  #define TEST_CR
  #define TEST_RR

  /* 3 cert reqs, 1 rev.req (kur = impl.rev) plus duplicate ir to check for
     rejection of second request for same user.  The duplicate-ir check is
	 currently disabled because it's enforced via database transaction
	 constraints, which means that once the initial ir has been recorded all
	 further issue operations with the same ID are excluded by the presence
	 of the ID for the ir.  This is a strong guarantee that subsequent
	 requests with the same ID will be disallowed, but not terribly useful
	 for self-test purposes */
  #define NO_CA_REQUESTS	( 4 + 0 )
#else
  #define TEST_IR
  #define TEST_KUR
  #define TEST_CR
  #define TEST_RR

  /* Loopback test requires SERVER_IS_CRYPTLIB */
  #define NO_CA_REQUESTS	0
#endif /* SERVER_IS_CRYPTLIB */

/* Define the following to enable testing of servers where the initial DN 
   (and optional additional information like the altName) is supplied by the 
   server (i.e. the user supplies a null DN) */

#ifdef SERVER_IS_CRYPTLIB
  #define SERVER_PROVIDES_DN
#endif /* CAs where the server provides the DN */

/* Cert request data for the various types of certs that a CMP CA can return */

static const CERT_DATA FAR_BSS cmpRsaSignRequestData[] = {
	/* Identification information */
  #ifdef SERVER_FIXED_DN
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "KR" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "INITECH" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "CryptLIB EE 1" ) },
  #else
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Dave's Signature Key" ) },
  #endif /* CAs that require a fixed DN in requests */

	/* Subject altName */
#ifndef SERVER_NO_ALTNAMES
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave@wetas-r-us.com" ) },
	{ CRYPT_CERTINFO_UNIFORMRESOURCEIDENTIFIER, IS_STRING, 0, TEXT( "http://www.wetas-r-us.com" ) },
#endif /* CAs that won't allow altNames in requests */

	/* Signature-only key */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC, CRYPT_KEYUSAGE_DIGITALSIGNATURE },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};
static const CERT_DATA FAR_BSS cmpRsaSignRequestNoDNData[] = {
	/* Identification information - none, it's provided by the server */

	/* Subject altName - none, it's provided by the server */

	/* Signature-only key */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC, CRYPT_KEYUSAGE_DIGITALSIGNATURE },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};
static const CERT_DATA FAR_BSS cmpRsaEncryptRequestData[] = {
	/* Identification information */
#ifdef SERVER_FIXED_DN
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "KR" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "INITECH" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "CryptLIB EE 1" ) },
#else
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Dave's Encryption Key" ) },
#endif /* CAs that require a fixed DN in requests */

	/* Subject altName */
#ifndef SERVER_NO_ALTNAMES
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave@wetas-r-us.com" ) },
	{ CRYPT_CERTINFO_UNIFORMRESOURCEIDENTIFIER, IS_STRING, 0, TEXT( "http://www.wetas-r-us.com" ) },
#endif /* CAs that won't allow altNames in requests */

	/* Encryption-only key */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC, CRYPT_KEYUSAGE_KEYENCIPHERMENT },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};
static const CERT_DATA FAR_BSS cmpRsaCaRequestData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Dave's Intermediate CA Key" ) },

	/* Subject altName */
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave-ca@wetas-r-us.com" ) },
	{ CRYPT_CERTINFO_UNIFORMRESOURCEIDENTIFIER, IS_STRING, 0, TEXT( "http://www.wetas-r-us.com" ) },

	/* CA key */
	{ CRYPT_CERTINFO_CA, IS_NUMERIC, TRUE },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};
static const CERT_DATA FAR_BSS cmpDsaRequestData[] = {
	/* Identification information */
#ifdef SERVER_FIXED_DN
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "KR" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "INITECH" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "CryptLIB EE 1" ) },
#else
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
  #ifdef SERVER_IR_DN
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Dave's Signature Key" ) },
  #else
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Dave's DSA Key" ) },
  #endif /* CA that requires same DN in cr as ir */
#endif /* CAs that require a fixed DN in requests */

	/* Subject altName */
#ifndef SERVER_NO_ALTNAMES
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave@wetas-r-us.com" ) },
	{ CRYPT_CERTINFO_UNIFORMRESOURCEIDENTIFIER, IS_STRING, 0, TEXT( "http://www.wetas-r-us.com" ) },
#endif /* CAs that won't allow altNames in requests */

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

/* PKI user data to authorise the issuing of the various certs */

static const CERT_DATA FAR_BSS cmpPkiUserData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Test PKI user" ) },

	/* Subject altName */
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave@wetas-r-us.com" ) },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};
static const CERT_DATA FAR_BSS cmpPkiUserCaData[] = {
	/* Identification information */
	{ CRYPT_CERTINFO_COUNTRYNAME, IS_STRING, 0, TEXT( "NZ" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONNAME, IS_STRING, 0, TEXT( "Dave's Wetaburgers" ) },
	{ CRYPT_CERTINFO_ORGANIZATIONALUNITNAME, IS_STRING, 0, TEXT( "Procurement" ) },
	{ CRYPT_CERTINFO_COMMONNAME, IS_STRING, 0, TEXT( "Test CA PKI user" ) },

	/* Subject altName */
	{ CRYPT_CERTINFO_RFC822NAME, IS_STRING, 0, TEXT( "dave@ca.wetas-r-us.com" ) },

	/* CA extensions */
	{ CRYPT_CERTINFO_KEYUSAGE, IS_NUMERIC,
	  CRYPT_KEYUSAGE_KEYCERTSIGN | CRYPT_KEYUSAGE_CRLSIGN },
	{ CRYPT_CERTINFO_CA, IS_NUMERIC, TRUE },

	{ CRYPT_ATTRIBUTE_NONE, IS_VOID }
	};

/* Create various CMP objects */

static int createCmpRequest( const CERT_DATA *requestData,
							 const CRYPT_CONTEXT privateKey,
							 const CRYPT_ALGO_TYPE cryptAlgo,
							 const BOOLEAN useFixedKey,
							 const CRYPT_KEYSET cryptKeyset )
	{
	CRYPT_CERTIFICATE cryptRequest;
	int status;

	/* Create the CMP (CRMF) request */
	if( privateKey != CRYPT_UNUSED )
		{
		time_t startTime;
		int dummy;

		/* If we're updating an existing cert we have to vary something in
		   the request to make sure that the result doesn't duplicate an
		   existing cert, to do this we fiddle the start time */
		status = cryptGetAttributeString( privateKey, CRYPT_CERTINFO_VALIDFROM,
										  &startTime, &dummy );
		if( cryptStatusError( status ) )
			return( FALSE );
		startTime++;

		/* It's an update of existing information, sign the request with the
		   given private key */
		status = cryptCreateCert( &cryptRequest, CRYPT_UNUSED,
								  CRYPT_CERTTYPE_REQUEST_CERT );
		if( cryptStatusOK( status ) )
			status = cryptSetAttribute( cryptRequest,
						CRYPT_CERTINFO_CERTIFICATE, privateKey );
		if( cryptStatusOK( status ) )
			status = cryptSetAttributeString( cryptRequest,
						CRYPT_CERTINFO_VALIDFROM, &startTime, sizeof( time_t ) );
		if( cryptStatusOK( status ) )
			status = cryptSignCert( cryptRequest, privateKey );
		if( cryptKeyset != CRYPT_UNUSED )
			{
			if( cryptStatusError( \
					cryptAddPrivateKey( cryptKeyset, privateKey,
										TEST_PRIVKEY_PASSWORD ) ) )
				return( FALSE );
			}
		}
	else
		{
		CRYPT_CONTEXT cryptContext;

		/* It's a new request, generate a private key and create a self-
		   signed request */
		if( useFixedKey )
			{
			/* Use a fixed private key, for testing purposes */
			if( cryptAlgo == CRYPT_ALGO_RSA )
				loadRSAContextsEx( CRYPT_UNUSED, NULL, &cryptContext, NULL,
								   USER_PRIVKEY_LABEL, FALSE, FALSE );
			else
				loadDSAContextsEx( CRYPT_UNUSED, &cryptContext, NULL,
								   USER_PRIVKEY_LABEL, NULL );
			status = CRYPT_OK;
			}
		else
			{
			cryptCreateContext( &cryptContext, CRYPT_UNUSED, cryptAlgo );
			cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_LABEL,
									 USER_PRIVKEY_LABEL,
									 paramStrlen( USER_PRIVKEY_LABEL ) );
			status = cryptGenerateKey( cryptContext );
			}
		if( cryptStatusOK( status ) )
			status = cryptCreateCert( &cryptRequest, CRYPT_UNUSED,
									  CRYPT_CERTTYPE_REQUEST_CERT );
		if( cryptStatusOK( status ) )
			status = cryptSetAttribute( cryptRequest,
						CRYPT_CERTINFO_SUBJECTPUBLICKEYINFO, cryptContext );
		if( cryptStatusOK( status ) && \
			!addCertFields( cryptRequest, requestData, __LINE__ ) )
			status = CRYPT_ERROR_FAILED;
		if( cryptStatusOK( status ) )
			status = cryptSignCert( cryptRequest, cryptContext );
		if( cryptKeyset != CRYPT_UNUSED )
			{
			if( cryptStatusError( \
					cryptAddPrivateKey( cryptKeyset, cryptContext,
										TEST_PRIVKEY_PASSWORD ) ) )
				return( FALSE );
			}
		cryptDestroyContext( cryptContext );
		}
	if( cryptStatusError( status ) )
		{
		printf( "Creation of CMP request failed with error code %d, line "
				"%d.\n", status, __LINE__ );
		return( FALSE );
		}

	return( cryptRequest );
	}

static int createCmpRevRequest( const CRYPT_CERTIFICATE cryptCert )
	{
	CRYPT_CERTIFICATE cryptRequest;
	int status;

	/* Create the CMP (CRMF) revocation request */
	status = cryptCreateCert( &cryptRequest, CRYPT_UNUSED,
							  CRYPT_CERTTYPE_REQUEST_REVOCATION );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptRequest, CRYPT_CERTINFO_CERTIFICATE,
									cryptCert );
	if( cryptStatusError( status ) )
		{
		printf( "Creation of CMP revocation request failed with error code "
				"%d, line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	return( cryptRequest );
	}

static int createCmpSession( const CRYPT_CONTEXT cryptCACert,
							 const C_STR server, const C_STR user,
							 const C_STR password,
							 const CRYPT_CONTEXT privateKey,
							 const BOOLEAN isRevocation,
							 const BOOLEAN isUpdate,
							 const BOOLEAN isPKIBoot )
	{
	CRYPT_SESSION cryptSession;
	int status;

	/* Create the CMP session */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_CMP );
	if( status == CRYPT_ERROR_PARAM3 )	/* CMP session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Set up the user and server information.  Revocation requests can be
	   signed or MACd so we handle either.  When requesting a cert using a
	   signed request (i.e.not an initialisation request) we use an update
	   since we're reusing the previously-generated cert data to request a
	   new one and some CAs won't allow this reuse for a straight request
	   but require explicit use of an update request */
	if( privateKey != CRYPT_UNUSED )
		{
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_CMP_REQUESTTYPE,
									isRevocation ? \
										CRYPT_REQUESTTYPE_REVOCATION : \
									isUpdate ? \
										CRYPT_REQUESTTYPE_KEYUPDATE : \
										CRYPT_REQUESTTYPE_CERTIFICATE );
		if( cryptStatusOK( status ) )
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_PRIVATEKEY,
										privateKey );
		}
	else
		{
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_USERNAME, user,
										  paramStrlen( user ) );
		if( cryptStatusOK( status ) )
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_CMP_REQUESTTYPE,
										isPKIBoot ? \
											CRYPT_REQUESTTYPE_PKIBOOT : \
										isRevocation ? \
											CRYPT_REQUESTTYPE_REVOCATION : \
											CRYPT_REQUESTTYPE_INITIALISATION );
		if( cryptStatusOK( status ) )
			status = cryptSetAttributeString( cryptSession,
									CRYPT_SESSINFO_PASSWORD, password,
									paramStrlen( password ) );
		}
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										CRYPT_SESSINFO_SERVER_NAME, server,
										paramStrlen( server ) );
	if( cryptStatusOK( status ) && cryptCACert != CRYPT_UNUSED )
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_CACERTIFICATE,
									cryptCACert );
	if( cryptStatusError( status ) )
		{
		printf( "Addition of session information failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	return( cryptSession );
	}

/* Request a particular cert type */

static int requestCert( const char *description, const CA_INFO *caInfoPtr,
						const C_STR readKeysetName,
						const C_STR writeKeysetName,
						const CERT_DATA *requestData,
						const CRYPT_ALGO_TYPE cryptAlgo,
						const CRYPT_CONTEXT cryptCACert,
						const BOOLEAN isPKIBoot, const BOOLEAN isDupIR,
						CRYPT_CERTIFICATE *issuedCert )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_KEYSET cryptKeyset = CRYPT_UNUSED;
	CRYPT_CONTEXT privateKey = CRYPT_UNUSED;
	CRYPT_CERTIFICATE cryptCmpRequest, cryptCmpResponse;
	const BOOLEAN useExistingKey = ( requestData == NULL ) ? TRUE : FALSE;
	int status;

#ifdef SERVER_PROVIDES_DN
	printf( "Testing %s processing with absent subject DN...\n", description );
#else
	printf( "Testing %s processing...\n", description );
#endif /* SERVER_PROVIDES_DN */

	/* Read the key needed to request a new cert from a keyset if necessary,
	   and create a keyset to save a new key to if required.  We have to do
	   the write last in case the read and write keyset are the same */
	if( readKeysetName != NULL )
		{
		status = getPrivateKey( &privateKey, readKeysetName,
								USER_PRIVKEY_LABEL, TEST_PRIVKEY_PASSWORD );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't get private key to request new certificate, "
					"status = %d, line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		}
	if( writeKeysetName != NULL )
		{
		status = cryptKeysetOpen( &cryptKeyset, CRYPT_UNUSED,
								  CRYPT_KEYSET_FILE, writeKeysetName,
								  CRYPT_KEYOPT_CREATE );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't create keyset to store certificate to, "
					"status = %d, line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		}

	/* Create the CMP session */
	cryptSession = createCmpSession( cryptCACert, caInfoPtr->url,
									 caInfoPtr->user, caInfoPtr->password,
									 privateKey, FALSE, useExistingKey,
									 isPKIBoot );
	if( cryptSession <= 0 )
		{
		if( cryptKeyset != CRYPT_UNUSED )
			cryptKeysetClose( cryptKeyset );
		return( cryptSession );
		}

	/* Set up the request.  Some CAs explicitly disallow multiple dissimilar
	   certs to exist for the same key (in fact for non-test servers other
	   CAs probably enforce this as well) but generating a new key for each
	   request is time-consuming so we only do it if it's enforced by the
	   CA */
	if( !isPKIBoot )
		{
#if defined( SERVER_IS_CRYPTLIB ) || defined( SERVER_FIXED_DN )
		cryptCmpRequest = createCmpRequest( requestData,
								useExistingKey ? privateKey : CRYPT_UNUSED,
								cryptAlgo, FALSE, cryptKeyset );
#else
		KLUDGE_WARN( "fixed key for request" );
		cryptCmpRequest = createCmpRequest( requestData,
								useExistingKey ? privateKey : CRYPT_UNUSED,
								cryptAlgo, TRUE, cryptKeyset );
#endif /* cryptlib and Initech won't allow two certs for same key */
		if( !cryptCmpRequest )
			return( FALSE );
		if( privateKey != CRYPT_UNUSED )
			cryptDestroyContext( privateKey );
		status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_REQUEST,
									cryptCmpRequest );
		cryptDestroyCert( cryptCmpRequest );
		if( cryptStatusError( status ) )
			{
			printf( "cryptSetAttribute() failed with error code %d, line %d.\n",
					status, __LINE__ );
			return( FALSE );
			}
		}

	/* Activate the session */
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	if( cryptStatusError( status ) )
		{
		if( cryptKeyset != CRYPT_UNUSED )
			cryptKeysetClose( cryptKeyset );
		if( isDupIR && status == CRYPT_ERROR_DUPLICATE )
			{
			/* If we're trying to get a duplicate cert issued then we're
			   supposed to fail at this point */
			cryptDestroySession( cryptSession );
			return( FALSE );
			}
		printExtError( cryptSession, "Attempt to activate CMP client session",
					   status, __LINE__ );
		cryptDestroySession( cryptSession );
		if( status == CRYPT_ERROR_OPEN || status == CRYPT_ERROR_READ )
			{
			/* These servers are constantly appearing and disappearing so if
			   we get a straight connect error we don't treat it as a serious
			   failure */
			puts( "  (Server could be down, faking it and continuing...)\n" );
			return( CRYPT_ERROR_FAILED );
			}
		if( status == CRYPT_ERROR_FAILED )
			{
			/* A general failed response is more likely to be due to the
			   server doing something unexpected than a cryptlib problem so
			   we don't treat it as a fatal error */
			puts( "  (This is more likely to be an issue with the server than "
				  "with cryptlib,\n   faking it and continuing...)\n" );
			return( CRYPT_ERROR_FAILED );
			}
		return( FALSE );
		}

	/* If it's a PKIBoot, which just sets (implicitly) trusted certs, we're
	   done */
	if( isPKIBoot )
		{
		cryptDestroySession( cryptSession );
		return( TRUE );
		}

	/* Obtain the response information */
	status = cryptGetAttribute( cryptSession, CRYPT_SESSINFO_RESPONSE,
								&cryptCmpResponse );
	cryptDestroySession( cryptSession );
	if( cryptStatusError( status ) )
		{
		printf( "cryptGetAttribute() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
#ifndef SERVER_IS_CRYPTLIB
	puts( "Returned certificate details are:" );
	printCertInfo( cryptCmpResponse );
#endif /* Keep the cryptlib results on one screen */
	if( cryptKeyset != CRYPT_UNUSED )
		{
		status = cryptAddPublicKey( cryptKeyset, cryptCmpResponse );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't write certificate to keyset, status = %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		cryptKeysetClose( cryptKeyset );
		}
	if( issuedCert != NULL )
		*issuedCert = cryptCmpResponse;
	else
		cryptDestroyCert( cryptCmpResponse );

	/* Clean up */
	printf( "%s processing succeeded.\n\n", description );
	return( TRUE );
	}

/* Revoke a previously-issued cert */

static int revokeCert( const char *description, const CA_INFO *caInfoPtr,
					   const C_STR keysetName,
					   const CRYPT_CERTIFICATE certToRevoke,
					   const CRYPT_CONTEXT cryptCACert,
					   const BOOLEAN signRequest )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_CONTEXT privateKey = CRYPT_UNUSED;
	CRYPT_CERTIFICATE cryptCmpRequest, cryptCert = certToRevoke;
	int status;

	printf( "Testing %s revocation processing...\n", description );

	/* Get the cert to revoke if necessary.  In some cases the server won't
	   accept a revocation password, so we have to get the private key as
	   well to sign the request */
	if( signRequest || cryptCert == CRYPT_UNUSED )
		{
		CRYPT_KEYSET cryptKeyset;

		status = cryptKeysetOpen( &cryptKeyset, CRYPT_UNUSED,
								  CRYPT_KEYSET_FILE, keysetName,
								  CRYPT_KEYOPT_READONLY );
		if( cryptStatusOK( status ) && signRequest )
			status = getPrivateKey( &privateKey, keysetName,
									USER_PRIVKEY_LABEL,
									TEST_PRIVKEY_PASSWORD );
		if( cryptStatusOK( status ) && cryptCert == CRYPT_UNUSED )
			status = cryptGetPublicKey( cryptKeyset, &cryptCert,
										CRYPT_KEYID_NAME,
										USER_PRIVKEY_LABEL );
		cryptKeysetClose( cryptKeyset );
		if( cryptStatusError( status ) )
			{
			puts( "Couldn't fetch certificate/key to revoke.\n" );
			return( FALSE );
			}
		}

	/* Create the CMP session and revocation request */
	cryptSession = createCmpSession( cryptCACert, caInfoPtr->url,
									 caInfoPtr->user, caInfoPtr->password,
									 privateKey, TRUE, FALSE, FALSE );
	if( privateKey != CRYPT_UNUSED )
		cryptDestroyContext( privateKey );
	if( cryptSession <= 0 )
		return( cryptSession );
	cryptCmpRequest = createCmpRevRequest( cryptCert );
	if( !cryptCmpRequest )
		return( FALSE );

	/* Set up the request and activate the session */
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_REQUEST,
								cryptCmpRequest );
	cryptDestroyCert( cryptCmpRequest );
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttribute() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "Attempt to activate CMP client session",
					   status, __LINE__ );
		cryptDestroySession( cryptSession );
		if( cryptCert != certToRevoke )
			cryptDestroyCert( cryptCert );
		if( status == CRYPT_ERROR_OPEN || status == CRYPT_ERROR_READ )
			{
			/* These servers are constantly appearing and disappearing so if
			   we get a straight connect error we don't treat it as a serious
			   failure */
			puts( "  (Server could be down, faking it and continuing...)\n" );
			return( CRYPT_ERROR_FAILED );
			}
		if( status == CRYPT_ERROR_FAILED )
			{
			/* A general failed response is more likely to be due to the
			   server doing something unexpected than a cryptlib problem so
			   we don't treat it as a fatal error */
			puts( "  (This is more likely to be an issue with the server than "
				  "with cryptlib,\n   faking it and continuing...)\n" );
			return( CRYPT_ERROR_FAILED );
			}
		return( FALSE );
		}

	/* Clean up */
	if( cryptCert != certToRevoke )
		cryptDestroyCert( cryptCert );
	cryptDestroySession( cryptSession );
	printf( "%s processing succeeded.\n\n", description );
	return( TRUE );
	}

/* Test the full range of CMP functionality.  This performs the following
   tests:

	RSA sign:
		ir + ip + reject (requires cmp.c mod)
		ir + ip + certconf + pkiconf
		kur + kup + certconf + pkiconf
		cr + cp + certconf + pkiconf (not performed since same as kur)
		rr + rp (of ir cert)
		rr + rp (of kur cert)
	RSA encr.:
		ir + ip + reject (requires cmp.c mod)
		ir + ip + certconf + pkiconf
		rr + rp (of ir cert)
	DSA:
		cr + cp + certconf + pkiconf (success implies that ir/kur/rr
						works since they've already been tested for RSA) */

static int connectCMP( const BOOLEAN usePKIBoot, const BOOLEAN localSession )
	{
	CRYPT_CERTIFICATE cryptCACert = CRYPT_UNUSED, cryptCert;
	C_CHR readFileName[ FILENAME_BUFFER_SIZE ];
	C_CHR writeFileName[ FILENAME_BUFFER_SIZE ];
#ifdef SERVER_IS_CRYPTLIB
	CA_INFO cryptlibCAInfo, *caInfoPtr = &cryptlibCAInfo;
  #ifdef TEST_IR
	C_CHR userID[ 64 ], issuePW[ 64 ];
  #endif /* SERVER_IS_CRYPTLIB */
#else
	const CA_INFO *caInfoPtr = &caInfo[ CA_NO ];
#endif /* cryptlib */
	int status;

#ifdef SERVER_IS_CRYPTLIB
	/* Wait for the server to finish initialising */
	if( localSession && waitMutex() == CRYPT_ERROR_TIMEOUT )
		{
		printf( "Timed out waiting for server to initialise, line %d.\n",
				__LINE__ );
		return( FALSE );
		}

	/* Set up the fixed info in the CA info record */
	memcpy( &cryptlibCAInfo, &caInfo[ CA_NO ], sizeof( CA_INFO ) );
	cryptlibCAInfo.name = "cryptlib";

	/* Make sure that the required user info is present.  If it isn't, the
	   CA auditing will detect a request from a nonexistant user and refuse
	   to issue a certificate */
	if( !getPkiUserInfo( NULL, NULL, NULL, TEXT( "Test PKI user" ) ) )
		{
		puts( "CA certificate store doesn't contain the PKI user "
			  "information needed to\nauthenticate certificate issue "
			  "operations.  This is probably because the\nserver loopback "
			  "test (which initialises the cert store) hasn't been run "
			  "yet.\nSkipping CMP test.\n" );
		return( CRYPT_ERROR_NOTAVAIL );
		}
#endif /* SERVER_IS_CRYPTLIB */

	/* Get the cert of the CA who will issue the cert unless we're doing a
	   PKIBoot, in which case the cert is obtained during the PKIBoot
	   process */
#ifndef SERVER_IS_CRYPTLIB
	printf( "Using the %s CMP server.\n", caInfoPtr->name );
#endif /* !SERVER_IS_CRYPTLIB */
#ifndef SERVER_PKIBOOT
	status = importCertFromTemplate( &cryptCACert, CMP_CA_FILE_TEMPLATE,
									 CA_NO );
	if( cryptStatusError( status ) )
		{
		printf( "Couldn't get CMP CA certificate, status = %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
#endif /* !SERVER_PKIBOOT */

	/* Test each cert request type: Initialisation, cert request using cert
	   from initialisation for authentication, key update of cert from
	   initialisation, revocation of both certs.  We insert a 1s delay
	   between requests to give the server time to recycle */

	/* Initialisation request */
#ifdef TEST_IR
  #ifdef SERVER_IS_CRYPTLIB
	/* cryptlib implements per-user (rather than shared interop) IDs and
	   passwords so we need to read the user ID and password information
	   before we can perform any operations */
	status = getPkiUserInfo( userID, issuePW, NULL,
							 TEXT( "Test PKI user" ) );
	if( status == CRYPT_ERROR_NOTAVAIL )
		{
		/* Cert store operations aren't available, exit but continue with
		   other tests */
	#ifndef SERVER_PKIBOOT
		cryptDestroyCert( cryptCACert );
	#endif /* !SERVER_PKIBOOT */
		return( TRUE );
		}
	else
		if( !status )
			{
	#ifndef SERVER_PKIBOOT
			cryptDestroyCert( cryptCACert );
	#endif /* !SERVER_PKIBOOT */
			return( FALSE );
			}

	/* Set up the variable info in the CA info record */
	cryptlibCAInfo.user = userID;
	cryptlibCAInfo.password = issuePW;
  #endif /* SERVER_IS_CRYPTLIB */
	/* Initialisation.  We define REVOKE_FIRST_CERT to indicate that we can
	   revoke this one later on */
	#define REVOKE_FIRST_CERT
	filenameParamFromTemplate( writeFileName, CMP_PRIVKEY_FILE_TEMPLATE, 1 );
	status = requestCert( "RSA signing cert.init.request", caInfoPtr, NULL,
						  usePKIBoot ? NULL : writeFileName,
#if defined( SERVER_PROVIDES_DN )
						  cmpRsaSignRequestNoDNData,
#else
						  cmpRsaSignRequestData,
#endif /* SERVER_PROVIDES_DN */
						  CRYPT_ALGO_RSA, cryptCACert, usePKIBoot, FALSE,
						  &cryptCert );
	if( status != TRUE )
		{
		/* If this is the self-test and there's a non-fatal error, make sure
		   we don't fail with a CRYPT_ERROR_INCOMPLETE when we're finished */
		cryptDestroyCert( cryptCACert );
		return( status );
		}
	if( usePKIBoot )
		{
		/* If we're testing the PKIBoot capability, there's only a single
		   request to process */
		cryptDestroyCert( cryptCACert );
		return( TRUE );
		}
	delayThread( 2 );
#endif /* TEST_IR */
#ifdef TEST_DUP_IR
	/* Attempt a second ir using the same PKI user data.  This should fail,
	   since the cert store only allows a single ir per user */
	if( requestCert( "Duplicate init.request", caInfoPtr, NULL, NULL,
					 cmpRsaSignRequestNoDNData, CRYPT_ALGO_RSA, cryptCACert,
					 FALSE, TRUE, NULL ) )
		{
		printf( "Duplicate init request wasn't detected by the CMP server, "
				"line %d.\n\n", __LINE__ );
		cryptDestroyCert( cryptCACert );
		return( FALSE );
		}
#endif /* TEST_DUP_IR */

	/* Cert request.  We have to perform this test before the kur since some
	   CAs implicitly revoke the cert being replaced, which means we can't
	   use it to authenticate requests any more once the kur has been
	   performed */
#ifdef TEST_CR
	/* We define REVOKE_SECOND_CERT to indicate that we can revoke this one
	   later on alongside the ir/kur'd cert, and save a copy to a file for
	   later use */
	#define REVOKE_SECOND_CERT
	filenameParamFromTemplate( readFileName, CMP_PRIVKEY_FILE_TEMPLATE, 1 );
	filenameParamFromTemplate( writeFileName, CMP_PRIVKEY_FILE_TEMPLATE, 2 );
	status = requestCert( "RSA signing certificate request", caInfoPtr,
						  readFileName, writeFileName, cmpRsaSignRequestData,
						  CRYPT_ALGO_RSA, cryptCACert, FALSE, FALSE, NULL );
	if( status != TRUE )
		{
  #if defined( TEST_IR )
		cryptDestroyCert( cryptCert );
  #endif /* TEST_IR || TEST_KUR */
		cryptDestroyCert( cryptCACert );
		return( status );
		}
	delayThread( 2 );
#endif /* TEST_CR */

	/* Key update request */
#ifdef TEST_KUR
  #ifdef TEST_IR
	/* We just created the cert, delete it so we can replace it with the
	   updated form */
	cryptDestroyCert( cryptCert );
  #endif /* TEST_IR */

	/* If it's a CA that implicitly revokes the cert being replaced (in
	   which case tracking things gets a bit too complicated since we now
	   need to use the updated rather than original cert to authenticate the
	   request) we just leave it unrevoked (the first cert is always
	   revoked) */
  #ifdef SERVER_IS_CRYPTLIB
	#undef REVOKE_FIRST_CERT
  #endif /* SERVER_IS_CRYPTLIB */

	/* Key update */
	filenameParamFromTemplate( readFileName, CMP_PRIVKEY_FILE_TEMPLATE, 1 );
	status = requestCert( "RSA signing certificate update", caInfoPtr,
						  readFileName, NULL, NULL, CRYPT_UNUSED,
						  cryptCACert, FALSE, FALSE, &cryptCert );
	if( status != TRUE )
		{
		cryptDestroyCert( cryptCACert );
		return( status );
		}
	delayThread( 2 );
#endif /* TEST_KUR */
#if 0
	/* DSA cert request.  We have to get this now because we're about to
	   revoke the cert we're using to sign the requests */
	filenameParamFromTemplate( readFileName, CMP_PRIVKEY_FILE_TEMPLATE, 1 );
	status = requestCert( "DSA certificate", caInfoPtr, readFileName, NULL,
						  cmpDsaRequestData, CRYPT_ALGO_DSA, cryptCACert,
						  FALSE, FALSE, NULL );
	if( status != TRUE )
		return( status );
	delayThread( 2 );
#endif /* 0 */
#if 0
	/* Encryption-only cert request.  This test requires a change in
	   certsign.c because when creating a cert request cryptlib always
	   allows signing for the request even if it's an encryption-only key
	   (this is required for PKCS #10, see the comment in the kernel code).
	   Because of this a request will always appear to be associated with a
	   signature-enabled key so it's necessary to make a code change to
	   disallow this.  Disallowing sigs for encryption-only keys would break
	   PKCS #10 since it's then no longer possible to create the self-signed
	   request, this is a much bigger concern than CMP.  Note that this
	   functionality is tested by the PnP PKI test, which creates the
	   necessary encryption-only requests internally and can do things that
	   we can't do from the outside */
	status = requestCert( "RSA encryption certificate", caInfoPtr,
						  readFileName, writeFileName, cmpRsaEncryptRequestData,
						  CRYPT_ALGO_RSA, cryptCACert, FALSE, FALSE, NULL );
	if( status != TRUE )
		return( status );
	delayThread( 2 );
#endif /* 0 */

	/* Revocation request */
#ifdef TEST_RR
	filenameParamFromTemplate( readFileName, CMP_PRIVKEY_FILE_TEMPLATE, 1 );
  #ifdef REVOKE_FIRST_CERT
	#ifdef SERVER_IR_DN
	status = revokeCert( "RSA initial/updated certificate", caInfoPtr,
						 readFileName, cryptCert, cryptCACert, TRUE );
	#else
	status = revokeCert( "RSA initial/updated certificate", caInfoPtr,
						 readFileName, cryptCert, cryptCACert, FALSE );
	#endif /* Certicom requires signed request */
	cryptDestroyCert( cryptCert );
	delayThread( 2 );
  #elif !defined( TEST_KUR ) || !defined( SERVER_IS_CRYPTLIB )
	/* We didn't issue the first cert in this run, try revoking it from
	   the cert stored in the key file unless we're talking to a CA that
	   implicitly revokes the cert being replaced during a kur */
	status = revokeCert( "RSA initial/updated certificate", caInfoPtr,
						 readFileName, CRYPT_UNUSED, cryptCACert, TRUE );
  #else
	/* This is a kur'd cert for which the original has been implicitly
	   revoked, we can't do much else with it */
	cryptDestroyCert( cryptCert );
  #endif /* REVOKE_FIRST_CERT */
	if( status != TRUE )
		{
		cryptDestroyCert( cryptCACert );
		return( status );
		}
  #ifdef REVOKE_SECOND_CERT
	/* We requested a second cert, revoke that too.  Note that we have to
	   sign this with the second cert since the first one may have just been
	   revoked */
	filenameParamFromTemplate( readFileName, CMP_PRIVKEY_FILE_TEMPLATE, 2 );
	status = revokeCert( "RSA signing certificate", caInfoPtr, readFileName,
						 CRYPT_UNUSED, cryptCACert, TRUE );
	if( status != TRUE )
		{
		cryptDestroyCert( cryptCACert );
		return( status );
		}
  #endif /* REVOKE_SECOND_CERT */
#endif /* TEST_RR */

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

int testSessionCMP( void )
	{
	return( connectCMP( FALSE, FALSE ) );
	}

/* Test the plug-and-play PKI functionality */

static int connectPNPPKI( const BOOLEAN isCaUser, const BOOLEAN useDevice,
						  const BOOLEAN localSession )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_KEYSET cryptKeyset;
	C_CHR userID[ 64 ], issuePW[ 64 ];
	int status;

	/* Create the CMP session */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_CMP );
	if( status == CRYPT_ERROR_PARAM3 )	/* CMP session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Open the device/create the keyset to contain the keys.  This doesn't
	   perform a full device.c-style auto-configure but assumes that it's
	   talking to a device that's already been initialised and is ready to
	   go */
	if( useDevice )
		{
		status = cryptDeviceOpen( &cryptKeyset, CRYPT_UNUSED,
								  CRYPT_DEVICE_PKCS11,
								  TEXT( "[Autodetect]" ) );
		if( cryptStatusError( status ) )
			{
			printf( "Crypto device open failed with error code %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		status = cryptSetAttributeString( cryptKeyset,
										  CRYPT_DEVINFO_AUTHENT_USER,
										  "test", 4 );
		if( cryptStatusError( status ) )
			{
			printf( "\nDevice login failed with error code %d, line %d.\n",
					status, __LINE__ );
			return( FALSE );
			}
		if( cryptDeleteKey( cryptKeyset, CRYPT_KEYID_NAME,
							TEXT( "Signature key" ) ) == CRYPT_OK )
			puts( "(Deleted a signature key object, presumably a leftover "
				  "from a previous run)." );
		if( cryptDeleteKey( cryptKeyset, CRYPT_KEYID_NAME,
							TEXT( "Encryption key" ) ) == CRYPT_OK )
			puts( "(Deleted an encryption key object, presumably a leftover "
				  "from a previous run)." );
		}
	else
		{
		status = cryptKeysetOpen( &cryptKeyset, CRYPT_UNUSED,
								  CRYPT_KEYSET_FILE, isCaUser ? \
										PNPCA_PRIVKEY_FILE : PNP_PRIVKEY_FILE,
								  CRYPT_KEYOPT_CREATE );
		if( cryptStatusError( status ) )
			{
			printf( "User keyset create failed with error code %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		}

	/* Wait for the server to finish initialising */
	if( localSession && waitMutex() == CRYPT_ERROR_TIMEOUT )
		{
		printf( "Timed out waiting for server to initialise, line %d.\n",
				__LINE__ );
		return( FALSE );
		}

	/* Get information needed for enrolment */
	status = getPkiUserInfo( userID, issuePW, NULL, isCaUser ? \
								TEXT( "Test CA PKI user" ) : \
								TEXT( "Test PKI user" ) );
	if( status == CRYPT_ERROR_NOTAVAIL )
		/* Cert store operations aren't available, exit but continue with
		   other tests */
		return( TRUE );
	else
		if( !status )
			return( FALSE );

	/* Set up the information we need for the plug-and-play PKI process */
	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 = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SERVER_NAME,
										  caInfo[ CA_CRYPTLIB_PNPPKI ].url,
										  paramStrlen( caInfo[ CA_CRYPTLIB_PNPPKI ].url ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_CMP_PRIVKEYSET,
									cryptKeyset );
	if( cryptStatusOK( status ) && useDevice )
		{
		/* Keygen on a device can take an awfully long time for some devices,
		   so we set an extended timeout to allow for this */
		cryptSetAttribute( cryptSession, CRYPT_OPTION_NET_READTIMEOUT,
						   NET_TIMEOUT );
		status = cryptSetAttribute( cryptSession,
									CRYPT_OPTION_NET_WRITETIMEOUT,
									NET_TIMEOUT );
		}
	if( useDevice )
		cryptDeviceClose( cryptKeyset );
	else
		cryptKeysetClose( cryptKeyset );
	if( cryptStatusError( status ) )
		{
		printf( "Addition of session information failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Activate the session */
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "Attempt to activate plug-and-play PKI "
					   "client session", status, __LINE__ );
		cryptDestroySession( cryptSession );
		return( FALSE );
		}

	/* Clean up */
	cryptDestroySession( cryptSession );

	/* If this is the intermediate CA cert, change the password to allow it
	   to be used with the standard PnP PKI test */
	if( isCaUser )
		{
		CRYPT_CONTEXT cryptKey;

		/* Get the newly-issued key */
		status = cryptKeysetOpen( &cryptKeyset, CRYPT_UNUSED,
								  CRYPT_KEYSET_FILE, PNPCA_PRIVKEY_FILE,
								  CRYPT_KEYOPT_NONE );
		if( cryptStatusOK( status ) )
			{
			status = cryptGetPrivateKey( cryptKeyset, &cryptKey,
										 CRYPT_KEYID_NAME,
										 TEXT( "Signature key" ), issuePW );
			cryptKeysetClose( cryptKeyset );
			}
		if( cryptStatusError( status ) )
			{
			printf( "Certified private-key read failed with error code %d, "
					"line %d.\n", status, __LINE__ );
			return( FALSE );
			}

		/* Replace the keyset with one with the key protected with a
		   different password */
		status = cryptKeysetOpen( &cryptKeyset, CRYPT_UNUSED,
								  CRYPT_KEYSET_FILE, PNPCA_PRIVKEY_FILE,
								  CRYPT_KEYOPT_CREATE );
		if( cryptStatusOK( status ) )
			{
			status = cryptAddPrivateKey( cryptKeyset, cryptKey,
										 TEST_PRIVKEY_PASSWORD );
			cryptKeysetClose( cryptKeyset );
			}
		cryptDestroyContext( cryptKey );
		if( cryptStatusError( status ) )
			{
			printf( "Certified private-key password change failed with error "
					"code %d, line %d.\n", status, __LINE__ );
			return( FALSE );
			}
		}

	return( TRUE );
	}

int testSessionPNPPKI( void )
	{
	return( connectPNPPKI( FALSE, FALSE, FALSE ) );
	}

/* Test the CMP server */

static int cmpServerSingleIteration( const CRYPT_CONTEXT cryptPrivateKey,
									 const CRYPT_KEYSET cryptCertStore,
									 const BOOLEAN useDevice )
	{
	CRYPT_SESSION cryptSession;
	int status;

	/* Create the CMP session and add the CA key and cert store */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_CMP_SERVER );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: cryptCreateSession() 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( cryptStatusOK( status ) && useDevice )
		{
		/* Keygen on a device can take an awfully long time for some devices,
		   so we set an extended timeout to allow for this */
		cryptSetAttribute( cryptSession, CRYPT_OPTION_NET_READTIMEOUT,
						   NET_TIMEOUT );
		status = cryptSetAttribute( cryptSession,
									CRYPT_OPTION_NET_WRITETIMEOUT,
									NET_TIMEOUT );
		}
	if( cryptStatusError( status ) )
		return( attrErrorExit( cryptSession, "SVR: cryptSetAttribute()",
							   status, __LINE__ ) );
	if( !setLocalConnect( cryptSession, 80 ) )
		return( FALSE );

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

	/* We processed the request, clean up */
	cryptDestroySession( cryptSession );
	return( TRUE );
	}

int testSessionCMPServer( void )
	{
	CRYPT_SESSION cryptSession;
	CRYPT_CONTEXT cryptCAKey;
	CRYPT_KEYSET cryptCertStore;
	int caCertTrusted, i, status;

	/* Acquire the init mutex */
	waitMutex();

	puts( "SVR: Testing CMP server session..." );

	/* Perform a test create of a CMP server session to verify that we can
	   do this test */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_CMP_SERVER );
	if( status == CRYPT_ERROR_PARAM3 )	/* CMP session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: cryptCreateSession() failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	cryptDestroySession( cryptSession );

	/* Set up the server-side objects */
	if( !serverInit( &cryptCAKey, &cryptCertStore, CA_PRIVKEY_FILE,
					 CA_PRIVKEY_LABEL, cmpPkiUserData, cmpPkiUserCaData,
					 "CMP" ) )
		return( FALSE );

	/* Make the CA key trusted for PKIBoot functionality */
	cryptGetAttribute( cryptCAKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT,
					   &caCertTrusted );
	cryptSetAttribute( cryptCAKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT, 1 );

	/* Tell the client that we're ready to go */
	releaseMutex();

	/* Run the server several times to handle the different requests */
	for( i = 0; i < NO_CA_REQUESTS; i++ )
		{
		printf( "SVR: Running server iteration %d.\n", i + 1 );
		if( !cmpServerSingleIteration( cryptCAKey, cryptCertStore, FALSE ) )
			{
#ifdef SERVER_IS_CRYPTLIB
			/* If we're running the loopback test and this is the second
			   iteration, the client is testing the ability to detect a
			   duplicate ir, so a failure is expected */
			if( i == 1 )
				{
				puts( "SVR: Failure was due to a rejected duplicate request "
					  "from the client,\n     continuing..." );
				continue;
				}
#endif /* SERVER_IS_CRYPTLIB */
			break;
			}
		}
	if( i == 0 )
		/* None of the requests succeeded */
		return( FALSE );
	printf( "SVR: %d of %d server requests were processed.\n", i,
			NO_CA_REQUESTS );

	/* Issue a CRL to make sure that the revocation was performed correctly.
	   We do this now because the cert management self-test can't easily
	   perform the check because it requires a CMP-revoked cert in order to
	   function */
	if( i == NO_CA_REQUESTS )
		{
		CRYPT_CERTIFICATE cryptCRL;
		int noEntries = 0;

		/* Issue the CRL */
		status = cryptCACertManagement( &cryptCRL, CRYPT_CERTACTION_ISSUE_CRL,
										cryptCertStore, cryptCAKey,
										CRYPT_UNUSED );
		if( cryptStatusError( status ) )
			return( extErrorExit( cryptCertStore, "cryptCACertManagement()",
								  status, __LINE__ ) );

		/* Make sure that the CRL contains at least one entry */
		if( cryptStatusOK( cryptSetAttribute( cryptCRL,
											  CRYPT_CERTINFO_CURRENT_CERTIFICATE,
											  CRYPT_CURSOR_FIRST ) ) )
			do
				noEntries++;
			while( cryptSetAttribute( cryptCRL,
									  CRYPT_CERTINFO_CURRENT_CERTIFICATE,
									  CRYPT_CURSOR_NEXT ) == CRYPT_OK );
		if( noEntries <= 0 )
			{
			puts( "CRL created from revoked certificate is empty, should "
				  "contain at least one\ncertificate entry." );
			return( FALSE );
			}

		/* Clean up */
		cryptDestroyCert( cryptCRL );
		}

	/* Clean up */
	if( !caCertTrusted )
		cryptSetAttribute( cryptCAKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT, 0 );
	cryptKeysetClose( cryptCertStore );
	cryptDestroyContext( cryptCAKey );

	puts( "SVR: CMP session succeeded.\n" );
	return( TRUE );
	}

/* Perform a client/server loopback test */

#ifdef WINDOWS_THREADS

static int pnppkiServer( const BOOLEAN pkiBootOnly, const BOOLEAN isCaUser,
						 const BOOLEAN isIntermediateCA,
						 const BOOLEAN useDevice )
	{
	CRYPT_CONTEXT cryptCAKey;
	CRYPT_KEYSET cryptCertStore;
	int caCertTrusted;

	/* Acquire the PNP PKI init mutex */
	waitMutex();

	printf( "SVR: Testing %s server session%s...\n",
			pkiBootOnly ? "PKIBoot" : "plug-and-play PKI",
			isCaUser ? " for CA cert" : \
				isIntermediateCA ? " using intermediate CA" : "" );

	/* Get the information needed by the server */
	if( isIntermediateCA )
		{
		/* The intermediate CA has a PnP-generated, so the key label is
		   the predefined PnP signature key one */
		if( !serverInit( &cryptCAKey, &cryptCertStore,
						 PNPCA_PRIVKEY_FILE, TEXT( "Signature key" ),
						 cmpPkiUserData, cmpPkiUserCaData, "CMP" ) )
			return( FALSE );
		}
	else
		{
		if( !serverInit( &cryptCAKey, &cryptCertStore, CA_PRIVKEY_FILE,
						 CA_PRIVKEY_LABEL, cmpPkiUserData,
						 cmpPkiUserCaData, "CMP" ) )
			return( FALSE );
		}

	/* Make the CA key trusted for PKIBoot functionality */
	cryptGetAttribute( cryptCAKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT,
					   &caCertTrusted );
	cryptSetAttribute( cryptCAKey, CRYPT_CERTINFO_TRUSTED_IMPLICIT, 1 );

	/* Tell the client that we're ready to go */
	releaseMutex();

	/* Run the server once to handle the plug-and-play PKI process */
	if( !cmpServerSingleIteration( cryptCAKey, cryptCertStore, useDevice ) )
		return( FALSE );

	/* Clean up */
	if( !caCertTrusted )
		cryptSetAttribute( cryptCAKey,
						   CRYPT_CERTINFO_TRUSTED_IMPLICIT, 0 );
	cryptKeysetClose( cryptCertStore );
	cryptDestroyContext( cryptCAKey );

	puts( "SVR: Plug-and-play PKI session succeeded.\n" );
	return( TRUE );
	}

unsigned __stdcall cmpServerThread( void *dummy )
	{
	testSessionCMPServer();
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionCMPClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

#ifndef SERVER_IS_CRYPTLIB
	/* Because the code has to handle so many CA-specific peculiarities, we
	   can only perform this test when the CA being used is the cryptlib
	   CA */
	puts( "Error: The local CMP session test only works with the cryptlib "
		  "CA." );
	return( FALSE );
#endif /* !SERVER_IS_CRYPTLIB */

	/* Start the server */
	createMutex();
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, cmpServerThread,
										 NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server */
	status = connectCMP( FALSE, TRUE );
	waitForThread( hThread );
	destroyMutex();
	return( status );
	}

unsigned __stdcall cmpPKIBootServerThread( void *dummy )
	{
	pnppkiServer( TRUE, FALSE, FALSE, FALSE );
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionCMPPKIBootClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

#ifndef SERVER_IS_CRYPTLIB
	/* Because the code has to handle so many CA-specific peculiarities, we
	   can only perform this test when the CA being used is the cryptlib
	   CA */
	puts( "Error: The local CMP session test only works with the cryptlib "
		  "CA." );
	return( FALSE );
#endif /* !SERVER_IS_CRYPTLIB */

	/* Start the server */
	createMutex();
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, cmpPKIBootServerThread,
										 NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server with PKIBoot enabled */
	status = connectCMP( TRUE, TRUE );
	waitForThread( hThread );
	destroyMutex();
	return( status );
	}

unsigned __stdcall cmpPnPPKIServerThread( void *dummy )
	{
	/* Call with the third parameter set to TRUE to use a chain of CA certs
	   (i.e. an intermediate CA between the root and end user) rather than
	   a single CA cert directly issuing the cert to the end user */
	pnppkiServer( FALSE, FALSE, FALSE, FALSE );
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionPNPPKIClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

	/* Start the server */
	createMutex();
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, cmpPnPPKIServerThread,
										 NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server with PKIBoot enabled */
	status = connectPNPPKI( FALSE, FALSE, TRUE );
	waitForThread( hThread );
	destroyMutex();
	return( status );
	}

unsigned __stdcall cmpPnPPKIDeviceServerThread( void *dummy )
	{
	/* Call with the third parameter set to TRUE to use a chain of CA certs
	   (i.e. an intermediate CA between the root and end user) rather than
	   a single CA cert directly issuing the cert to the end user */
	pnppkiServer( FALSE, FALSE, FALSE, TRUE );
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionPNPPKIDeviceClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

	/* Start the server */
	createMutex();
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, cmpPnPPKIDeviceServerThread,
										 NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server with PKIBoot enabled */
	status = connectPNPPKI( FALSE, TRUE, TRUE );
	waitForThread( hThread );
	destroyMutex();
	return( status );
	}

unsigned __stdcall cmpPnPPKICaServerThread( void *dummy )
	{
	pnppkiServer( FALSE, TRUE, FALSE, FALSE );
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionPNPPKICAClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

	/* Start the server */
	createMutex();
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, cmpPnPPKICaServerThread,
										 NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server with PKIBoot enabled */
	status = connectPNPPKI( TRUE, FALSE, TRUE );
	waitForThread( hThread );
	destroyMutex();
	return( status );
	}

unsigned __stdcall cmpPnPPKIIntermedCaServerThread( void *dummy )
	{
	pnppkiServer( FALSE, FALSE, TRUE, FALSE );
	_endthreadex( 0 );
	return( 0 );
	}

int testSessionPNPPKIIntermedCAClientServer( void )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

	/* Start the server */
	createMutex();
	hThread = ( HANDLE ) _beginthreadex( NULL, 0, cmpPnPPKIIntermedCaServerThread,
										 NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server with PKIBoot enabled */
	status = connectPNPPKI( FALSE, FALSE, TRUE );
	waitForThread( hThread );
	destroyMutex();
	return( status );
	}
#endif /* WINDOWS_THREADS */

#endif /* TEST_SESSION */
