/*************************************************************************
* *
* 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.cmp;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.KeyPair;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.X509Principal;
import org.cesecore.core.ejb.ca.store.CertificateProfileSessionRemote;
import org.cesecore.core.ejb.ra.raadmin.EndEntityProfileSessionRemote;
import org.ejbca.config.CmpConfiguration;
import org.ejbca.core.ejb.ca.caadmin.CAAdminSessionRemote;
import org.ejbca.core.ejb.ca.caadmin.CaSessionRemote;
import org.ejbca.core.ejb.config.ConfigurationSessionRemote;
import org.ejbca.core.model.AlgorithmConstants;
import org.ejbca.core.model.ca.caadmin.CAInfo;
import org.ejbca.core.model.ca.certificateprofiles.CertificateProfile;
import org.ejbca.core.model.ca.certificateprofiles.CertificateProfileExistsException;
import org.ejbca.core.model.ca.certificateprofiles.EndUserCertificateProfile;
import org.ejbca.core.model.log.Admin;
import org.ejbca.core.model.ra.raadmin.EndEntityProfile;
import org.ejbca.core.model.ra.raadmin.EndEntityProfileExistsException;
import org.ejbca.core.protocol.unid.UnidFnrHandler;
import org.ejbca.util.CertTools;
import org.ejbca.util.CryptoProviderTools;
import org.ejbca.util.InterfaceCache;
import org.ejbca.util.keystore.KeyTools;
import com.novosec.pkix.asn1.cmp.PKIMessage;
/**
* Tests the unid-fnr plugin.
* Read the assert printout {@link #test01()} to understand how to set things up for the test.
*
* @author primelars
* @version $Id: CmpRAUnidTest.java 12052 2011-05-21 07:29:28Z anatom $
*/
public class CmpRAUnidTest extends CmpTestCase {
private static final Logger log = Logger.getLogger(CmpRAUnidTest.class);
private static final String PBEPASSWORD = "password";
private static final String UNIDPREFIX = "1234-5678-";
private static final String CPNAME = UNIDPREFIX+CmpRAUnidTest.class.getName();
private static final String EEPNAME = UNIDPREFIX+CmpRAUnidTest.class.getName();
/**
* SUBJECT_DN of user used in this test, this contains special, escaped,
* characters to test that this works with CMP RA operations
*/
private static final String FNR = "90123456789";
private static final String LRA = "01234";
private static final String SUBJECT_SN = FNR+'-'+LRA;
private static final String SUBJECT_DN = "C=SE,SN="+SUBJECT_SN+",CN=unid-frn";
private final String issuerDN;
private final KeyPair keys;
private final int caid;
private final Admin admin = new Admin(Admin.TYPE_BATCHCOMMANDLINE_USER);
private final X509Certificate cacert;
private final CAAdminSessionRemote caAdminSession = InterfaceCache.getCAAdminSession();
private final CaSessionRemote caSession = InterfaceCache.getCaSession();
private final CertificateProfileSessionRemote certificateProfileSession = InterfaceCache.getCertificateProfileSession();
private final ConfigurationSessionRemote configurationSession = InterfaceCache.getConfigurationSession();
private final EndEntityProfileSessionRemote endEntityProfileSession = InterfaceCache.getEndEntityProfileSession();
public CmpRAUnidTest(String arg0) throws Exception {
super(arg0);
CryptoProviderTools.installBCProvider();
// Try to use AdminCA1 if it exists
final CAInfo adminca1 = this.caAdminSession.getCAInfo(this.admin, "AdminCA1");
if (adminca1 == null) {
final Collection<Integer> caids = this.caSession.getAvailableCAs(this.admin);
final Iterator<Integer> iter = caids.iterator();
int tmp = 0;
while (iter.hasNext()) {
tmp = iter.next().intValue();
if ( tmp!=0 ) {
break;
}
}
this.caid = tmp;
} else {
this.caid = adminca1.getCAId();
}
if (this.caid == 0) {
assertTrue("No active CA! Must have at least one active CA to run tests!", false);
}
final CAInfo cainfo = this.caAdminSession.getCAInfo(this.admin, this.caid);
final Collection<Certificate> certs = cainfo.getCertificateChain();
if (certs.size() > 0) {
final Iterator<Certificate> certiter = certs.iterator();
final Certificate cert = certiter.next();
final String subject = CertTools.getSubjectDN(cert);
if (StringUtils.equals(subject, cainfo.getSubjectDN())) {
// Make sure we have a BC certificate
this.cacert = (X509Certificate) CertTools.getCertfromByteArray(cert.getEncoded());
} else {
this.cacert = null;
}
} else {
this.cacert = null;
log.error("NO CACERT for caid " + this.caid);
}
this.issuerDN = this.cacert.getIssuerDN().getName();
// Configure CMP for this test
updatePropertyOnServer(CmpConfiguration.CONFIG_OPERATIONMODE, "ra");
updatePropertyOnServer(CmpConfiguration.CONFIG_ALLOWRAVERIFYPOPO, "true");
updatePropertyOnServer(CmpConfiguration.CONFIG_RESPONSEPROTECTION, "pbe");
updatePropertyOnServer(CmpConfiguration.CONFIG_RA_AUTHENTICATIONSECRET, PBEPASSWORD);
updatePropertyOnServer(CmpConfiguration.CONFIG_RA_CERTIFICATEPROFILE, "KeyId");
updatePropertyOnServer(CmpConfiguration.CONFIG_RA_ENDENTITYPROFILE, "KeyId");
updatePropertyOnServer(CmpConfiguration.CONFIG_RACANAME, cainfo.getName());
updatePropertyOnServer(CmpConfiguration.CONFIG_CERTREQHANDLER_CLASS, UnidFnrHandler.class.getName());
// Configure a Certificate profile (CmpRA) using ENDUSER as template
if (this.certificateProfileSession.getCertificateProfile(this.admin, CPNAME) == null) {
final CertificateProfile cp = new EndUserCertificateProfile();
try { // TODO: Fix this better
this.certificateProfileSession.addCertificateProfile(this.admin, CPNAME, cp);
} catch (CertificateProfileExistsException e) {
log.error("Certificate profile exists: ", e);
}
}
final int cpId = this.certificateProfileSession.getCertificateProfileId(this.admin, CPNAME);
if (this.endEntityProfileSession.getEndEntityProfile(this.admin, EEPNAME) == null) {
final EndEntityProfile eep = new EndEntityProfile(true);
eep.setValue(EndEntityProfile.AVAILCERTPROFILES, 0, "" + cpId);
try {
this.endEntityProfileSession.addEndEntityProfile(this.admin, EEPNAME, eep);
} catch (EndEntityProfileExistsException e) {
log.error("Could not create end entity profile.", e);
}
}
this.keys = KeyTools.genKeys("512", AlgorithmConstants.KEYALGORITHM_RSA);
}
@Override
public void setUp() throws Exception {
super.setUp();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
@Override
protected void checkDN(String sExpected, X509Name actual) {
final X509Name expected = new X509Name(sExpected);
final Vector<DERObjectIdentifier> expectedOIDs = expected.getOIDs();
final Vector<String> expectedValues = expected.getValues();
final Vector<DERObjectIdentifier> actualOIDs = actual.getOIDs();
final Vector<String> actualValues = actual.getValues();
assertEquals("Not the expected number of elements in the created certificate.", expectedOIDs.size(), actualOIDs.size());
for ( int i=0; i<expectedOIDs.size(); i++ ) {
final DERObjectIdentifier oid = expectedOIDs.get(i);
final int j = actualOIDs.indexOf(oid);
if ( !oid.equals(X509Name.SN) ) {
log.debug("Check that "+oid.getId()+" is OK. Expected '"+expectedValues.get(i)+"'. Actual '"+actualValues.get(j)+"'.");
assertEquals("Not expected "+oid, expectedValues.get(i), actualValues.get(j));
continue;
}
log.debug("Special handling of the SN "+oid.getId()+". Input '"+expectedValues.get(i)+"'. Transformed '"+actualValues.get(j)+"'.");
final String expectedSNPrefix=UNIDPREFIX+LRA;
final String actualSNPrefix=actualValues.get(j).substring(0, expectedSNPrefix.length());
assertEquals("New serial number prefix not as expected.", expectedSNPrefix,actualSNPrefix);
final String actualSNRandom=actualValues.get(j).substring(expectedSNPrefix.length());
assertTrue( "Random in serial number not OK: "+actualSNRandom, Pattern.compile("^\\w{6}$").matcher(actualSNRandom).matches() );
}
}
public void test01() throws Exception {
final Connection connection;
final String host="localhost";
final String user="uniduser";
final String pass="unidpass";
final String name="unid";
try {
connection = DriverManager.getConnection("jdbc:mysql://"+host+":3306/"+name, user, pass);
} catch (SQLException e) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
pw.println();
pw.println("You have not set up a unid-fnr DB properly to run the test.");
pw.println("If you don't bother about it (don't if you don't know what it is) please just ignore this error.");
pw.println("But if you want to run the test please make sure that the mysql unid-fnr DB is set up.");
pw.println("Then execute next line at the mysql prompt:");
pw.println("mysql> grant all on "+name+".* to "+user+"@'"+host+"' identified by '"+pass+"';");
pw.println("And then create the DB:");
pw.println("$ mysqladmin -u"+host+" -u"+user+" -p"+pass+" create "+name+";.");
pw.println("These properties must the also be defined for the jboss data source. The name of the DS must be set in cmp.properties. Not that the datasource must be a 'no-tx-datasource', like OcspDS.");
pw.println("You also have to set the path to the 'mysql.jar' as the 'mysql.lib' system property for the test.");
pw.println("Example how to the test with this property:");
pw.println("ant -Dmysql.lib=/usr/share/java/mysql.jar test:run");
log.error(sw, e);
assertTrue(sw.toString(),false);
return;
}
try {
doTest(connection);
} finally {
connection.close();
}
}
private void doTest(Connection dbConn) throws Exception {
final byte[] nonce = CmpMessageHelper.createSenderNonce();
final byte[] transid = CmpMessageHelper.createSenderNonce();
final int reqId;
final String unid;
{
// In this test SUBJECT_DN contains special, escaped characters to verify
// that that works with CMP RA as well
final PKIMessage one = genCertReq(this.issuerDN, SUBJECT_DN, this.keys, this.cacert, nonce, transid, true, null, null, null, null);
final PKIMessage req = protectPKIMessage(one, false, PBEPASSWORD, CPNAME, 567);
assertNotNull(req);
reqId = req.getBody().getIr().getCertReqMsg(0).getCertReq().getCertReqId().getValue().intValue();
final ByteArrayOutputStream bao = new ByteArrayOutputStream();
final DEROutputStream out = new DEROutputStream(bao);
out.writeObject(req);
final byte[] ba = bao.toByteArray();
// Send request and receive response
final byte[] resp = sendCmpHttp(ba, 200);
checkCmpResponseGeneral(resp, this.issuerDN, SUBJECT_DN, this.cacert, nonce, transid, false, PBEPASSWORD);
final X509Certificate cert = checkCmpCertRepMessage(SUBJECT_DN, this.cacert, resp, reqId);
unid = (String)new X509Principal( cert.getSubjectX500Principal().getEncoded() ).getValues(X509Name.SN).get(0);
log.debug("Unid: "+unid);
}
{
final PreparedStatement ps = dbConn.prepareStatement("select fnr from UnidFnrMapping where unid=?");
ps.setString(1, unid);
final ResultSet result = ps.executeQuery();
assertTrue("Unid '"+unid+"' not found in DB.", result.next());
final String fnr = result.getString(1);
log.debug("FNR read from DB: "+fnr);
assertEquals("Right FNR not found in DB.", FNR, fnr);
}
{
// Send a confirm message to the CA
final String hash = "foo123";
final PKIMessage confirm = genCertConfirm(SUBJECT_DN, this.cacert, nonce, transid, hash, reqId);
assertNotNull(confirm);
final PKIMessage req1 = protectPKIMessage(confirm, false, PBEPASSWORD, 567);
final ByteArrayOutputStream bao = new ByteArrayOutputStream();
final DEROutputStream out = new DEROutputStream(bao);
out.writeObject(req1);
final byte[] ba = bao.toByteArray();
// Send request and receive response
final byte[] resp = sendCmpHttp(ba, 200);
checkCmpResponseGeneral(resp, this.issuerDN, SUBJECT_DN, this.cacert, nonce, transid, false, PBEPASSWORD);
checkCmpPKIConfirmMessage(SUBJECT_DN, this.cacert, resp);
}
}
public void testZZZCleanUp() throws Exception {
log.trace(">testZZZCleanUp");
this.endEntityProfileSession.removeEndEntityProfile(this.admin, EEPNAME);
this.certificateProfileSession.removeCertificateProfile(this.admin, CPNAME);
assertTrue("Unable to clean up properly.", this.configurationSession.restoreConfiguration());
log.trace("<testZZZCleanUp");
}
}