/************************************************************************* * * * EJBCA Community: 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.security.KeyPair; import java.security.PrivateKey; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Random; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.cmp.CMPCertificate; import org.bouncycastle.asn1.cmp.CertRepMessage; import org.bouncycastle.asn1.cmp.CertResponse; import org.bouncycastle.asn1.cmp.CertifiedKeyPair; import org.bouncycastle.asn1.cmp.PKIBody; import org.bouncycastle.asn1.cmp.PKIFailureInfo; import org.bouncycastle.asn1.cmp.PKIHeader; import org.bouncycastle.asn1.cmp.PKIHeaderBuilder; import org.bouncycastle.asn1.cmp.PKIMessage; import org.bouncycastle.asn1.crmf.CertReqMessages; import org.bouncycastle.asn1.crmf.EncryptedValue; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x9.X962Parameters; import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; import org.bouncycastle.cms.CMSSignedGenerator; import org.bouncycastle.jce.X509KeyUsage; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.AsymmetricKeyUnwrapper; import org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper; import org.bouncycastle.operator.jcajce.JceInputDecryptorProviderBuilder; import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.bouncycastle.util.Arrays; import org.cesecore.CaTestUtils; import org.cesecore.certificates.ca.CA; import org.cesecore.certificates.ca.CAConstants; import org.cesecore.certificates.ca.CaSessionRemote; import org.cesecore.certificates.ca.X509CAInfo; import org.cesecore.certificates.ca.catoken.CAToken; import org.cesecore.certificates.ca.extendedservices.ExtendedCAServiceInfo; import org.cesecore.certificates.certificate.InternalCertificateStoreSessionRemote; import org.cesecore.certificates.certificateprofile.CertificateProfile; import org.cesecore.certificates.certificateprofile.CertificateProfileConstants; import org.cesecore.certificates.crl.RevokedCertInfo; import org.cesecore.certificates.endentity.EndEntityConstants; import org.cesecore.certificates.endentity.EndEntityInformation; import org.cesecore.certificates.endentity.EndEntityType; import org.cesecore.certificates.endentity.EndEntityTypes; import org.cesecore.certificates.util.AlgorithmConstants; import org.cesecore.certificates.util.AlgorithmTools; import org.cesecore.configuration.GlobalConfigurationSessionRemote; import org.cesecore.keys.token.CryptoTokenTestUtils; import org.cesecore.keys.util.KeyTools; import org.cesecore.util.Base64; import org.cesecore.util.CertTools; import org.cesecore.util.CryptoProviderTools; import org.cesecore.util.EjbRemoteHelper; import org.cesecore.util.StringTools; import org.ejbca.config.CmpConfiguration; import org.ejbca.core.ejb.ca.caadmin.CAAdminSessionRemote; import org.ejbca.core.ejb.ra.EndEntityAccessSessionRemote; import org.ejbca.core.ejb.ra.EndEntityExistsException; import org.ejbca.core.ejb.ra.EndEntityManagementSessionRemote; import org.ejbca.core.ejb.ra.NoSuchEndEntityException; import org.ejbca.core.model.SecConst; import org.ejbca.core.model.ca.caadmin.extendedcaservices.KeyRecoveryCAServiceInfo; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; /** * This test runs in CMP client mode. * * You can run this test against a CMP Proxy instead of directly to the CA by setting the system property httpCmpProxyURL, * for example "-DhttpCmpProxyURL=http://proxy-ip:8080/cmpProxy-6.4.0", which can be set in Run Configurations if running the * test from Eclipse. * * @version $Id: CrmfRequestTest.java 30985 2019-01-04 14:36:16Z samuellb $ */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class CrmfRequestTest extends CmpTestCase { private static final Logger log = Logger.getLogger(CrmfRequestTest.class); private static final String USER = "abc123rry" + new Random().nextLong(); private final static X500Name USER_DN = new X500Name("CN=" + USER + ", O=PrimeKey Solutions AB, C=SE"); private final static String ISSUER_DN = "CN=TestCA"; private final KeyPair keys; private final int caid; private final X509Certificate cacert; private final CA testx509ca; private final CmpConfiguration cmpConfiguration; private final static String cmpAlias = "CrmfRequestTestCmpConfigAlias"; private final CaSessionRemote caSession = EjbRemoteHelper.INSTANCE.getRemoteSession(CaSessionRemote.class); private final EndEntityManagementSessionRemote endEntityManagementSession = EjbRemoteHelper.INSTANCE.getRemoteSession(EndEntityManagementSessionRemote.class); private final GlobalConfigurationSessionRemote globalConfigurationSession = EjbRemoteHelper.INSTANCE.getRemoteSession(GlobalConfigurationSessionRemote.class); private final InternalCertificateStoreSessionRemote internalCertStoreSession = EjbRemoteHelper.INSTANCE.getRemoteSession(InternalCertificateStoreSessionRemote.class, EjbRemoteHelper.MODULE_TEST); @BeforeClass public static void beforeClass() { CryptoProviderTools.installBCProviderIfNotAvailable(); } public CrmfRequestTest() throws Exception { this.cmpConfiguration = (CmpConfiguration) this.globalConfigurationSession.getCachedConfiguration(CmpConfiguration.CMP_CONFIGURATION_ID); int keyusage = X509KeyUsage.digitalSignature + X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign; this.testx509ca = CaTestUtils.createTestX509CA(ISSUER_DN, null, false, keyusage); this.caid = this.testx509ca.getCAId(); this.cacert = (X509Certificate) this.testx509ca.getCACertificate(); this.keys = KeyTools.genKeys("512", AlgorithmConstants.KEYALGORITHM_RSA); } @Override @Before public void setUp() throws Exception { super.setUp(); this.caSession.addCA(ADMIN, this.testx509ca); log.debug("ISSUER_DN: " + ISSUER_DN); log.debug("caid: " + this.caid); this.cmpConfiguration.addAlias(cmpAlias); this.cmpConfiguration.setRAMode(cmpAlias, false); this.cmpConfiguration.setResponseProtection(cmpAlias, "signature"); this.cmpConfiguration.setCMPDefaultCA(cmpAlias, ISSUER_DN); this.cmpConfiguration.setAuthenticationModule(cmpAlias, CmpConfiguration.AUTHMODULE_REG_TOKEN_PWD + ";" + CmpConfiguration.AUTHMODULE_HMAC); this.cmpConfiguration.setAuthenticationParameters(cmpAlias, "-;foo123"); this.cmpConfiguration.setExtractUsernameComponent(cmpAlias, "CN"); this.cmpConfiguration.setRACertProfile(cmpAlias, CP_DN_OVERRIDE_NAME); this.cmpConfiguration.setRAEEProfile(cmpAlias, String.valueOf(eepDnOverrideId)); this.globalConfigurationSession.saveConfiguration(ADMIN, this.cmpConfiguration); } @Override @After public void tearDown() throws Exception { super.tearDown(); CryptoTokenTestUtils.removeCryptoToken(null, this.testx509ca.getCAToken().getCryptoTokenId()); this.caSession.removeCA(ADMIN, this.caid); try { this.endEntityManagementSession.deleteUser(ADMIN, "cmptest"); } catch (NoSuchEndEntityException e) { // A test probably failed before creating the entity log.debug("Failed to delete USER \"cmptest\"."); } this.cmpConfiguration.removeAlias(cmpAlias); this.globalConfigurationSession.saveConfiguration(ADMIN, this.cmpConfiguration); } @Override public String getRoleName() { return this.getClass().getSimpleName(); } @Test public void test01CrmfHttpUnknowUser() throws Exception { log.trace(">test01CrmfHttpUnknowUser"); byte[] nonce = CmpMessageHelper.createSenderNonce(); byte[] transid = CmpMessageHelper.createSenderNonce(); // USER_DN = USER_DN + ", serialNumber=01234567"; PKIMessage req = genCertReq(ISSUER_DN, USER_DN, this.keys, this.cacert, nonce, transid, false, null, new Date(), new Date(), null, null, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, USER_DN, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); // Expect a CertificateResponse (reject) message with error FailInfo.INCORRECT_DATA checkCmpFailMessage(resp, "Wrong username or password", 1, reqId, 7, PKIFailureInfo.incorrectData); log.trace(" signCertColl = new ArrayList<>(); signCertColl.add(signCert); CmpMessageHelper.signPKIMessage(req, signCertColl, this.keys.getPrivate(), CMSSignedGenerator.DIGEST_SHA1, BouncyCastleProvider.PROVIDER_NAME); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, USER_DN, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); // Expect a CertificateResponse (reject) message with error FailInfo.INCORRECT_DATA checkCmpFailMessage(resp, "Wrong username or password", 1, reqId, 7, PKIFailureInfo.incorrectData); } @Test public void test03CrmfHttpOkUser() throws Exception { log.trace(">test03CrmfHttpOkUser"); // Create a new good USER X500Name userDN = createCmpUser("cmptest", "foo123", "C=SE,O=PrimeKey,CN=cmptest", true, this.caid, -1, -1); byte[] nonce = CmpMessageHelper.createSenderNonce(); byte[] transid = CmpMessageHelper.createSenderNonce(); PKIMessage req = genCertReq(ISSUER_DN, userDN, this.keys, this.cacert, nonce, transid, false, null, null, null, null, null, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); X509Certificate cert = checkCmpCertRepMessage(userDN, this.cacert, resp, reqId); String altNames = CertTools.getSubjectAlternativeName(cert); assertNull("AltNames was not null (" + altNames + ").", altNames); // Send a confirm message to the CA String hash = "foo123"; PKIMessage confirm = genCertConfirm(userDN, this.cacert, nonce, transid, hash, reqId); ba = CmpMessageHelper.pkiMessageToByteArray(confirm); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN, this.cacert, nonce, transid, false, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); checkCmpPKIConfirmMessage(userDN, this.cacert, resp); // Now revoke the bastard! PKIMessage rev = genRevReq(ISSUER_DN, userDN, cert.getSerialNumber(), this.cacert, nonce, transid, true, null, null); byte[] barev = CmpMessageHelper.pkiMessageToByteArray(rev); // Send request and receive response resp = sendCmpHttp(barev, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN, this.cacert, nonce, transid, false, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); checkCmpFailMessage(resp, "PKI Message is not authenticated properly. No HMAC protection was found.", PKIBody.TYPE_ERROR, reqId, PKIFailureInfo.badRequest, PKIFailureInfo.incorrectData); // // Try again, this time setting implicitConfirm in the header, expecting the server to reply with implicitConfirm as well userDN = createCmpUser("cmptest", "foo123", "C=SE,O=PrimeKey,CN=cmptest", true, this.caid, -1, -1); nonce = CmpMessageHelper.createSenderNonce(); transid = CmpMessageHelper.createSenderNonce(); DEROctetString keyId = new DEROctetString("primekey".getBytes()); req = genCertReq(ISSUER_DN, userDN, this.keys, this.cacert, nonce, transid, false, null, null, null, null, null, keyId, true); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), true, "primekey"); cert = checkCmpCertRepMessage(userDN, this.cacert, resp, reqId); altNames = CertTools.getSubjectAlternativeName(cert); assertNull("AltNames was not null (" + altNames + ").", altNames); log.trace("test04BlueXCrmf"); // An EE with a matching subject and clear text password set to "foo123" must exist for HMAC validation in this test. // foo123 is not the correct password however, so we will fail HMAC verification final String username = "Some Common Name"; try { super.createCmpUser(username, "password", "CN=Some Common Name", false, this.caid, -1, -1); byte[] resp = sendCmpHttp(bluexir, 200, cmpAlias); assertNotNull(resp); // In this very old BlueX message, POP verification fails. // The HMAC password used to protect the request is 'password', which is set on the CMP user "Some Common Name" above checkCmpPKIErrorMessage(resp, "C=NL,O=A.E.T. Europe B.V.,OU=Development,CN=Test CA 1", new X500Name(new RDN[0]), PKIFailureInfo.badPOP, null); // expecting a bad_pop } finally { endEntityManagementSession.deleteUser(ADMIN, username); } try { super.createCmpUser(username, "foo123", "CN=Some Common Name", false, this.caid, -1, -1); byte[] resp = sendCmpHttp(bluexir, 200, cmpAlias); assertNotNull(resp); // If we don't know the HMAC password, the below error will be instead checkCmpPKIErrorMessage(resp, "C=NL,O=A.E.T. Europe B.V.,OU=Development,CN=Test CA 1", new X500Name(new RDN[0]), PKIFailureInfo.badRequest, null); // expecting a bad_pop } finally { endEntityManagementSession.deleteUser(ADMIN, username); } log.trace("test05BadBytes"); byte[] msg = bluexir; // Change some bytes to make the message bad msg[10] = 0; msg[15] = 0; msg[22] = 0; msg[56] = 0; msg[88] = 0; /* Before EJBCA 6.8.0 we responded with HTTP 400, but now we send a PKIFailureInfo.badRequest instead. */ byte[] resp = sendCmpHttp(msg, 200, cmpAlias); assertNotNull(resp); checkCmpFailMessage(resp, "Not a valid CMP message.", PKIBody.TYPE_ERROR, 123, PKIFailureInfo.badRequest, PKIFailureInfo.incorrectData); log.trace("test07SignedConfirmationMessage()"); CmpConfirmResponseMessage cmpConfRes = new CmpConfirmResponseMessage(); cmpConfRes.setSignKeyInfo(this.testx509ca.getCertificateChain(), this.keys.getPrivate(), null); cmpConfRes.setSender(new GeneralName(USER_DN)); cmpConfRes.setRecipient(new GeneralName(new X500Name("CN=cmpRecipient, O=TEST"))); cmpConfRes.setSenderNonce("DAxFSkJDQSBTYW=="); cmpConfRes.setRecipientNonce("DAxFSkJDQSBTYY=="); cmpConfRes.setTransactionId("MTMzNwo="); cmpConfRes.create(); byte[] resp = cmpConfRes.getResponseMessage(); PKIMessage msg = PKIMessage.getInstance(ASN1Primitive.fromByteArray(resp)); boolean veriStatus = CmpMessageHelper.verifyCertBasedPKIProtection(msg, this.keys.getPublic()); assertTrue("Verification failed.", veriStatus); log.trace("testUnsignedConfirmationMessage()"); CmpConfirmResponseMessage cmpConfRes = new CmpConfirmResponseMessage(); //cmpConfRes.setSignKeyInfo(this.testx509ca.getCertificateChain(), this.keys.getPrivate(), null); cmpConfRes.setSender(new GeneralName(USER_DN)); cmpConfRes.setRecipient(new GeneralName(new X500Name("CN=cmpRecipient, O=TEST"))); cmpConfRes.setSenderNonce("DAxFSkJDQSBTYW=="); cmpConfRes.setRecipientNonce("DAxFSkJDQSBTYY=="); cmpConfRes.setTransactionId("MTMzNwo="); cmpConfRes.create(); byte[] resp = cmpConfRes.getResponseMessage(); PKIMessage msg = PKIMessage.getInstance(ASN1Primitive.fromByteArray(resp)); try { CmpMessageHelper.verifyCertBasedPKIProtection(msg, this.keys.getPublic()); fail("Attempting to verify signature on an unsigned message should have failed."); } catch (SignatureException e) { log.debug("Expected exception: " + e.getMessage()); } log.trace("test08SubjectDNSerialnumber"); // Create a new good USER String cmpsntestUsername = "cmpsntest"; String cmpsntest2Username = "cmpsntest2"; final X500Name userDN1 = createCmpUser(cmpsntestUsername, "foo123", "C=SE,SN=12234567,CN=cmpsntest", true, this.caid, -1, -1); try { byte[] nonce = CmpMessageHelper.createSenderNonce(); byte[] transid = CmpMessageHelper.createSenderNonce(); PKIMessage req = genCertReq(ISSUER_DN, userDN1, this.keys, this.cacert, nonce, transid, false, null, null, null, null, null, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN1, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); X509Certificate cert = checkCmpCertRepMessage(userDN1, this.cacert, resp, reqId); // Now revoke the certificate! PKIMessage rev = genRevReq(ISSUER_DN, userDN1, cert.getSerialNumber(), this.cacert, nonce, transid, true, null, null); assertNotNull(rev); rev = protectPKIMessage(rev, false, "foo123", 567); assertNotNull(rev); byte[] barev = CmpMessageHelper.pkiMessageToByteArray(rev); // Send request and receive response resp = sendCmpHttp(barev, 200,cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN1, this.cacert, nonce, transid, false, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); int revStatus = checkRevokeStatus(ISSUER_DN, CertTools.getSerialNumber(cert)); assertNotEquals("Revocation request failed to revoke the certificate", RevokedCertInfo.NOT_REVOKED, revStatus); // Create another USER with the subjectDN serialnumber spelled "SERIALNUMBER" instead of "SN" KeyPair keys2 = KeyTools.genKeys("512", AlgorithmConstants.KEYALGORITHM_RSA); final X500Name userDN2 = createCmpUser(cmpsntest2Username, "foo123", "C=SE,SERIALNUMBER=123456789,CN=cmpsntest2", true, this.caid, -1, -1); req = genCertReq(ISSUER_DN, userDN2, keys2, this.cacert, nonce, transid, false, null, null, null, null, null, null); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN2, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); cert = checkCmpCertRepMessage(userDN2, this.cacert, resp, reqId); // Now revoke this certificate too rev = genRevReq(ISSUER_DN, userDN2, cert.getSerialNumber(), this.cacert, nonce, transid, true, null, null); assertNotNull(rev); rev = protectPKIMessage(rev, false, "foo123", 567); assertNotNull(rev); barev = CmpMessageHelper.pkiMessageToByteArray(rev); // Send request and receive response resp = sendCmpHttp(barev, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, userDN2, this.cacert, nonce, transid, false, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); revStatus = checkRevokeStatus(ISSUER_DN, CertTools.getSerialNumber(cert)); assertNotEquals("Revocation request failed to revoke the certificate", RevokedCertInfo.NOT_REVOKED, revStatus); } finally { this.endEntityManagementSession.deleteUser(ADMIN, cmpsntestUsername); this.endEntityManagementSession.deleteUser(ADMIN, cmpsntest2Username); } log.trace("test09KeyIdTest()"); DEROctetString octs = new DEROctetString("foo123".getBytes()); String keyid = CmpMessageHelper.getStringFromOctets(octs); assertEquals("foo123", keyid); PKIHeaderBuilder headerbuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, new GeneralName(new X500Name("CN=Sender")), new GeneralName(new X500Name("CN=Recipient"))); headerbuilder.setSenderKID(new DEROctetString("foo123".getBytes())); PKIHeader header = headerbuilder.build(); keyid = CmpMessageHelper.getStringFromOctets(header.getSenderKID()); assertEquals("foo123", keyid); log.trace("test10EscapedCharsInDN"); this.cmpConfiguration.setExtractUsernameComponent(cmpAlias, "DN"); this.globalConfigurationSession.saveConfiguration(ADMIN, this.cmpConfiguration); byte[] nonce = CmpMessageHelper.createSenderNonce(); byte[] transid = CmpMessageHelper.createSenderNonce(); // --------------- Send a CRMF request with the whole DN as username with escapable characters --------------- // final String sRequestName = "CN=another\0nullguy%00"; // Create a new good USER final X500Name requestName = createCmpUser(sRequestName, "foo123", sRequestName, false, this.caid, -1, -1); try { PKIMessage req = genCertReq(ISSUER_DN, requestName, this.keys, this.cacert, nonce, transid, false, null, null, null, null, null, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, requestName, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); X509Certificate cert = checkCmpCertRepMessage(new X500Name(StringTools.strip(sRequestName)), this.cacert, resp, reqId); assertNotNull(cert); // Now revoke the bastard! PKIMessage rev = genRevReq(ISSUER_DN, requestName, cert.getSerialNumber(), this.cacert, nonce, transid, true, null, null); assertNotNull(rev); rev = protectPKIMessage(rev, false, "foo123", 567); byte[] barev = CmpMessageHelper.pkiMessageToByteArray(rev); // Send request and receive response resp = sendCmpHttp(barev, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, requestName, this.cacert, nonce, transid, false, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); int revStatus = checkRevokeStatus(ISSUER_DN, CertTools.getSerialNumber(cert)); assertNotEquals("Revocation request failed to revoke the certificate", RevokedCertInfo.NOT_REVOKED, revStatus); } finally { String escapedName = StringTools.stripUsername(sRequestName); try { this.endEntityManagementSession.deleteUser(ADMIN, escapedName); } catch (NoSuchEndEntityException e) { // A test probably failed before creating the entity log.debug("Failed to delete USER: " + escapedName); } } // --------------- Send a CRMF request with a username with escapable characters --------------- // final String username = "another\0nullguy%00"; final String sDN = "CN=" + username + ", C=SE, O=hejsan"; KeyPair key2 = KeyTools.genKeys("512", AlgorithmConstants.KEYALGORITHM_RSA); // Create a new good USER final X500Name dn = createCmpUser(username, "foo123", sDN, false, this.caid, -1, -1); try { PKIMessage req = genCertReq(ISSUER_DN, dn, key2, this.cacert, nonce, transid, false, null, null, null, null, null, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, dn, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); X509Certificate cert = checkCmpCertRepMessage(dn, this.cacert, resp, reqId); assertNotNull(cert); // Now revoke the bastard! PKIMessage rev = genRevReq(ISSUER_DN, dn, cert.getSerialNumber(), this.cacert, nonce, transid, true, null, null); assertNotNull(rev); rev = protectPKIMessage(rev, false, "foo123", 567); byte[] barev = CmpMessageHelper.pkiMessageToByteArray(rev); // Send request and receive response resp = sendCmpHttp(barev, 200, cmpAlias); checkCmpResponseGeneral(resp, ISSUER_DN, dn, this.cacert, nonce, transid, false, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); int revStatus = checkRevokeStatus(ISSUER_DN, CertTools.getSerialNumber(cert)); assertNotEquals("Revocation request failed to revoke the certificate", RevokedCertInfo.NOT_REVOKED, revStatus); } finally { String escapedName = StringTools.strip(username); try { this.endEntityManagementSession.deleteUser(ADMIN, escapedName); } catch (NoSuchEndEntityException e) { // A test probably failed before creating the entity log.debug("Failed to delete USER: " + escapedName); } } log.trace(" extendedCaServices = new ArrayList(2); extendedCaServices.add(new KeyRecoveryCAServiceInfo(ExtendedCAServiceInfo.STATUS_ACTIVE)); String caname = CertTools.getPartFromDN(subcaDN, "CN"); boolean ldapOrder = !CertTools.isDNReversed(subcaDN); X509CAInfo cainfo = new X509CAInfo(subcaDN, caname, CAConstants.CA_ACTIVE, CertificateProfileConstants.CERTPROFILE_FIXED_SUBCA, "3650d", this.caid, this.testx509ca.getCertificateChain(), catoken); cainfo.setDescription("JUnit RSA SubCA"); cainfo.setExtendedCAServiceInfos(extendedCaServices); cainfo.setUseLdapDnOrder(ldapOrder); cainfo.setCmpRaAuthSecret("foo123"); CAAdminSessionRemote caAdminSession = EjbRemoteHelper.INSTANCE.getRemoteSession(CAAdminSessionRemote.class); caAdminSession.createCA(ADMIN, cainfo); assertTrue(this.caSession.existsCa(subcaID)); cainfo = (X509CAInfo) this.caSession.getCAInfo(ADMIN, subcaID); X509Certificate subcaCert = (X509Certificate) cainfo.getCertificateChain().iterator().next(); // --------- Create a user ----------------- // final X500Name userDN = new X500Name("C=SE,O=PrimeKey,CN=cmptest"); EndEntityInformation user = new EndEntityInformation("cmptest", userDN.toString(), subcaID, null, "cmptest@primekey.se", new EndEntityType(EndEntityTypes.ENDUSER), // EndEntityConstants.EMPTY_END_ENTITY_PROFILE, CertificateProfileConstants.CERTPROFILE_FIXED_ENDUSER, this.eepDnOverrideId, this.cpDnOverrideId, SecConst.TOKEN_SOFT_PEM, 0, null); user.setPassword("foo123"); try { this.endEntityManagementSession.addUser(ADMIN, user, true); log.debug("created user: cmptest, foo123, " + userDN); } catch (EndEntityExistsException e) { log.debug("User cmptest already exists."); this.endEntityManagementSession.changeUser(ADMIN, user, true); this.endEntityManagementSession.setUserStatus(ADMIN, "cmptest", EndEntityConstants.STATUS_NEW); log.debug("Reset status to NEW"); } assertTrue(this.endEntityManagementSession.existsUser("cmptest")); EndEntityAccessSessionRemote eeAccessSession = EjbRemoteHelper.INSTANCE.getRemoteSession(EndEntityAccessSessionRemote.class); EndEntityInformation ee = eeAccessSession.findUser(ADMIN, "cmptest"); assertEquals(subcaID, ee.getCAId()); // -------- generate and send a CMP request -------------- // byte[] nonce = CmpMessageHelper.createSenderNonce(); byte[] transid = CmpMessageHelper.createSenderNonce(); PKIMessage req = genCertReq(subcaDN, userDN, this.keys, subcaCert, nonce, transid, false, null, null, null, null, null, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); checkCmpResponseGeneral(resp, subcaDN, userDN, subcaCert, nonce, transid, true, null, PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); final X509Certificate cert = checkCmpCertRepMessage(userDN, subcaCert, resp, reqId); assertNotNull(cert); // ------- Check that the entire certificate chain is in the extraCerts field in the response PKIMessage respMsg = PKIMessage.getInstance(resp); assertNotNull(respMsg); CMPCertificate[] certChain = respMsg.getExtraCerts(); assertEquals(2, certChain.length); assertEquals(subcaDN, certChain[0].getX509v3PKCert().getSubject().toString()); assertEquals(ISSUER_DN, certChain[1].getX509v3PKCert().getSubject().toString()); } finally { try { this.endEntityManagementSession.deleteUser(ADMIN, username); } catch (NoSuchEndEntityException e) { // A test probably failed before creating the entity log.debug("Failed to delete user: " + username); } CryptoTokenTestUtils.removeCryptoToken(null, cryptoTokenId); // Remove CA certificate of CA that we will remove Collection certs = this.caSession.getCAInfo(ADMIN, subcaID).getCertificateChain(); this.internalCertStoreSession.removeCertificate(certs.iterator().next()); // Remove the CA itself this.caSession.removeCA(ADMIN, subcaID); } } /** Tests server generated keys, which are requested by sending a missing request public key in the CRMF request * message, or a SubjectPublicKeyInfo with AlgorithmId but not key bits, as specified in: * RFC4210 section 5.3.4 and Appendix D.4, RFC4211 Section 6.6 and Appendix B */ @Test public void test12ServerGeneratedKeys() throws Exception { log.trace(">test12ServerGeneratedKeys"); // Create a new good USER final String cmptestUsername = "cmpsrvgentest"; //final String cmptestCPName = "CMPSRVGENTEST"; final String cmptestCPName = CP_DN_OVERRIDE_NAME; CertificateProfile certificateProfile = this.certProfileSession.getCertificateProfile(CP_DN_OVERRIDE_NAME); assertNotNull(certificateProfile); // Backup the certificate profile so we can restore it afterwards, because we will modify it in this test // certificateProfile.setAvailableBitLengths(new int[] {1024, 2048}); // certificateProfile.setAvailableKeyAlgorithms(new String[]{"RSA", "ECDSA"}); CertificateProfile backup = certificateProfile.clone(); final int cpID = certProfileSession.getCertificateProfileId(CP_DN_OVERRIDE_NAME); final int eepID = endEntityProfileSession.getEndEntityProfileId(EEP_DN_OVERRIDE_NAME); log.info("Using Certificate Profile with ID: "+cpID); final X500Name userDN1 = createCmpUser(cmptestUsername, "foo123", "C=SE,O=MemyselfandI,CN="+cmptestUsername, false, this.caid, eepID, cpID); String fingerprint1 = null; String fingerprint2 = null; String fingerprint3 = null; String fingerprint4 = null; try { byte[] nonce = CmpMessageHelper.createSenderNonce(); byte[] transid = CmpMessageHelper.createSenderNonce(); // 0. // Send a CMP request with empty public key, signaling server key generation, but where server key generation is not allowed (the default) in the CMP alias // Should fail AlgorithmIdentifier pAlg = new AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption); PKIMessage req = genCertReq(ISSUER_DN, userDN1, /*keys*/null, this.cacert, nonce, transid, false, null, null, null, null, pAlg, null); assertNotNull(req); CertReqMessages ir = (CertReqMessages) req.getBody().getContent(); int reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); byte[] ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response byte[] resp = sendCmpHttp(ba, 200, cmpAlias); // This request should fail because we did not provide a protocolEncrKey key // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "Server generated keys not allowed"); // checkCmpFailMessage(resp, "Request public key can not be empty without providing a protocolEncrKey", 1, reqId, 7, PKIFailureInfo.badRequest); // 1. // Send a CMP request with empty public key, signaling server key generation, but where there is no protoclEncrKey to encrypt the response with // Should fail // Allow server key generation in the CMP alias this.cmpConfiguration.setAllowServerGeneratedKeys(cmpAlias, true); this.globalConfigurationSession.saveConfiguration(ADMIN, this.cmpConfiguration); pAlg = new AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption); req = genCertReq(ISSUER_DN, userDN1, /*keys*/null, this.cacert, nonce, transid, false, null, null, null, null, pAlg, null); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // This request should fail because we did not provide a protocolEncrKey key // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "Request public key can not be empty without providing a suitable protocolEncrKey (RSA)"); // checkCmpFailMessage(resp, "Request public key can not be empty without providing a protocolEncrKey", 1, reqId, 7, PKIFailureInfo.badRequest); // 2. // Add protocolEncKey that is not an RSA key, this will return an error as well KeyPair protocolEncKey = KeyTools.genKeys("secp256r1", "ECDSA"); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, null, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "Request public key can not be empty without providing a suitable protocolEncrKey (RSA)"); // 3. // Add protocolEncrKey or the correct type (RSA), but have request public key null, and not a single choice of keys in the Certificate Profile, should fail // Sending null means that the server should choose the keytype and size allowed by the certificate profile protocolEncKey = KeyTools.genKeys("1024", "RSA"); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, null, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "Certificate profile specified more than one key algoritm, not possible to server generate keys"); // 4. // Set a single selection in the Certificate Profile and expect a good answer // Sending null means that the server should choose the keytype and size allowed by the certificate profile certificateProfile.setAvailableBitLengths(new int[] {1024}); certificateProfile.setAvailableKeyAlgorithms(new String[]{"RSA"}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, null, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Now we should have a cert response PKIMessage pkiMessage = checkCmpResponseGeneral(resp, ISSUER_DN, userDN1, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha256WithRSAEncryption.getId()); X509Certificate cert = checkCmpCertRepMessage(userDN1, this.cacert, resp, reqId); assertNotNull(cert); fingerprint1 = CertTools.getFingerprintAsString(cert); // We should also have a private key in the response { final PKIBody pkiBody = pkiMessage.getBody(); final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent(); final CertResponse certResponse = certRepMessage.getResponse()[0]; final CertifiedKeyPair certifiedKeyPair = certResponse.getCertifiedKeyPair(); // certifiedKeyPair.getCertOrEncCert().getCertificate() is what we verified above in checkCmpCertRepMessage // Now lets try to dig out the encrypted private key // Created from: // JcaEncryptedValueBuilder encBldr = new JcaEncryptedValueBuilder( // new JceAsymmetricKeyWrapper(protocolEncrKey).setProvider(BouncyCastleProvider.PROVIDER_NAME), // new JceCRMFEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BouncyCastleProvider.PROVIDER_NAME).build()); // myCertifiedKeyPair = new CertifiedKeyPair(retCert, encBldr.build(kp.getPrivate()), null); EncryptedValue encValue = certifiedKeyPair.getPrivateKey(); AsymmetricKeyUnwrapper unwrapper = new JceAsymmetricKeyUnwrapper(encValue.getKeyAlg(), protocolEncKey.getPrivate()); byte[] secKeyBytes = (byte[])unwrapper.generateUnwrappedKey(encValue.getKeyAlg(), encValue.getEncSymmKey().getBytes()).getRepresentation(); // recover private key PKCS8EncryptedPrivateKeyInfo respInfo = new PKCS8EncryptedPrivateKeyInfo(encValue.getEncValue().getBytes()); PrivateKeyInfo keyInfo = respInfo.decryptPrivateKeyInfo(new JceInputDecryptorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(secKeyBytes)); assertEquals(keyInfo.getPrivateKeyAlgorithm(), encValue.getIntendedAlg()); // Verify that we didn't get our protocol encr key back (which should be impossible since we never sent the private key over) assertFalse(Arrays.areEqual(protocolEncKey.getPrivate().getEncoded(), keyInfo.getEncoded())); // Verify that the private key returned matches the public key in the certificate we got PrivateKey privKey = BouncyCastleProvider.getPrivateKey(keyInfo); byte[] data = "foobar we want to sign this data, cats and dogs rule!".getBytes(); byte[] signedData = KeyTools.signData(privKey, AlgorithmConstants.SIGALG_SHA256_WITH_RSA, data); final boolean signatureOK = KeyTools.verifyData(cert.getPublicKey(), AlgorithmConstants.SIGALG_SHA256_WITH_RSA, data, signedData); assertTrue(signatureOK); // Verify that the private/public key generated by the server is the algorithm and size that we expected assertEquals("RSA", privKey.getAlgorithm()); assertEquals(1024, KeyTools.getKeyLength(cert.getPublicKey())); } // 5. // Try with ECC keys // Sending null means that the server should choose the keytype and size allowed by the certificate profile this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); certificateProfile.setAvailableKeyAlgorithms(new String[]{"ECDSA"}); certificateProfile.setAvailableEcCurves(new String[]{"secp256r1"}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, null, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Now we should have a cert response pkiMessage = checkCmpResponseGeneral(resp, ISSUER_DN, userDN1, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha256WithRSAEncryption.getId()); cert = checkCmpCertRepMessage(userDN1, this.cacert, resp, reqId); assertNotNull(cert); fingerprint2 = CertTools.getFingerprintAsString(cert); // We should also have a private key in the response { final PKIBody pkiBody = pkiMessage.getBody(); final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent(); final CertResponse certResponse = certRepMessage.getResponse()[0]; final CertifiedKeyPair certifiedKeyPair = certResponse.getCertifiedKeyPair(); EncryptedValue encValue = certifiedKeyPair.getPrivateKey(); AsymmetricKeyUnwrapper unwrapper = new JceAsymmetricKeyUnwrapper(encValue.getKeyAlg(), protocolEncKey.getPrivate()); byte[] secKeyBytes = (byte[])unwrapper.generateUnwrappedKey(encValue.getKeyAlg(), encValue.getEncSymmKey().getBytes()).getRepresentation(); // recover private key PKCS8EncryptedPrivateKeyInfo respInfo = new PKCS8EncryptedPrivateKeyInfo(encValue.getEncValue().getBytes()); PrivateKeyInfo keyInfo = respInfo.decryptPrivateKeyInfo( new JceInputDecryptorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(secKeyBytes)); assertEquals(keyInfo.getPrivateKeyAlgorithm(), encValue.getIntendedAlg()); // Verify that we didn't get our protocol encr key back (which should be impossible since we never sent the private key over) assertFalse(Arrays.areEqual(protocolEncKey.getPrivate().getEncoded(), keyInfo.getEncoded())); // Verify that the private key returned matches the public key in the certificate we got PrivateKey privKey = BouncyCastleProvider.getPrivateKey(keyInfo); byte[] data = "foobar we want to sign this data, cats and dogs rule!".getBytes(); byte[] signedData = KeyTools.signData(privKey, AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA, data); final boolean signatureOK = KeyTools.verifyData(cert.getPublicKey(), AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA, data, signedData); assertTrue(signatureOK); // Verify that the private/public key generated by the server is the algorithm and size that we expected assertEquals("EC", privKey.getAlgorithm()); final String keySpec = AlgorithmTools.getKeySpecification(cert.getPublicKey()); assertEquals("prime256v1", keySpec); } // 6. // Instead of sending an empty public key, send a SubjectPublicKeyInfo with empty bitstring as specified in RFC4210: // First we try specifying RSA key, but profile only allows ECDSA, should fail // // "Note that subjectPublicKeyInfo MAY be present and contain an AlgorithmIdentifier followed by a zero-length BIT STRING for the subjectPublicKey // "if it is desired to inform the CA/RA of algorithm and parameter preferences regarding the to-be-generated key pair" // Server should then get the algorithm from the SubjectPublicKeyInfo this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); certificateProfile.setAvailableKeyAlgorithms(new String[]{"ECDSA"}); certificateProfile.setAvailableEcCurves(new String[]{"secp256r1"}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); // Start with RSA public key info, with empty BITString // Note for a normal RSA key the AlgorithmIdentifier.parameters is specified to be DERNull (not java null, but ASN.1 type null) // See RFC3279 for SubjectPublicKeyInfo OIDs and parameters for RSA, ECDSA etc SubjectPublicKeyInfo spkInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier( PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new byte[0]); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, spkInfo, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "RSA key generation requested, but certificate profile specified does not allow RSA"); // 7. // Same as above, but profile allows multiple RSA key sizes, should fail // this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); certificateProfile.setAvailableKeyAlgorithms(new String[]{"RSA"}); certificateProfile.setAvailableBitLengths(new int[] {1024, 2048}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); // Start with RSA public key info, with empty BITString // Note for a normal RSA key the AlgorithmIdentifier.parameters is specified to be DERNull (not java null, but ASN.1 type null) // See RFC3279 for SubjectPublicKeyInfo OIDs and parameters for RSA, ECDSA etc req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, spkInfo, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "Certificate profile specified more than one key size, not possible to server generate keys"); // 8. // Try the same but with an unsupported algorithm, should fail this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); // Start with RSA public key info, with empty BITString spkInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier( PKCSObjectIdentifiers.des_EDE3_CBC, DERNull.INSTANCE), new byte[0]); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, spkInfo, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "Server key generation requested, but SubjectPublicKeyInfo specifies unsupported algorithm 1.2.840.113549.3.7"); // 9. // Instead of sending an empty public key, send a SubjectPublicKeyInfo with empty bitstring as specified in RFC4210: // "Note that subjectPublicKeyInfo MAY be present and contain an AlgorithmIdentifier followed by a zero-length BIT STRING for the subjectPublicKey // "if it is desired to inform the CA/RA of algorithm and parameter preferences regarding the to-be-generated key pair" // Server should then get the algorithm from the SubjectPublicKeyInfo this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); certificateProfile.setAvailableKeyAlgorithms(new String[]{"RSA"}); certificateProfile.setAvailableBitLengths(new int[] {1024}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); // Start with RSA public key info, with empty BITString spkInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier( PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new byte[0]); // SubjectPublicKeyInfo spkInfoEC = new SubjectPublicKeyInfo(new AlgorithmIdentifier( // X9ObjectIdentifiers.id_ecPublicKey, DERNull.INSTANCE), new byte[0]); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, spkInfo, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Now we should have a cert response pkiMessage = checkCmpResponseGeneral(resp, ISSUER_DN, userDN1, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha256WithRSAEncryption.getId()); cert = checkCmpCertRepMessage(userDN1, this.cacert, resp, reqId); assertNotNull(cert); fingerprint3 = CertTools.getFingerprintAsString(cert); // We should also have a private key in the response { final PKIBody pkiBody = pkiMessage.getBody(); final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent(); final CertResponse certResponse = certRepMessage.getResponse()[0]; final CertifiedKeyPair certifiedKeyPair = certResponse.getCertifiedKeyPair(); EncryptedValue encValue = certifiedKeyPair.getPrivateKey(); AsymmetricKeyUnwrapper unwrapper = new JceAsymmetricKeyUnwrapper(encValue.getKeyAlg(), protocolEncKey.getPrivate()); byte[] secKeyBytes = (byte[])unwrapper.generateUnwrappedKey(encValue.getKeyAlg(), encValue.getEncSymmKey().getBytes()).getRepresentation(); // recover private key PKCS8EncryptedPrivateKeyInfo respInfo = new PKCS8EncryptedPrivateKeyInfo(encValue.getEncValue().getBytes()); PrivateKeyInfo keyInfo = respInfo.decryptPrivateKeyInfo(new JceInputDecryptorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(secKeyBytes)); assertEquals(keyInfo.getPrivateKeyAlgorithm(), encValue.getIntendedAlg()); // Verify that we didn't get our protocol encr key back (which should be impossible since we never sent the private key over) assertFalse(Arrays.areEqual(protocolEncKey.getPrivate().getEncoded(), keyInfo.getEncoded())); // Verify that the private key returned matches the public key in the certificate we got PrivateKey privKey = BouncyCastleProvider.getPrivateKey(keyInfo); byte[] data = "foobar we want to sign this data, cats and dogs rule!".getBytes(); byte[] signedData = KeyTools.signData(privKey, AlgorithmConstants.SIGALG_SHA256_WITH_RSA, data); final boolean signatureOK = KeyTools.verifyData(cert.getPublicKey(), AlgorithmConstants.SIGALG_SHA256_WITH_RSA, data, signedData); assertTrue(signatureOK); // Verify that the private/public key generated by the server is the algorithm and size that we expected assertEquals("RSA", privKey.getAlgorithm()); assertEquals(1024, KeyTools.getKeyLength(cert.getPublicKey())); } // 10. // Same as above with ECDSA, first specify a curve that isn't allowed in the profile this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); certificateProfile.setAvailableKeyAlgorithms(new String[]{"ECDSA"}); certificateProfile.setAvailableEcCurves(new String[]{"secp256r1"}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); // Try with an ECDSA public key info, with empty BITString // See RFC3279 for SubjectPublicKeyInfo OIDs and parameters for RSA, ECDSA etc // We'll specify the named curve we request here X962Parameters params = new X962Parameters(X9ObjectIdentifiers.prime192v1); spkInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier( X9ObjectIdentifiers.id_ecPublicKey, params), new byte[0]); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, spkInfo, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Expect a CertificateResponse (reject) message with error FailInfo.BAD_REQUEST checkCmpPKIErrorMessage(resp, ISSUER_DN, userDN1, PKIFailureInfo.badRequest, "ECDSA key generation requested, but X962Parameters curve is none of the allowed named curves: prime192v1"); // 11. // Change the profile to allow the curve we specify as params to SubjectPublicKeyInfo this.endEntityManagementSession.setUserStatus(ADMIN, cmptestUsername, EndEntityConstants.STATUS_NEW); certificateProfile.setAvailableKeyAlgorithms(new String[]{"ECDSA"}); certificateProfile.setAvailableEcCurves(new String[]{"prime192v1", "secp256r1"}); certProfileSession.changeCertificateProfile(ADMIN, cmptestCPName, certificateProfile); // Try with an ECDSA public key info, with empty BITString, but with params specifying a curve // See RFC3279 for SubjectPublicKeyInfo OIDs and parameters for RSA, ECDSA etc // We'll specify the named curve we request here spkInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier( X9ObjectIdentifiers.id_ecPublicKey, params), new byte[0]); req = genCertReq(ISSUER_DN, userDN1, userDN1, null, /*keys*/null, spkInfo, protocolEncKey, cacert, nonce, transid, false, null, null, null, null, pAlg, null, false); assertNotNull(req); ir = (CertReqMessages) req.getBody().getContent(); reqId = ir.toCertReqMsgArray()[0].getCertReq().getCertReqId().getValue().intValue(); ba = CmpMessageHelper.pkiMessageToByteArray(req); // Send request and receive response resp = sendCmpHttp(ba, 200, cmpAlias); // Now we should have a cert response pkiMessage = checkCmpResponseGeneral(resp, ISSUER_DN, userDN1, this.cacert, nonce, transid, true, null, PKCSObjectIdentifiers.sha256WithRSAEncryption.getId()); cert = checkCmpCertRepMessage(userDN1, this.cacert, resp, reqId); assertNotNull(cert); fingerprint4 = CertTools.getFingerprintAsString(cert); // We should also have a private key in the response { final PKIBody pkiBody = pkiMessage.getBody(); final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent(); final CertResponse certResponse = certRepMessage.getResponse()[0]; final CertifiedKeyPair certifiedKeyPair = certResponse.getCertifiedKeyPair(); EncryptedValue encValue = certifiedKeyPair.getPrivateKey(); AsymmetricKeyUnwrapper unwrapper = new JceAsymmetricKeyUnwrapper(encValue.getKeyAlg(), protocolEncKey.getPrivate()); byte[] secKeyBytes = (byte[])unwrapper.generateUnwrappedKey(encValue.getKeyAlg(), encValue.getEncSymmKey().getBytes()).getRepresentation(); // recover private key PKCS8EncryptedPrivateKeyInfo respInfo = new PKCS8EncryptedPrivateKeyInfo(encValue.getEncValue().getBytes()); PrivateKeyInfo keyInfo = respInfo.decryptPrivateKeyInfo(new JceInputDecryptorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(secKeyBytes)); assertEquals(keyInfo.getPrivateKeyAlgorithm(), encValue.getIntendedAlg()); // Verify that we didn't get our protocol encr key back (which should be impossible since we never sent the private key over) assertFalse(Arrays.areEqual(protocolEncKey.getPrivate().getEncoded(), keyInfo.getEncoded())); // Verify that the private key returned matches the public key in the certificate we got PrivateKey privKey = BouncyCastleProvider.getPrivateKey(keyInfo); byte[] data = "foobar we want to sign this data, cats and dogs rule!".getBytes(); byte[] signedData = KeyTools.signData(privKey, AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA, data); final boolean signatureOK = KeyTools.verifyData(cert.getPublicKey(), AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA, data, signedData); assertTrue(signatureOK); // Verify that the private/public key generated by the server is the algorithm and size that we expected assertEquals("EC", privKey.getAlgorithm()); final String keySpec = AlgorithmTools.getKeySpecification(cert.getPublicKey()); assertEquals("prime192v1", keySpec); } } finally { log.debug("Deleting certificate: "+fingerprint1); this.internalCertStoreSession.removeCertificate(fingerprint1); log.debug("Deleting certificate: "+fingerprint2); this.internalCertStoreSession.removeCertificate(fingerprint2); log.debug("Deleting certificate: "+fingerprint3); this.internalCertStoreSession.removeCertificate(fingerprint3); log.debug("Deleting certificate: "+fingerprint4); this.internalCertStoreSession.removeCertificate(fingerprint4); log.debug("Deleting user: "+cmptestUsername); try { this.endEntityManagementSession.deleteUser(ADMIN, cmptestUsername); } catch (NoSuchEndEntityException e) { // NOPMD: ignore } // Re-set CMP alias configuration this.cmpConfiguration.setAllowServerGeneratedKeys(cmpAlias, false); this.globalConfigurationSession.saveConfiguration(ADMIN, this.cmpConfiguration); // Restore certificate profile to what it was before the test this.certProfileSession.changeCertificateProfile(ADMIN, CP_DN_OVERRIDE_NAME, backup); } log.trace("