Java tutorial
/************************************************************************* * * * 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.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CRLException; import java.security.cert.CertStore; import java.security.cert.CertStoreException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Iterator; import junit.framework.TestCase; import junit.framework.TestSuite; import org.apache.log4j.Logger; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DERPrintableString; import org.bouncycastle.asn1.DERString; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.cms.CMSEnvelopedData; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedGenerator; import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformationStore; import org.bouncycastle.cms.SignerId; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.util.encoders.Base64; import org.ejbca.core.model.AlgorithmConstants; import org.ejbca.core.protocol.ResponseStatus; import org.ejbca.core.protocol.scep.ScepRequestMessage; import org.ejbca.util.CertTools; import org.ejbca.util.CryptoProviderTools; import org.ejbca.util.keystore.KeyTools; /** * Tests SCEP enrollment with an RA (SCEP polling RA mode). * This test assumes a CA hierarchy. One root CA AdminCA1 and one sub CA ScepCA. * * @version $Id: ProtocolScepHttpTest.java 9435 2010-07-14 15:18:39Z mikekushner $ */ public class ProtocolScepHttpTest extends TestCase { private static Logger log = Logger.getLogger(ProtocolScepHttpTest.class); private static final String httpReqPath = "http://127.0.0.1:8080"; private static final String resourceScep = "/scepraserver/scep/pkiclient.exe"; private static final String resourceScepNoCA = "/scepraserver/scep/noca/pkiclient.exe"; private static final String radn = "CN=Scep RA,O=PrimeKey,C=SE"; private static final String cadn = "CN=Scep CA,O=EJBCA Sample,C=SE"; private static final String rootcadn = "CN=AdminCA1,O=EJBCA Sample,C=SE"; private static X509Certificate rootcacert = null; private static X509Certificate cacert = null; private static X509Certificate racert = null; private static KeyPair keys = null; private String transId = null; private String senderNonce = null; public static void main(String args[]) { junit.textui.TestRunner.run(suite()); } public static TestSuite suite() { return new TestSuite(ProtocolScepHttpTest.class); } public ProtocolScepHttpTest(String name) throws Exception { super(name); // Install BouncyCastle provider CryptoProviderTools.installBCProvider(); if (keys == null) { keys = KeyTools.genKeys("512", AlgorithmConstants.KEYALGORITHM_RSA); } } public void setUp() throws Exception { } public void tearDown() throws Exception { } // GetCACert and GetCACertChain behaves the same if it is an RA that responds public void test02ScepGetCACert() throws Exception { log.debug(">test02ScepGetCACert()"); scepGetCACertChain("GetCACert", "application/x-x509-ca-ra-cert"); log.debug(">test02ScepGetCACert()"); } public void test03ScepGetCACertChain() throws Exception { log.debug(">test03ScepGetCACertChain()"); scepGetCACertChain("GetCACertChain", "application/x-x509-ca-ra-cert-chain"); log.debug(">test03ScepGetCACertChain()"); } private void scepGetCACertChain(String method, String mimetype) throws Exception { String reqUrl = httpReqPath + '/' + resourceScepNoCA + "?operation=" + method + "&message=test"; URL url = new URL(reqUrl); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.getDoOutput(); con.connect(); assertEquals("Response code", 200, con.getResponseCode()); assertEquals("Content-Type", mimetype, con.getContentType()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // This works for small requests, and SCEP requests are small enough InputStream in = con.getInputStream(); int b = in.read(); while (b != -1) { baos.write(b); b = in.read(); } baos.flush(); in.close(); byte[] respBytes = baos.toByteArray(); assertNotNull("Response can not be null.", respBytes); assertTrue(respBytes.length > 0); CMSSignedData s = new CMSSignedData(respBytes); assertNotNull(s); SignerInformationStore signers = s.getSignerInfos(); Collection col = signers.getSigners(); assertTrue(col.size() == 0); CertStore certstore = s.getCertificatesAndCRLs("Collection", "BC"); Collection certs = certstore.getCertificates(null); // Length two if the Scep RA server is signed directly by a Root CA // Length three if the Scep RA server is signed by a CA which is signed by a Root CA assertEquals(3, certs.size()); Iterator it = certs.iterator(); racert = (X509Certificate) it.next(); cacert = (X509Certificate) it.next(); rootcacert = (X509Certificate) it.next(); log.info("Got CA cert with DN: " + cacert.getSubjectDN().getName()); assertEquals(cadn, cacert.getSubjectDN().getName()); log.info("Got Root CA cert with DN: " + rootcacert.getSubjectDN().getName()); assertEquals(rootcadn, rootcacert.getSubjectDN().getName()); log.info("Got RA cert with DN: " + racert.getSubjectDN().getName()); assertEquals(radn, racert.getSubjectDN().getName()); } public void test04ScepGetCACaps() throws Exception { log.debug(">test04ScepGetCACaps()"); String reqUrl = httpReqPath + '/' + resourceScep + "?operation=GetCACaps&message=test"; URL url = new URL(reqUrl); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.getDoOutput(); con.connect(); assertEquals("Response code", 200, con.getResponseCode()); assertEquals("Content-Type", "text/plain", con.getContentType()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // This works for small requests, and SCEP requests are small enough InputStream in = con.getInputStream(); int b = in.read(); while (b != -1) { baos.write(b); b = in.read(); } baos.flush(); in.close(); byte[] respBytes = baos.toByteArray(); assertNotNull("Response can not be null.", respBytes); assertTrue(respBytes.length > 0); assertEquals(new String(respBytes), "POSTPKIOperation\nSHA-1"); log.debug(">test04ScepGetCACaps()"); } // This test will send a request and expect back a pending response. // It will then start polling the RA waiting for a real reply. // When polling it will accept a pending or a success reply public void test05ScepRequestOKSHA1() throws Exception { log.debug(">test05ScepRequestOKSHA1()"); // send SCEP req to RA server and get pending request, until the request is processed on the CA // Then we will get a certificate response back ScepRequestGenerator gen = new ScepRequestGenerator(); byte[] msgBytes = genScepRequest(gen, CMSSignedGenerator.DIGEST_SHA1); // Send message with GET byte[] retMsg = sendScep(false, msgBytes, false); assertNotNull(retMsg); checkScepResponse(retMsg, senderNonce, transId, false, CMSSignedGenerator.DIGEST_SHA1, false, ResponseStatus.PENDING); // Send GetCertInitial and wait for a certificate response, you will probably get PENDING reply several times int keeprunning = 0; boolean processed = false; while ((keeprunning < 5) && !processed) { log.info("Waiting 5 secs..."); Thread.sleep(5000); // wait 5 seconds between polls msgBytes = genScepGetCertInitial(gen, CMSSignedGenerator.DIGEST_SHA1); // Send message with GET retMsg = sendScep(false, msgBytes, false); assertNotNull(retMsg); if (isScepResponseMessageOfType(retMsg, ResponseStatus.PENDING)) { log.info("Received a PENDING message"); checkScepResponse(retMsg, senderNonce, transId, false, CMSSignedGenerator.DIGEST_SHA1, false, ResponseStatus.PENDING); } else { log.info("Received a SUCCESS message"); checkScepResponse(retMsg, senderNonce, transId, false, CMSSignedGenerator.DIGEST_SHA1, false, ResponseStatus.SUCCESS); processed = true; } keeprunning++; } assertTrue(processed); log.debug("<test05ScepRequestOKSHA1()"); } // This test will send a request and expect back a pending response. // It will then start polling the RA waiting for a real reply. // When polling it will accept a pending or a success reply public void test06ScepRequestOKSHA1PostNoCA() throws Exception { log.debug(">test06ScepRequestOKSHA1PostNoCA()"); // send SCEP req to RA server and get pending request, until the request is processed on the CA // Then we will get a certificate response back ScepRequestGenerator gen = new ScepRequestGenerator(); byte[] msgBytes = genScepRequest(gen, CMSSignedGenerator.DIGEST_SHA1); // Send message with POST byte[] retMsg = sendScep(true, msgBytes, true); assertNotNull(retMsg); checkScepResponse(retMsg, senderNonce, transId, false, CMSSignedGenerator.DIGEST_SHA1, true, ResponseStatus.PENDING); // Send GetCertInitial and wait for a certificate response, you will probably get PENDING reply several times int keeprunning = 0; boolean processed = false; while ((keeprunning < 5) && !processed) { Thread.sleep(5000); // wait 5 seconds between polls msgBytes = genScepGetCertInitial(gen, CMSSignedGenerator.DIGEST_SHA1); // Send message with GET retMsg = sendScep(true, msgBytes, true); assertNotNull(retMsg); if (isScepResponseMessageOfType(retMsg, ResponseStatus.PENDING)) { checkScepResponse(retMsg, senderNonce, transId, false, CMSSignedGenerator.DIGEST_SHA1, true, ResponseStatus.PENDING); } else { checkScepResponse(retMsg, senderNonce, transId, false, CMSSignedGenerator.DIGEST_SHA1, true, ResponseStatus.SUCCESS); processed = true; } keeprunning++; } assertTrue(processed); log.debug("<test06ScepRequestOKSHA1PostNoCA()"); } // // // Private helper methods // // private byte[] genScepRequest(ScepRequestGenerator gen, String digestoid) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, InvalidAlgorithmParameterException, CertStoreException, IOException, CMSException, CertificateEncodingException, IllegalStateException { gen.setKeys(keys); gen.setDigestOid(digestoid); byte[] msgBytes = null; String dn = "C=SE, O=PrimeKey, CN=sceptest"; msgBytes = gen.generateCertReq(dn, "foo123", racert); assertNotNull(msgBytes); transId = gen.getTransactionId(); assertNotNull(transId); byte[] idBytes = Base64.decode(transId.getBytes()); assertEquals(16, idBytes.length); senderNonce = gen.getSenderNonce(); byte[] nonceBytes = Base64.decode(senderNonce.getBytes()); assertEquals(16, nonceBytes.length); return msgBytes; } private byte[] genScepGetCertInitial(ScepRequestGenerator gen, String digestoid) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CertStoreException, IOException, CMSException, IllegalStateException { gen.setKeys(keys); gen.setDigestOid(digestoid); byte[] msgBytes = null; String dn = "C=SE, O=PrimeKey, CN=sceptest"; // must be same as when the request was generated msgBytes = gen.generateGetCertInitial(dn, racert); assertNotNull(msgBytes); transId = gen.getTransactionId(); assertNotNull(transId); byte[] idBytes = Base64.decode(transId.getBytes()); assertEquals(16, idBytes.length); senderNonce = gen.getSenderNonce(); byte[] nonceBytes = Base64.decode(senderNonce.getBytes()); assertEquals(16, nonceBytes.length); return msgBytes; } private boolean isScepResponseMessageOfType(byte[] retMsg, ResponseStatus extectedResponseStatus) throws CMSException, NoSuchAlgorithmException, NoSuchProviderException { // // Parse response message // CMSSignedData s = new CMSSignedData(retMsg); // The signer, i.e. the CA, check it's the right CA SignerInformationStore signers = s.getSignerInfos(); Collection col = signers.getSigners(); assertTrue(col.size() > 0); Iterator iter = col.iterator(); SignerInformation signerInfo = (SignerInformation) iter.next(); SignerId sinfo = signerInfo.getSID(); // Check that the signer is the expected CA assertEquals(CertTools.stringToBCDNString(racert.getIssuerDN().getName()), CertTools.stringToBCDNString(sinfo.getIssuerAsString())); // Verify the signature boolean ret = signerInfo.verify(racert.getPublicKey(), "BC"); assertTrue(ret); // Get authenticated attributes AttributeTable tab = signerInfo.getSignedAttributes(); // --Fail info Attribute attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_failInfo)); // --Message type attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_messageType)); assertNotNull(attr); ASN1Set values = attr.getAttrValues(); assertEquals(values.size(), 1); DERString str = DERPrintableString.getInstance((values.getObjectAt(0))); String messageType = str.getString(); assertEquals("3", messageType); // --Success status attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_pkiStatus)); assertNotNull(attr); values = attr.getAttrValues(); assertEquals(values.size(), 1); str = DERPrintableString.getInstance((values.getObjectAt(0))); String responsestatus = str.getString(); if (extectedResponseStatus.getValue().equals(responsestatus)) { return true; } return false; } private void checkScepResponse(byte[] retMsg, String senderNonce, String transId, boolean crlRep, String digestOid, boolean noca, ResponseStatus expectedResponseStatus) throws CMSException, NoSuchProviderException, NoSuchAlgorithmException, CertStoreException, InvalidKeyException, CertificateException, SignatureException, CRLException, IOException { // // Parse response message // CMSSignedData s = new CMSSignedData(retMsg); // The signer, i.e. the CA, check it's the right CA SignerInformationStore signers = s.getSignerInfos(); Collection col = signers.getSigners(); assertTrue(col.size() > 0); Iterator iter = col.iterator(); SignerInformation signerInfo = (SignerInformation) iter.next(); // Check that the message is signed with the correct digest alg assertEquals(signerInfo.getDigestAlgOID(), digestOid); SignerId sinfo = signerInfo.getSID(); // Check that the signer is the expected CA assertEquals(CertTools.stringToBCDNString(racert.getIssuerDN().getName()), CertTools.stringToBCDNString(sinfo.getIssuerAsString())); // Verify the signature boolean ret = signerInfo.verify(racert.getPublicKey(), "BC"); assertTrue(ret); // Get authenticated attributes AttributeTable tab = signerInfo.getSignedAttributes(); // --Fail info Attribute attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_failInfo)); // No failInfo on this success message if (expectedResponseStatus == ResponseStatus.SUCCESS) { assertNull(attr); } // --Message type attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_messageType)); assertNotNull(attr); ASN1Set values = attr.getAttrValues(); assertEquals(values.size(), 1); DERString str = DERPrintableString.getInstance((values.getObjectAt(0))); String messageType = str.getString(); assertEquals("3", messageType); // --Success status attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_pkiStatus)); assertNotNull(attr); values = attr.getAttrValues(); assertEquals(values.size(), 1); str = DERPrintableString.getInstance((values.getObjectAt(0))); String responsestatus = str.getString(); assertEquals(expectedResponseStatus.getValue(), responsestatus); // --SenderNonce attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_senderNonce)); assertNotNull(attr); values = attr.getAttrValues(); assertEquals(values.size(), 1); ASN1OctetString octstr = ASN1OctetString.getInstance(values.getObjectAt(0)); // SenderNonce is something the server came up with, but it should be 16 chars assertTrue(octstr.getOctets().length == 16); // --Recipient Nonce attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_recipientNonce)); assertNotNull(attr); values = attr.getAttrValues(); assertEquals(values.size(), 1); octstr = ASN1OctetString.getInstance(values.getObjectAt(0)); // recipient nonce should be the same as we sent away as sender nonce assertEquals(senderNonce, new String(Base64.encode(octstr.getOctets()))); // --Transaction ID attr = tab.get(new DERObjectIdentifier(ScepRequestMessage.id_transId)); assertNotNull(attr); values = attr.getAttrValues(); assertEquals(values.size(), 1); str = DERPrintableString.getInstance((values.getObjectAt(0))); // transid should be the same as the one we sent assertEquals(transId, str.getString()); // // Check different message types // if (!responsestatus.equals(ResponseStatus.PENDING.getValue()) && messageType.equals("3")) { // First we extract the encrypted data from the CMS enveloped data contained // within the CMS signed data CMSProcessable sp = s.getSignedContent(); byte[] content = (byte[]) sp.getContent(); CMSEnvelopedData ed = new CMSEnvelopedData(content); RecipientInformationStore recipients = ed.getRecipientInfos(); Collection c = recipients.getRecipients(); assertEquals(c.size(), 1); Iterator it = c.iterator(); byte[] decBytes = null; RecipientInformation recipient = (RecipientInformation) it.next(); decBytes = recipient.getContent(keys.getPrivate(), "BC"); // This is yet another CMS signed data CMSSignedData sd = new CMSSignedData(decBytes); // Get certificates from the signed data CertStore certstore = sd.getCertificatesAndCRLs("Collection", "BC"); if (crlRep) { // We got a reply with a requested CRL Collection crls = certstore.getCRLs(null); assertEquals(crls.size(), 1); it = crls.iterator(); X509CRL retCrl = null; // CRL is first (and only) retCrl = (X509CRL) it.next(); log.info("Got CRL with DN: " + retCrl.getIssuerDN().getName()); // try { // FileOutputStream fos = new FileOutputStream("sceptest.der"); // fos.write(retCrl.getEncoded()); // fos.close(); // } catch (Exception e) {} // check the returned CRL assertEquals(cacert.getSubjectDN().getName(), retCrl.getIssuerDN().getName()); retCrl.verify(cacert.getPublicKey()); } else { // We got a reply with a requested certificate Collection certs = certstore.getCertificates(null); log.info("Got certificate reply with certchain of length: " + certs.size()); // EJBCA returns the issued cert and the CA cert (cisco vpn client requires that the ca cert is included) if (noca) { assertEquals(certs.size(), 1); } else { assertEquals(certs.size(), 2); } it = certs.iterator(); // Issued certificate must be first boolean verified = false; boolean gotcacert = false; String mysubjectdn = CertTools.stringToBCDNString("C=SE,O=PrimeKey,CN=sceptest"); X509Certificate usercert = null; while (it.hasNext()) { X509Certificate retcert = (X509Certificate) it.next(); // try { // FileOutputStream fos = new FileOutputStream("sceptest.der"); // fos.write(retcert.getEncoded()); // fos.close(); // } catch (Exception e) {} // check the returned certificate String subjectdn = CertTools.stringToBCDNString(retcert.getSubjectDN().getName()); if (mysubjectdn.equals(subjectdn)) { System.out.println("Got user cert with DN: " + retcert.getSubjectDN().getName()); // issued certificate assertEquals(CertTools.stringToBCDNString("C=SE,O=PrimeKey,CN=sceptest"), subjectdn); //System.out.println(retcert); //System.out.println(cacert); retcert.verify(cacert.getPublicKey()); assertTrue(checkKeys(keys.getPrivate(), retcert.getPublicKey())); verified = true; String altName = CertTools.getSubjectAlternativeName(retcert); assertEquals("iPAddress=10.0.0.1, dNSName=foo.bar.com", altName); usercert = retcert; } else { log.info("Got CA cert with DN: " + retcert.getSubjectDN().getName()); // ca certificate assertEquals(cacert.getSubjectDN().getName(), retcert.getSubjectDN().getName()); gotcacert = true; usercert.verify(retcert.getPublicKey()); } } assertTrue(verified); if (noca) { assertFalse(gotcacert); } else { assertTrue(gotcacert); } } } } /** * checks that a public and private key matches by signing and verifying a message */ private boolean checkKeys(PrivateKey priv, PublicKey pub) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { Signature signer = Signature.getInstance("SHA1WithRSA"); signer.initSign(priv); signer.update("PrimeKey".getBytes()); byte[] signature = signer.sign(); Signature signer2 = Signature.getInstance("SHA1WithRSA"); signer2.initVerify(pub); signer2.update("PrimeKey".getBytes()); return signer2.verify(signature); } private byte[] sendScep(boolean post, byte[] scepPackage, boolean noca) throws IOException { // POST the OCSP request // we are going to do a POST String resource = resourceScep; if (noca) { resource = resourceScepNoCA; } String urlString = httpReqPath + '/' + resource + "?operation=PKIOperation"; log.debug("UrlString =" + urlString); log.debug("scepPackage.length: " + scepPackage.length); HttpURLConnection con = null; if (post) { URL url = new URL(urlString); con = (HttpURLConnection) url.openConnection(); con.setDoOutput(true); con.setRequestMethod("POST"); con.connect(); // POST it OutputStream os = con.getOutputStream(); os.write(scepPackage); os.close(); } else { String reqUrl = urlString + "&message=" + URLEncoder.encode(new String(Base64.encode(scepPackage)), "UTF-8"); URL url = new URL(reqUrl); con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.getDoOutput(); con.connect(); } log.debug("HTTP response message: " + con.getResponseMessage()); assertEquals("Response code ", 200, con.getResponseCode()); assertEquals("Content-Type", "application/x-pki-message", con.getContentType()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // This works for small requests, and SCEP requests are small enough InputStream in = con.getInputStream(); int b = in.read(); while (b != -1) { baos.write(b); b = in.read(); } baos.flush(); in.close(); byte[] respBytes = baos.toByteArray(); assertNotNull("Response can not be null.", respBytes); assertTrue(respBytes.length > 0); return respBytes; } }