/*************************************************************************
* *
* 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.core.protocol.ws.client;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.List;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.util.encoders.Base64;
import org.ejbca.core.model.ra.UserDataConstants;
import org.ejbca.core.protocol.ws.client.gen.Certificate;
import org.ejbca.core.protocol.ws.client.gen.CertificateResponse;
import org.ejbca.core.protocol.ws.client.gen.EjbcaWS;
import org.ejbca.core.protocol.ws.client.gen.UserDataVOWS;
import org.ejbca.core.protocol.ws.common.CertificateHelper;
import org.ejbca.ui.cli.ErrorAdminCommandException;
import org.ejbca.ui.cli.IAdminCommand;
import org.ejbca.ui.cli.IllegalAdminCommandException;
import org.ejbca.util.CertTools;
import org.ejbca.util.PerformanceTest;
import org.ejbca.util.PerformanceTest.Command;
import org.ejbca.util.PerformanceTest.CommandFactory;
import org.ejbca.util.query.BasicMatch;
/**
* @author Lars Silven, PrimeKey Solutions AB
* @version $Id: StressTestCommand.java 10558 2010-11-18 09:07:34Z anatom $
*/
public class StressTestCommand extends EJBCAWSRABaseCommand implements IAdminCommand {
final static private String USER_NAME_TAG = "<userName>";
final PerformanceTest performanceTest;
enum TestType {
BASIC,
BASICSINGLETRANS,
REVOKE,
REVOKEALOT
}
private class MyCommandFactory implements CommandFactory {
final private String caName;
final private String endEntityProfileName;
final private String certificateProfileName;
final private TestType testType;
final private int maxCertificateSN;
final private String subjectDN;
MyCommandFactory( String _caName, String _endEntityProfileName, String _certificateProfileName,
TestType _testType, int _maxCertificateSN, String _subjectDN ) {
this.testType = _testType;
this.caName = _caName;
this.endEntityProfileName = _endEntityProfileName;
this.certificateProfileName = _certificateProfileName;
this.maxCertificateSN = _maxCertificateSN;
this.subjectDN = _subjectDN;
}
public Command[] getCommands() throws Exception {
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
final EjbcaWS ejbcaWS = getEjbcaRAWSFNewReference();
final JobData jobData = new JobData(this.subjectDN);
switch (this.testType) {
case BASIC:
return new Command[]{
new EditUserCommand(ejbcaWS, this.caName, this.endEntityProfileName, this.certificateProfileName, jobData, true, this.maxCertificateSN),
new Pkcs10RequestCommand(ejbcaWS, kpg.generateKeyPair(), jobData) };
case BASICSINGLETRANS:
return new Command[]{
new CertificateRequestCommand(ejbcaWS, this.caName, this.endEntityProfileName, this.certificateProfileName, jobData, true, this.maxCertificateSN, kpg.generateKeyPair())
};
case REVOKE:
return new Command[]{
new EditUserCommand(ejbcaWS, this.caName, this.endEntityProfileName, this.certificateProfileName, jobData, true, this.maxCertificateSN),
new Pkcs10RequestCommand(ejbcaWS, kpg.generateKeyPair(), jobData),
new FindUserCommand(ejbcaWS, jobData),
new ListCertsCommand(ejbcaWS, jobData),
new RevokeCertCommand(ejbcaWS, jobData),
new EditUserCommand(ejbcaWS, this.caName, this.endEntityProfileName, this.certificateProfileName, jobData, false, -1),
new Pkcs10RequestCommand(ejbcaWS, kpg.generateKeyPair(), jobData) };
case REVOKEALOT:
return new Command[]{
new MultipleCertsRequestsForAUserCommand(ejbcaWS, this.caName, this.endEntityProfileName, this.certificateProfileName, jobData, kpg),
new FindUserCommand(ejbcaWS, jobData),
new ListCertsCommand(ejbcaWS, jobData),
new RevokeCertCommand(ejbcaWS, jobData)//,
};
default:
return null;
}
}
}
class JobData {
String userName;
String passWord;
final String subjectDN;
X509Certificate userCertsToBeRevoked[];
public JobData(String subjectDN) {
this.subjectDN = subjectDN;
}
String getDN() {
return this.subjectDN.replace(USER_NAME_TAG, this.userName);
}
@Override
public String toString() {
return "Username '"+this.userName+"' with password '"+this.passWord+"'.";
}
}
private class BaseCommand {
final protected JobData jobData;
BaseCommand(JobData _jobData) {
this.jobData = _jobData;
}
@Override
public String toString() {
return "Class \'" +this.getClass().getCanonicalName()+"' with this job data: "+ this.jobData.toString();
}
}
private class Pkcs10RequestCommand extends BaseCommand implements Command {
final private EjbcaWS ejbcaWS;
final private PKCS10CertificationRequest pkcs10;
Pkcs10RequestCommand(EjbcaWS _ejbcaWS, KeyPair keys, JobData _jobData) throws Exception {
super(_jobData);
this.pkcs10 = new PKCS10CertificationRequest("SHA1WithRSA", CertTools.stringToBcX509Name("CN=NOUSED"), keys.getPublic(), new DERSet(), keys.getPrivate());
this.ejbcaWS = _ejbcaWS;
}
public boolean doIt() throws Exception {
final CertificateResponse certificateResponse = this.ejbcaWS.pkcs10Request(this.jobData.userName, this.jobData.passWord,
new String(Base64.encode(this.pkcs10.getEncoded())),null,CertificateHelper.RESPONSETYPE_CERTIFICATE);
return checkAndLogCertificateResponse(certificateResponse, this.jobData);
}
public String getJobTimeDescription() {
return "Relative time spent signing certificates";
}
}
/**
* @param certificateResponse
* @throws CertificateException
*/
private boolean checkAndLogCertificateResponse(
final CertificateResponse certificateResponse, final JobData jobData)
throws CertificateException {
boolean ret = false;
final Iterator<X509Certificate> i = (Iterator<X509Certificate>)CertificateFactory.getInstance("X.509").generateCertificates(new ByteArrayInputStream(Base64.decode(certificateResponse.getData()))).iterator();
X509Certificate cert = null;
while ( i.hasNext() ) {
cert = i.next();
}
if ( cert==null ) {
StressTestCommand.this.performanceTest.getLog().error("no certificate generated for user "+jobData.userName);
} else {
final String commonName = CertTools.getPartFromDN(cert.getSubjectDN().getName(), "CN");
if ( commonName.equals(jobData.userName) ) {
StressTestCommand.this.performanceTest.getLog().info("Cert created. Subject DN: \""+cert.getSubjectDN()+"\".");
StressTestCommand.this.performanceTest.getLog().result(CertTools.getSerialNumber(cert));
ret = true;
}
StressTestCommand.this.performanceTest.getLog().error("Cert not created for right user. Username: \""+jobData.userName+"\" Subject DN: \""+cert.getSubjectDN()+"\".");
}
return ret;
}
private class MultipleCertsRequestsForAUserCommand extends BaseCommand implements Command {
final EjbcaWS ejbcaWS;
final String caName;
final String endEntityProfileName;
final String certificateProfileName;
final KeyPairGenerator kpg;
MultipleCertsRequestsForAUserCommand(EjbcaWS _ejbcaWS, String _caName, String _endEntityProfileName, String _certificateProfileName, JobData _jobData, KeyPairGenerator _kpg) throws Exception {
super(_jobData);
this.caName = _caName;
this.endEntityProfileName = _endEntityProfileName;
this.certificateProfileName = _certificateProfileName;
this.kpg = _kpg;
this.ejbcaWS = _ejbcaWS;
}
public boolean doIt() throws Exception {
boolean createUser = true;
for (int i=0; i<50; i++) {
EditUserCommand editUserCommand = new EditUserCommand(this.ejbcaWS, this.caName, this.endEntityProfileName, this.certificateProfileName, this.jobData, createUser, -1);
if (!editUserCommand.doIt()) {
StressTestCommand.this.performanceTest.getLog().error("MultiplePkcs10RequestsCommand failed for "+this.jobData.userName);
return false;
}
createUser = false;
Pkcs10RequestCommand pkcs10RequestCommand = new Pkcs10RequestCommand(this.ejbcaWS, this.kpg.generateKeyPair(), this.jobData);
if (!pkcs10RequestCommand.doIt()) {
StressTestCommand.this.performanceTest.getLog().error("MultiplePkcs10RequestsCommand failed for "+this.jobData.userName);
return false;
}
}
return true;
}
public String getJobTimeDescription() {
return "Relative time spent creating a lot of certificates";
}
}
private class FindUserCommand extends BaseCommand implements Command {
final private EjbcaWS ejbcaWS;
FindUserCommand(EjbcaWS _ejbcaWS, JobData _jobData) throws Exception {
super(_jobData);
this.ejbcaWS = _ejbcaWS;
}
public boolean doIt() throws Exception {
final org.ejbca.core.protocol.ws.client.gen.UserMatch match = new org.ejbca.core.protocol.ws.client.gen.UserMatch();
match.setMatchtype(BasicMatch.MATCH_TYPE_EQUALS);
match.setMatchvalue(this.jobData.getDN());
match.setMatchwith(org.ejbca.util.query.UserMatch.MATCH_WITH_DN);
final List<UserDataVOWS> result = this.ejbcaWS.findUser(match);
if (result.size()<1) {
StressTestCommand.this.performanceTest.getLog().error("No users found for DN \""+this.jobData.getDN()+"\"");
return false;
}
final Iterator<UserDataVOWS> i = result.iterator();
while ( i.hasNext() ) {
final String userName = i.next().getUsername();
if( !userName.equals(this.jobData.userName) ) {
StressTestCommand.this.performanceTest.getLog().error("wrong user name \""+userName+"\" for certificate with DN \""+this.jobData.getDN()+"\"");
return false;
}
}
return true;
}
public String getJobTimeDescription() {
return "Relative time spent looking for user";
}
}
private class ListCertsCommand extends BaseCommand implements Command {
final private EjbcaWS ejbcaWS;
ListCertsCommand(EjbcaWS _ejbcaWS, JobData _jobData) throws Exception {
super(_jobData);
this.ejbcaWS = _ejbcaWS;
}
public boolean doIt() throws Exception {
final List<Certificate> result = this.ejbcaWS.findCerts(this.jobData.userName, true);
final Iterator<Certificate> i = result.iterator();
this.jobData.userCertsToBeRevoked = new X509Certificate[result.size()];
for( int j=0; i.hasNext(); j++ ) {
this.jobData.userCertsToBeRevoked[j] = (X509Certificate)CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(Base64.decode(i.next().getCertificateData())));
}
if ( this.jobData.userCertsToBeRevoked.length < 1 ) {
StressTestCommand.this.performanceTest.getLog().error("no cert found for user "+this.jobData.userName);
return false;
}
return true;
}
public String getJobTimeDescription() {
return "Relative time spent finding certs for user.";
}
}
private class RevokeCertCommand extends BaseCommand implements Command {
final private EjbcaWS ejbcaWS;
RevokeCertCommand(EjbcaWS _ejbcaWS, JobData _jobData) throws Exception {
super(_jobData);
this.ejbcaWS = _ejbcaWS;
}
public boolean doIt() throws Exception {
for (int i=0; i<this.jobData.userCertsToBeRevoked.length; i++) {
this.ejbcaWS.revokeCert(this.jobData.userCertsToBeRevoked[i].getIssuerDN().getName(),
this.jobData.userCertsToBeRevoked[i].getSerialNumber().toString(16),
REVOKATION_REASON_UNSPECIFIED);
}
return true;
}
public String getJobTimeDescription() {
return "Relative time spent revoking certificates.";
}
}
private class EditUserCommand extends BaseCommand implements Command {
final private EjbcaWS ejbcaWS;
final private UserDataVOWS user;
final private boolean doCreateNewUser;
final private int bitsInCertificateSN;
EditUserCommand(EjbcaWS _ejbcaWS, String caName, String endEntityProfileName, String certificateProfileName,
JobData _jobData, boolean _doCreateNewUser, int _bitsInCertificateSN) {
super(_jobData);
this.doCreateNewUser = _doCreateNewUser;
this.ejbcaWS = _ejbcaWS;
this.user = new UserDataVOWS();
this.user.setClearPwd(true);
this.user.setCaName(caName);
this.user.setEmail(null);
this.user.setSubjectAltName(null);
this.user.setStatus(UserDataConstants.STATUS_NEW);
this.user.setTokenType(org.ejbca.core.protocol.ws.objects.UserDataVOWS.TOKEN_TYPE_USERGENERATED);
this.user.setEndEntityProfileName(endEntityProfileName);
this.user.setCertificateProfileName(certificateProfileName);
this.bitsInCertificateSN = _bitsInCertificateSN;
}
public boolean doIt() throws Exception {
if ( this.doCreateNewUser ) {
this.jobData.passWord = "foo123";
this.jobData.userName = "WSTESTUSER"+StressTestCommand.this.performanceTest.nextLong();
}
if ( this.bitsInCertificateSN>0 && this.doCreateNewUser ) {
this.user.setCertificateSerialNumber(new BigInteger(this.bitsInCertificateSN, StressTestCommand.this.performanceTest.getRandom()));
}
this.user.setSubjectDN(this.jobData.getDN());
this.user.setUsername(this.jobData.userName);
this.user.setPassword(this.jobData.passWord);
this.ejbcaWS.editUser(this.user);
return true;
}
public String getJobTimeDescription() {
if ( this.doCreateNewUser ) {
return "Relative time spent registring new users";
}
return "Relative time spent setting status of user to NEW";
}
} // EditUserCommand
/**
* Command for using the "single transaction" certificateRequest method from EjbcaWS
*/
private class CertificateRequestCommand extends BaseCommand implements Command {
final private EjbcaWS ejbcaWS;
final private UserDataVOWS user;
final private boolean doCreateNewUser;
final private int bitsInCertificateSN;
final private PKCS10CertificationRequest pkcs10;
CertificateRequestCommand(EjbcaWS _ejbcaWS, String caName, String endEntityProfileName, String certificateProfileName,
JobData _jobData, boolean _doCreateNewUser, int _bitsInCertificateSN, KeyPair keys) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException {
super(_jobData);
this.doCreateNewUser = _doCreateNewUser;
this.ejbcaWS = _ejbcaWS;
this.user = new UserDataVOWS();
this.user.setClearPwd(true);
this.user.setCaName(caName);
this.user.setEmail(null);
this.user.setSubjectAltName(null);
this.user.setStatus(UserDataConstants.STATUS_NEW);
this.user.setTokenType(org.ejbca.core.protocol.ws.objects.UserDataVOWS.TOKEN_TYPE_USERGENERATED);
this.user.setEndEntityProfileName(endEntityProfileName);
this.user.setCertificateProfileName(certificateProfileName);
this.bitsInCertificateSN = _bitsInCertificateSN;
this.pkcs10 = new PKCS10CertificationRequest("SHA1WithRSA", CertTools.stringToBcX509Name("CN=NOUSED"), keys.getPublic(), new DERSet(), keys.getPrivate());
}
public boolean doIt() throws Exception {
if ( this.doCreateNewUser ) {
this.jobData.passWord = "foo123";
this.jobData.userName = "WSTESTUSER"+StressTestCommand.this.performanceTest.nextLong();
}
if ( this.bitsInCertificateSN>0 && this.doCreateNewUser ) {
this.user.setCertificateSerialNumber(new BigInteger(this.bitsInCertificateSN, StressTestCommand.this.performanceTest.getRandom()));
}
this.user.setSubjectDN(this.jobData.getDN());
this.user.setUsername(this.jobData.userName);
this.user.setPassword(this.jobData.passWord);
int requestType = CertificateHelper.CERT_REQ_TYPE_PKCS10;
String responseType = CertificateHelper.RESPONSETYPE_CERTIFICATE;
String hardTokenSN = null; // not used
String requestData = new String(Base64.encode(this.pkcs10.getEncoded()));
final CertificateResponse certificateResponse = this.ejbcaWS.certificateRequest(this.user, requestData, requestType, hardTokenSN, responseType);
return checkAndLogCertificateResponse(certificateResponse, this.jobData);
}
public String getJobTimeDescription() {
if ( this.doCreateNewUser ) {
return "Relative time spent registring new users";
}
return "Relative time spent setting status of user to NEW";
}
} // CertificateRequestCommand
/**
* @param args
*/
public StressTestCommand(String[] _args) {
super(_args);
this.performanceTest = new PerformanceTest();
}
/* (non-Javadoc)
* @see org.ejbca.core.protocol.ws.client.EJBCAWSRABaseCommand#usage()
*/
@Override
protected void usage() {
getPrintStream().println("Command used to perform a \"stress\" test of EJBCA.");
getPrintStream().println("The command will start up a number of threads.");
getPrintStream().println("Each thread will continuously add new users to EJBCA. After adding a new user the thread will fetch a certificate for it.");
getPrintStream().println();
getPrintStream().println("Usage : stress <caname> <nr of threads> <max wait time in ms to fetch cert after adding user> [<end entity profile name>] [<certificate profile name>] [<type of test>]");
getPrintStream().println();
getPrintStream().println("Here is an example of how the test could be started:");
getPrintStream().println("./ejbcawsracli.sh stress AdminCA1 20 5000");
getPrintStream().println("20 threads is started. After adding a user the thread waits between 0-500 ms before requesting a certificate for it. The certificates will all be signed by the CA AdminCA1.");
getPrintStream().println();
getPrintStream().println("To define a template for the subject DN of each new user use the java system property 'subjectDN'.");
getPrintStream().println("If the property value contains one or several '<userName>' string these strings will be substituted with the user name.");
getPrintStream().println("Example: JAVA_OPT=\"-DsubjectDN=CN=<userName>,O=Acme,UID=hej<userName>,OU=,OU=First Fixed,OU=sfsdf,OU=Middle Fixed,OU=fsfsd,OU=Last Fixed\" ../../PWE/ejbca_3_11/dist/clientToolBox/ejbcaClientToolBox.sh EjbcaWsRaCli stress ldapDirect 1 1000 ldapClientOUTest ldapClientDirect");
getPrintStream().print("Types of stress tests:");
TestType testTypes[] = TestType.values();
for ( TestType testType : testTypes ) {
getPrintStream().print(" " + testType);
}
getPrintStream().println();
}
/* (non-Javadoc)
* @see org.ejbca.ui.cli.IAdminCommand#execute()
*/
public void execute() throws IllegalAdminCommandException, ErrorAdminCommandException {
try {
if(this.args.length < 2){
usage();
System.exit(-1); // NOPMD, this is not a JEE app
}
final int numberOfThreads = this.args.length>2 ? Integer.parseInt(this.args[2]) : 1;
final int waitTime = this.args.length>3 ? Integer.parseInt(this.args[3]) : -1;
final String caName = this.args[1];
final String endEntityProfileName = this.args.length>4 ? this.args[4] : "EMPTY";
final String certificateProfileName = this.args.length>5 ? this.args[5] : "ENDUSER";
final TestType testType = this.args.length>6 ? TestType.valueOf(this.args[6]) : TestType.BASIC;
final int maxCertificateSN;
final String subjectDN = System.getProperty("subjectDN", "CN="+USER_NAME_TAG);
{
final String sTmp = System.getProperty("maxCertSN");
int iTmp;
try {
iTmp = sTmp!=null && sTmp.length()>0 ? Integer.parseInt(sTmp) : -1;
} catch ( NumberFormatException e ) {
iTmp = -1;
}
maxCertificateSN = iTmp;
}
this.performanceTest.execute(new MyCommandFactory(caName, endEntityProfileName, certificateProfileName, testType, maxCertificateSN, subjectDN),
numberOfThreads, waitTime, getPrintStream());
getPrintStream().println("A test key for each thread is generated. This could take some time if you have specified many threads and long keys.");
synchronized(this) {
wait();
}
} catch( InterruptedException e) {
// do nothing since user wants to exit.
} catch( Exception e) {
throw new ErrorAdminCommandException(e);
}finally{
this.performanceTest.getLog().close();
}
}
}