Java tutorial
/* * Copyright (c) 2009-2010 David Grant * Copyright (c) 2010 ThruPoint Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jscep.server; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.Writer; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCRLStore; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.CMSAbsentContent; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.SignerInfoGenerator; import org.bouncycastle.cms.SignerInfoGeneratorBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculatorProvider; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.util.Store; import org.bouncycastle.util.encoders.Base64; import org.jscep.asn1.IssuerAndSubject; import org.jscep.message.CertRep; import org.jscep.message.MessageDecodingException; import org.jscep.message.MessageEncodingException; import org.jscep.message.PkcsPkiEnvelopeDecoder; import org.jscep.message.PkcsPkiEnvelopeEncoder; import org.jscep.message.PkiMessage; import org.jscep.message.PkiMessageDecoder; import org.jscep.message.PkiMessageEncoder; import org.jscep.transaction.FailInfo; import org.jscep.transaction.MessageType; import org.jscep.transaction.Nonce; import org.jscep.transaction.OperationFailureException; import org.jscep.transaction.TransactionId; import org.jscep.transport.request.Operation; import org.jscep.transport.response.Capability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class provides a base Servlet which can be extended using the abstract * methods to implement a SCEP CA (or RA). */ public abstract class ScepServlet extends HttpServlet { private static final String GET = "GET"; private static final String POST = "POST"; private static final String MSG_PARAM = "message"; private static final String OP_PARAM = "operation"; private static final Logger LOGGER = LoggerFactory.getLogger(ScepServlet.class); /** * Serialization ID */ private static final long serialVersionUID = 1L; /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public final void service(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException { byte[] body = getMessageBytes(req); final Operation op; try { op = getOperation(req); if (op == null) { // The operation parameter must be set. res.setStatus(HttpServletResponse.SC_BAD_REQUEST); Writer writer = res.getWriter(); writer.write("Missing \"operation\" parameter."); writer.flush(); return; } } catch (IllegalArgumentException e) { // The operation was not recognised. res.setStatus(HttpServletResponse.SC_BAD_REQUEST); Writer writer = res.getWriter(); writer.write("Invalid \"operation\" parameter."); writer.flush(); return; } LOGGER.debug("Incoming Operation: " + op); final String reqMethod = req.getMethod(); if (op == Operation.PKI_OPERATION) { if (!reqMethod.equals(POST) && !reqMethod.equals(GET)) { // PKIOperation must be sent using GET or POST res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); res.addHeader("Allow", GET + ", " + POST); return; } } else { if (!reqMethod.equals(GET)) { // Operations other than PKIOperation must be sent using GET res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); res.addHeader("Allow", GET); return; } } LOGGER.debug("Method " + reqMethod + " Allowed for Operation: " + op); if (op == Operation.GET_CA_CAPS) { try { LOGGER.debug("Invoking doGetCaCaps"); doGetCaCaps(req, res); } catch (Exception e) { throw new ServletException(e); } } else if (op == Operation.GET_CA_CERT) { try { LOGGER.debug("Invoking doGetCaCert"); doGetCaCert(req, res); } catch (Exception e) { throw new ServletException(e); } } else if (op == Operation.GET_NEXT_CA_CERT) { try { LOGGER.debug("Invoking doGetNextCaCert"); doGetNextCaCert(req, res); } catch (Exception e) { throw new ServletException(e); } } else if (op == Operation.PKI_OPERATION) { // PKIOperation res.setHeader("Content-Type", "application/x-pki-message"); CMSSignedData sd; try { sd = new CMSSignedData(body); } catch (CMSException e) { throw new ServletException(e); } Store reqStore = sd.getCertificates(); Collection<X509CertificateHolder> reqCerts = reqStore.getMatches(null); CertificateFactory factory; try { factory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new ServletException(e); } X509CertificateHolder holder = reqCerts.iterator().next(); ByteArrayInputStream bais = new ByteArrayInputStream(holder.getEncoded()); X509Certificate reqCert; try { reqCert = (X509Certificate) factory.generateCertificate(bais); } catch (CertificateException e) { throw new ServletException(e); } PkiMessage<?> msg; try { PkcsPkiEnvelopeDecoder envDecoder = new PkcsPkiEnvelopeDecoder(getRecipient(), getRecipientKey()); PkiMessageDecoder decoder = new PkiMessageDecoder(reqCert, envDecoder); msg = decoder.decode(sd); } catch (MessageDecodingException e) { LOGGER.error("Error decoding request", e); throw new ServletException(e); } LOGGER.debug("Processing message {}", msg); MessageType msgType = msg.getMessageType(); Object msgData = msg.getMessageData(); Nonce senderNonce = Nonce.nextNonce(); TransactionId transId = msg.getTransactionId(); Nonce recipientNonce = msg.getSenderNonce(); CertRep certRep; if (msgType == MessageType.GET_CERT) { final IssuerAndSerialNumber iasn = (IssuerAndSerialNumber) msgData; final X500Name principal = iasn.getName(); final BigInteger serial = iasn.getSerialNumber().getValue(); try { List<X509Certificate> issued = doGetCert(principal, serial); if (issued.size() == 0) { certRep = new CertRep(transId, senderNonce, recipientNonce, FailInfo.badCertId); } else { CMSSignedData messageData = getMessageData(issued); certRep = new CertRep(transId, senderNonce, recipientNonce, messageData); } } catch (OperationFailureException e) { certRep = new CertRep(transId, senderNonce, recipientNonce, e.getFailInfo()); } catch (Exception e) { throw new ServletException(e); } } else if (msgType == MessageType.GET_CERT_INITIAL) { final IssuerAndSubject ias = (IssuerAndSubject) msgData; final X500Name issuer = X500Name.getInstance(ias.getIssuer()); final X500Name subject = X500Name.getInstance(ias.getSubject()); try { List<X509Certificate> issued = doGetCertInitial(issuer, subject, transId); if (issued.size() == 0) { certRep = new CertRep(transId, senderNonce, recipientNonce); } else { CMSSignedData messageData = getMessageData(issued); certRep = new CertRep(transId, senderNonce, recipientNonce, messageData); } } catch (OperationFailureException e) { certRep = new CertRep(transId, senderNonce, recipientNonce, e.getFailInfo()); } catch (Exception e) { throw new ServletException(e); } } else if (msgType == MessageType.GET_CRL) { final IssuerAndSerialNumber iasn = (IssuerAndSerialNumber) msgData; final X500Name issuer = iasn.getName(); final BigInteger serialNumber = iasn.getSerialNumber().getValue(); try { LOGGER.debug("Invoking doGetCrl"); CMSSignedData messageData = getMessageData(doGetCrl(issuer, serialNumber)); certRep = new CertRep(transId, senderNonce, recipientNonce, messageData); } catch (OperationFailureException e) { LOGGER.error("Error executing GetCRL request", e); certRep = new CertRep(transId, senderNonce, recipientNonce, e.getFailInfo()); } catch (Exception e) { LOGGER.error("Error executing GetCRL request", e); throw new ServletException(e); } } else if (msgType == MessageType.PKCS_REQ) { final PKCS10CertificationRequest certReq = (PKCS10CertificationRequest) msgData; try { LOGGER.debug("Invoking doEnrol"); List<X509Certificate> issued = doEnrol(certReq, transId); if (issued.size() == 0) { certRep = new CertRep(transId, senderNonce, recipientNonce); } else { CMSSignedData messageData = getMessageData(issued); certRep = new CertRep(transId, senderNonce, recipientNonce, messageData); } } catch (OperationFailureException e) { certRep = new CertRep(transId, senderNonce, recipientNonce, e.getFailInfo()); } catch (Exception e) { throw new ServletException(e); } } else { throw new ServletException("Unknown Message for Operation"); } PkcsPkiEnvelopeEncoder envEncoder = new PkcsPkiEnvelopeEncoder(reqCert, "DESede"); PkiMessageEncoder encoder = new PkiMessageEncoder(getSignerKey(), getSigner(), getSignerCertificateChain(), envEncoder); CMSSignedData signedData; try { signedData = encoder.encode(certRep); } catch (MessageEncodingException e) { LOGGER.error("Error decoding response", e); throw new ServletException(e); } res.getOutputStream().write(signedData.getEncoded()); res.getOutputStream().close(); } else { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown Operation"); } } private CMSSignedData getMessageData(final List<X509Certificate> certs) throws IOException, CMSException, GeneralSecurityException { CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); JcaCertStore store; try { store = new JcaCertStore(certs); } catch (CertificateEncodingException e) { IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } generator.addCertificates(store); return generator.generate(new CMSAbsentContent()); } private CMSSignedData getMessageData(final X509CRL crl) throws IOException, CMSException, GeneralSecurityException { CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); JcaCRLStore store; if (crl == null) { store = new JcaCRLStore(Collections.emptyList()); } else { store = new JcaCRLStore(Collections.singleton(crl)); } generator.addCertificates(store); return generator.generate(new CMSAbsentContent()); } private void doGetNextCaCert(final HttpServletRequest req, final HttpServletResponse res) throws Exception { res.setHeader("Content-Type", "application/x-x509-next-ca-cert"); List<X509Certificate> certs = getNextCaCertificate(req.getParameter(MSG_PARAM)); if (certs.size() == 0) { res.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "GetNextCACert Not Supported"); } else { CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); JcaCertStore store; try { store = new JcaCertStore(certs); } catch (CertificateEncodingException e) { IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } generator.addCertificates(store); DigestCalculatorProvider digestProvider = new JcaDigestCalculatorProviderBuilder().build(); SignerInfoGeneratorBuilder infoGenBuilder = new SignerInfoGeneratorBuilder(digestProvider); X509CertificateHolder certHolder = new X509CertificateHolder(getRecipient().getEncoded()); ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA").build(getRecipientKey()); SignerInfoGenerator infoGen = infoGenBuilder.build(contentSigner, certHolder); generator.addSignerInfoGenerator(infoGen); CMSSignedData degenerateSd = generator.generate(new CMSAbsentContent()); byte[] bytes = degenerateSd.getEncoded(); res.getOutputStream().write(bytes); res.getOutputStream().close(); } } private void doGetCaCert(final HttpServletRequest req, final HttpServletResponse res) throws Exception { final List<X509Certificate> certs = doGetCaCertificate(req.getParameter(MSG_PARAM)); final byte[] bytes; if (certs.size() == 0) { res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "GetCaCert failed to obtain CA from store"); bytes = new byte[0]; } else if (certs.size() == 1) { res.setHeader("Content-Type", "application/x-x509-ca-cert"); bytes = certs.get(0).getEncoded(); } else { res.setHeader("Content-Type", "application/x-x509-ca-ra-cert"); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); JcaCertStore store; try { store = new JcaCertStore(certs); } catch (CertificateEncodingException e) { IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } generator.addCertificates(store); CMSSignedData degenerateSd = generator.generate(new CMSAbsentContent()); bytes = degenerateSd.getEncoded(); } res.getOutputStream().write(bytes); res.getOutputStream().close(); } private Operation getOperation(final HttpServletRequest req) { String op = req.getParameter(OP_PARAM); if (op == null) { return null; } return Operation.forName(req.getParameter(OP_PARAM)); } private void doGetCaCaps(final HttpServletRequest req, final HttpServletResponse res) throws Exception { res.setHeader("Content-Type", "text/plain"); final Set<Capability> caps = doCapabilities(req.getParameter("message")); for (Capability cap : caps) { res.getWriter().write(cap.toString()); res.getWriter().write('\n'); } res.getWriter().close(); } /** * Returns the capabilities of the specified CA. * * @param identifier * the CA identifier, which may be an empty string. * @return the capabilities. * @throws Exception * if any problem occurs */ protected abstract Set<Capability> doCapabilities(final String identifier) throws Exception; /** * Returns the certificate chain of the specified CA. * * @param identifier * the CA identifier, which may be an empty string. * @return the CA's certificate. * @throws Exception * if any problem occurs */ protected abstract List<X509Certificate> doGetCaCertificate(String identifier) throws Exception; /** * Return the chain of the next X.509 certificate which will be used by the * specified CA. * * @param identifier * the CA identifier, which may be an empty string. * @return the list of certificates. * @throws Exception * if any problem occurs */ protected abstract List<X509Certificate> getNextCaCertificate(String identifier) throws Exception; /** * Retrieve the certificate chain identified by the given parameters. * * @param issuer * the issuer name. * @param serial * the serial number. * @return the identified certificate, if any. * @throws OperationFailureException * if the operation cannot be completed * @throws Exception * if any problem occurs */ protected abstract List<X509Certificate> doGetCert(final X500Name issuer, final BigInteger serial) throws Exception; /** * Checks to see if a previously-requested certificate has been issued. If * the certificate has been issued, this method will return the appropriate * certificate chain. Otherwise, this method should return null or an empty * list to indicate that the request is still pending. * * @param issuer * the issuer name. * @param subject * the subject name. * @param transId * the transaction ID. * @return the identified certificate, if any. * @throws OperationFailureException * if the operation cannot be completed * @throws Exception * if any problem occurs */ protected abstract List<X509Certificate> doGetCertInitial(final X500Name issuer, final X500Name subject, final TransactionId transId) throws Exception; /** * Retrieve the CRL covering the given certificate identifiers. * * @param issuer * the certificate issuer. * @param serial * the certificate serial number. * @return the CRL. * @throws OperationFailureException * if the operation cannot be completed * @throws Exception * if any problem occurs */ protected abstract X509CRL doGetCrl(final X500Name issuer, final BigInteger serial) throws Exception; /** * Enrols a certificate into the PKI represented by this SCEP interface. If * the request can be completed immediately, this method returns an * appropriate certificate chain. If the request is pending, this method * should return null or any empty list. * * @param certificationRequest * the PKCS #10 CertificationRequest * @param transId * the transaction ID * @return the certificate chain, if any * @throws OperationFailureException * if the operation cannot be completed * @throws Exception * if any problem occurs */ protected abstract List<X509Certificate> doEnrol(final PKCS10CertificationRequest certificationRequest, final TransactionId transId) throws Exception; /** * Returns the private key of the recipient entity represented by this SCEP * server. * * @return the private key. */ protected abstract PrivateKey getRecipientKey(); /** * Returns the certificate of the server recipient entity. * * @return the certificate. */ protected abstract X509Certificate getRecipient(); /** * Returns the private key of the entity represented by this SCEP server. * * @return the private key. */ protected abstract PrivateKey getSignerKey(); /** * Returns the certificate of the entity represented by this SCEP server. * * @return the certificate. */ protected abstract X509Certificate getSigner(); /** * Returns the certificate chain of the entity represented by this SCEP server. * * @return the chain */ protected abstract X509Certificate[] getSignerCertificateChain(); private byte[] getMessageBytes(final HttpServletRequest req) throws IOException { if (req.getMethod().equals(POST)) { return IOUtils.toByteArray(req.getInputStream()); } else { Operation op = getOperation(req); if (op == Operation.PKI_OPERATION) { String msg = req.getParameter(MSG_PARAM); if (msg.length() == 0) { return new byte[0]; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Decoding {}", msg); } return Base64.decode(msg); } else { return new byte[0]; } } } }