/*************************************************************************
* *
* EJBCA: The OpenSource Certificate Authority *
* *
* This software is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.ejbca.extra.ra;
import java.io.FileInputStream;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Vector;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ejbca.extra.db.Message;
import org.ejbca.extra.db.MessageHome;
import org.ejbca.extra.db.PKCS10Request;
import org.ejbca.extra.db.PKCS10Response;
import org.ejbca.extra.db.PKCS12Request;
import org.ejbca.extra.db.PKCS12Response;
import org.ejbca.extra.db.SubMessages;
import org.ejbca.extra.util.RAKeyStore;
import org.ejbca.util.CertTools;
import org.ejbca.util.CryptoProviderTools;
/**
* A test client for the External RA API that can be used for simple load and performance test and
* to figure out expected response times for different types of requests.
*
* Important, if signing is used must the RA server key store be configured as an 'Super
* Administrator' in EJBCA.
*
* @version $Id: ExtRATestClient.java 10226 2010-10-19 12:59:56Z anatom $
*/
public class ExtRATestClient {
private static final Log log = LogFactory.getLog(ExtRATestClient.class);
//private static final String TYPE_CERT = "CERT";
private static final String TYPE_KEYSTORE = "KEYSTORE";
private static final String SECURITY_UNSECURED = "UNSECURED";
private static final String SECURITY_SIGNED = "SIGNED";
private static final String SECURITY_ENCRYPTED = "ENCRYPTED";
private static final String SECURITY_SIGNEDENCRYPTED = "SIGNEDENCRYPTED";
private static final int ARG_TYPE = 0;
private static final int ARG_KEYSTOREPATH = 1;
private static final int ARG_PASSWORD = 2;
private static final int ARG_ENCRYPTIONCERT = 3;
private static final int ARG_SECURITYLEVEL = 4;
private static final int ARG_REQUESTSPERMIN = 5;
private static final int ARG_CONCURRENTRAS = 6;
private static final int ARG_WAITTIME = 7;
protected PrivateKey raKey = null;
protected X509Certificate raCert = null;
protected Vector cAChain = null;
protected X509Certificate encCert = null;
protected String securitylevel = SECURITY_UNSECURED;
protected boolean requestKeyStore = false;
protected int reqPerMin = 10;
private int concurrentRAs = 2;
protected int waitTime = 30;
protected int generateUserRequests = 0;
protected SecureRandom random = new SecureRandom();
protected static final String pkcs10_1 =
"MIIBkzCB/QIBADBUMQswCQYDVQQGEwJTRTETMBEGA1UECBMKU29tZS1TdGF0ZTEh"
+"MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ0wCwYDVQQDEwRUZXN0"
+"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDczgi13kcTGTMmOdMU/QzvH6JV"
+"QxL23dqdYpsV//XHO2bjKlgqqc3MpGH4QQkz/80rzFi4EwuqBpOnXo0P09I2jztk"
+"IG4TSM+RwOfvaAMDJ1B6eeih6JX+v0A5PaWJlx1nshUuikcYJK3iNVepy39li0m3"
+"OBwub9NnnVWXuClUGwIDAQABoAAwDQYJKoZIhvcNAQEEBQADgYEAz4NpjNraufWg"
+"ZDv5J1muOHwZvOO9Is1L8WvMLG+jgH8Q2rPpDq8buIIWDy6VK8ghr7xhZzEZznTX"
+"5HLSLB1a6KvktiVSKB0nmAmDU28xXLWWwkA7/68J6DvAipk00bHdxuEJ4+Mg8UJ0"
+"Mr+aXDlmZUfghzlB70dDUy/Np/YJVb8=";
private MessageHome msghome = null;
ExtRATestClient(String[] args) throws Exception {
CryptoProviderTools.installBCProvider();
if(args.length != 8){
log.debug("Number of arguments: " + args.length);
help();
System.exit(-1); // NOPMD, it's not a JEE app
}else{
requestKeyStore = args[ARG_TYPE].equalsIgnoreCase(TYPE_KEYSTORE);
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("external-ra-cli");
msghome = new MessageHome(entityManagerFactory, MessageHome.MESSAGETYPE_EXTRA, true);
securitylevel = args[ARG_SECURITYLEVEL];
if(!securitylevel.equalsIgnoreCase(SECURITY_UNSECURED) &&
!securitylevel.equalsIgnoreCase(SECURITY_SIGNED) &&
!securitylevel.equalsIgnoreCase(SECURITY_ENCRYPTED) &&
!securitylevel.equalsIgnoreCase(SECURITY_SIGNEDENCRYPTED)){
throw new Exception("Invalid SecurityLevel: "+securitylevel);
}
if(securitylevel.equalsIgnoreCase(SECURITY_SIGNED) || securitylevel.equalsIgnoreCase(SECURITY_SIGNEDENCRYPTED)){
RAKeyStore rakeystore = new RAKeyStore(args[ARG_KEYSTOREPATH], args[ARG_PASSWORD]);
Certificate[] chain = rakeystore.getKeyStore().getCertificateChain(rakeystore.getAlias());
cAChain = new Vector();
for(int i=0; i< chain.length ; i++){
if(((X509Certificate) chain[i]).getBasicConstraints() != -1){
cAChain.add(chain[i]);
}
}
raKey = (PrivateKey) rakeystore.getKeyStore().getKey(rakeystore.getAlias(), args[ARG_PASSWORD].toCharArray());
raCert = (X509Certificate) rakeystore.getKeyStore().getCertificate(rakeystore.getAlias());
}
if(securitylevel.equalsIgnoreCase(SECURITY_ENCRYPTED) || securitylevel.equalsIgnoreCase(SECURITY_SIGNEDENCRYPTED)){
CertificateFactory cf = CertTools.getCertificateFactory();
encCert = (X509Certificate) cf.generateCertificate(new FileInputStream(args[ARG_ENCRYPTIONCERT]));
}
reqPerMin = Integer.parseInt(args[ARG_REQUESTSPERMIN]);
concurrentRAs = Integer.parseInt(args[ARG_CONCURRENTRAS]);
waitTime = Integer.parseInt(args[ARG_WAITTIME]);
}
}
public static void main(String[] args) throws Exception {
ExtRATestClient testclient = new ExtRATestClient(args);
testclient.run();
}
public void help(){
System.out.println("External RA API Test Client");
System.out.println("Usage : <CERT | KEYSTORE> <KeyStorePath> <KeyStorePwd> <EncCert> <SecurityLevel> <RequestsPerMin> <ConcurrentRAs> <WaitTime>");
System.out.println("Where :");
System.out.println(" <CERT | KEYSTORE> : Type of test, CERT creates single PKCS10 requests, KEYSTORE creates one PKCS10 and one PKCS12 request for each message" );
System.out.println(" <KeyStorePath> : The path to the keystore used to sign/encrypt messages. Use NOKEYSTORE for unencrypted security level.");
System.out.println(" <KeyStorePwd> : Password to unlock the keystore,. Use NOPWD for unencrypted security level.");
System.out.println(" <EncCert> : Path to certificate (DER) used to encrypt messages,. Use NOCERT for unencrypted security level.");
System.out.println(" <SecurityLevel> : Security Level, Valid values are " + SECURITY_UNSECURED + ", " + SECURITY_SIGNED + ", " + SECURITY_ENCRYPTED + ", " + SECURITY_SIGNEDENCRYPTED +"");
System.out.println(" <RequestsPerMin> : Requests to generate every minute per concurrent RA.");
System.out.println(" <ConcurrentRAs> : Number of concurrent RAs that will create requests.");
System.out.println(" <WaitTime> : Number of seconds to wait for answer before exception is thrown.\n\n");
System.out.println("The database connection is configured in META-INF/persistence.xml. The JDBC driver JAR should be copied to the endorsed directory.\n");
System.out.println("Examples : ");
System.out.println(" Simple test sending unsecured requests every 5 s in one thread ");
System.out.println(" expecting an answer within 60 s :");
System.out.println(" java -jar externalra-cli.jar CERT NOKEYSTORE NOPWD NOCERT " + SECURITY_UNSECURED + " 12 1 60 \n");
System.out.println(" Advanced test using encrypted and signed requests for pkcs10 cert and a pkcs12 keystore ");
System.out.println(" every 1 min in two threads, expecting an answer within 60 s :");
System.out.println(" java -jar externalra-cli.jar KEYSTORE rakeystore.p12 foo123 enccert.cer SIGNEDENCRYPTED 1 2 60 \n");
}
/**
* Starts the test application
*/
public void run(){
for(int i=1; i <= concurrentRAs; i++){
ConcurrentRAThread rAThread = new ConcurrentRAThread("Thread-"+i);
println("Starting new Thread : " + "Thread-"+i);
rAThread.start();
}
}
public synchronized void createUser(String username, SubMessages submessages){
msghome.create(username, submessages);
}
public synchronized Message findByUser(String username){
return msghome.findByMessageId(username);
}
public synchronized void println(String message){
System.out.println(message);
System.out.flush();
}
private class ConcurrentRAThread extends Thread { // NOPMD, it's not a JEE app
private boolean run = false;
private String threadName = "";
private long serialNumber = 0;
public ConcurrentRAThread(String threadName) {
this.threadName = threadName;
}
public void run() {
run=true;
while(run) {
try {
// Start Request Thread
RequestThread reqThread = new RequestThread(threadName, serialNumber++);
reqThread.start();
} catch(Exception e) {
e.printStackTrace();
}
try {
sleep(getTimeToNextRequests());
} catch( InterruptedException e) {}
}
}
private long getTimeToNextRequests() {
return (60000 / reqPerMin) + (random.nextLong() % 1000);
}
public void stopThread() {
this.run = false;
}
}
private class RequestThread extends Thread { // NOPMD, it's not a JEE app
private boolean run = false;
private String threadName = "";
private long serialNumber = 0;
public RequestThread(String threadName, long serialNumber) {
this.threadName = threadName;
this.serialNumber = serialNumber;
}
public void run() {
// Generate request
String username = "TEST_" + threadName + "_REQ-" + serialNumber;
long pkcs10RequestId = 0;
long pkcs12RequestId = 0;
long starttime = new Date().getTime();
SubMessages submgs = generateSubMessage();
pkcs10RequestId = createPKCS10Request(username,submgs);
if (requestKeyStore) {
pkcs12RequestId = createPKCS12Request(username,submgs);
}
createUser(username, submgs);
run=true;
// Wait for response
boolean processed = false;
Message msg = null;
int wait = waitTime;
while (wait >= 0 && run) {
msg = findByUser(username);
if (msg != null && msg.getStatus().equals(Message.STATUS_PROCESSED)) {
processed = true;
break;
}
try {
sleep(1000);
} catch (InterruptedException e) {
}
wait--;
}
if (!processed) {
println("Error : Couldn't get processed response within the specified waitTime : Username :" + username + ", WaitTime : " + waitTime);
} else {
SubMessages respmsgs = null;
if (raKey != null) {
respmsgs = msg.getSubMessages(raKey,cAChain,null);
} else {
respmsgs = msg.getSubMessages(null,null,null);
}
PKCS10Response pkcs10resp = (PKCS10Response) respmsgs.getSubMessages().get(0);
PKCS12Response pkcs12resp = null;
if (requestKeyStore) {
pkcs12resp = (PKCS12Response) respmsgs.getSubMessages().get(1);
}
if (pkcs10resp.getRequestId() != pkcs10RequestId) {
println("Error in PKCS10 Request requestId doesn't match responseId for user : " + username + ", request Id : " + pkcs10RequestId + " = " + pkcs10resp.getRequestId());
}
if (requestKeyStore && pkcs12resp.getRequestId() != pkcs12RequestId) {
println("Error in PKCS12 Request requestId doesn't match responseId for user : " + username + ", request Id : " + pkcs12RequestId + " = " + pkcs12resp.getRequestId());
}
if (!pkcs10resp.isSuccessful()) {
println("Error in PKCS10 Request for user : " + username + ", message : " + pkcs10resp.getFailInfo());
}
if (requestKeyStore && !pkcs12resp.isSuccessful()) {
println("Error in PKCS12 Request for user : " + username + ", message : " + pkcs12resp.getFailInfo());
}
long endtime = new Date().getTime();
float processtime = ((float) (endtime - starttime)) / 1000;
if (pkcs10resp.isSuccessful() && !requestKeyStore) {
println(" " + username + " Generated Sucessfully in " + processtime + " seconds, Total Requests " + ++generateUserRequests);
}
if (requestKeyStore && pkcs10resp.isSuccessful() && pkcs12resp.isSuccessful()) {
println(" " + username + " Generated Sucessfully in " + processtime + " seconds, Total Requests " + ++generateUserRequests);
}
}
}
private long createPKCS10Request(String username, SubMessages submessages) {
long requestId = random.nextLong();
submessages.addSubMessage(new PKCS10Request(requestId,username, "CN=PKCS10REQ", "RFC822NAME=PKCS10Request@test.com",
"PKCS10Request@test.com", null, "EMPTY", "ENDUSER", "AdminCA1",pkcs10_1));
return requestId;
}
private long createPKCS12Request(String username, SubMessages submessages) {
long requestId = random.nextLong();
submessages.addSubMessage(new PKCS12Request(requestId,username, "CN=PKCS12REQ", "RFC822NAME=PKCS12Request@test.com",
"PKCS12Request@test.com", null, "EMPTY", "ENDUSER",
"AdminCA1","foo123",PKCS12Request.KEYALG_RSA, "1024", true));
return requestId;
}
private SubMessages generateSubMessage() {
if(securitylevel.equalsIgnoreCase(SECURITY_SIGNEDENCRYPTED)){
return new SubMessages(raCert,raKey, encCert);
}
if(securitylevel.equalsIgnoreCase(SECURITY_SIGNED)){
return new SubMessages(raCert,raKey,null);
}
if(securitylevel.equalsIgnoreCase(SECURITY_ENCRYPTED)){
return new SubMessages(null,null,encCert);
}
return new SubMessages(null,null,null);
}
public void stopThread() {
this.run = false;
}
}
}