/* * * Copyright (c) 2013 - 2017 Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.pki.scep.serveremulator; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.cmp.PKIMessage; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSAbsentContent; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.util.encoders.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.audit.AuditEvent; import org.xipki.commons.audit.AuditLevel; import org.xipki.commons.audit.AuditService; import org.xipki.commons.audit.AuditStatus; import org.xipki.commons.common.util.LogUtil; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.security.util.X509Util; import org.xipki.pki.scep.exception.MessageDecodingException; import org.xipki.pki.scep.message.CaCaps; import org.xipki.pki.scep.message.NextCaMessage; import org.xipki.pki.scep.transaction.CaCapability; import org.xipki.pki.scep.transaction.Operation; import org.xipki.pki.scep.util.ScepConstants; import org.xipki.pki.scep.util.ScepUtil; /** * URL http://host:port/scep/<name>/<profile-alias>/pkiclient.exe * * @author Lijun Liao * @since 2.0.0 */ public class ScepServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(ScepServlet.class); private static final long serialVersionUID = 1L; private static final String CT_RESPONSE = ScepConstants.CT_PKI_MESSAGE; private AuditService auditService; private ScepResponder responder; public ScepServlet(final ScepResponder responder) { this.responder = ParamUtil.requireNonNull("responder", responder); } public AuditService getAuditService() { return auditService; } public void setAuditService(final AuditService auditService) { this.auditService = auditService; } @Override public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { service(request, response, false); } @Override public void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { service(request, response, true); } private void service(final HttpServletRequest request, final HttpServletResponse response, final boolean post) throws ServletException, IOException { String servletPath = request.getServletPath(); AuditEvent event = new AuditEvent(new Date()); event.setApplicationName(ScepAuditConstants.APPNAME); event.setName(ScepAuditConstants.NAME_PERF); event.addEventData(ScepAuditConstants.NAME_servletPath, servletPath); AuditLevel auditLevel = AuditLevel.INFO; AuditStatus auditStatus = AuditStatus.SUCCESSFUL; String auditMessage = null; OutputStream respStream = response.getOutputStream(); try { CaCaps caCaps = responder.getCaCaps(); if (post && !caCaps.containsCapability(CaCapability.POSTPKIOperation)) { final String message = "HTTP POST is not supported"; LOG.error(message); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentLength(0); auditMessage = message; auditStatus = AuditStatus.FAILED; return; } String operation = request.getParameter("operation"); event.addEventData(ScepAuditConstants.NAME_operation, operation); if ("PKIOperation".equalsIgnoreCase(operation)) { CMSSignedData reqMessage; // parse the request try { byte[] content; if (post) { content = ScepUtil.read(request.getInputStream()); } else { String b64 = request.getParameter("message"); content = Base64.decode(b64); } reqMessage = new CMSSignedData(content); } catch (Exception ex) { final String message = "invalid request"; LogUtil.error(LOG, ex, message); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentLength(0); auditMessage = message; auditStatus = AuditStatus.FAILED; return; } ContentInfo ci; try { ci = responder.servicePkiOperation(reqMessage, event); } catch (MessageDecodingException ex) { final String message = "could not decrypt and/or verify the request"; LogUtil.error(LOG, ex, message); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentLength(0); auditMessage = message; auditStatus = AuditStatus.FAILED; return; } catch (CaException ex) { final String message = "system internal error"; LogUtil.error(LOG, ex, message); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentLength(0); auditMessage = message; auditStatus = AuditStatus.FAILED; return; } byte[] respBytes = ci.getEncoded(); response.setContentType(CT_RESPONSE); response.setContentLength(respBytes.length); respStream.write(respBytes); } else if (Operation.GetCACaps.getCode().equalsIgnoreCase(operation)) { // CA-Ident is ignored response.setContentType(ScepConstants.CT_TEXT_PLAIN); byte[] caCapsBytes = responder.getCaCaps().getBytes(); respStream.write(caCapsBytes); response.setContentLength(caCapsBytes.length); } else if (Operation.GetCACert.getCode().equalsIgnoreCase(operation)) { // CA-Ident is ignored byte[] respBytes; String ct; if (responder.getRaEmulator() == null) { ct = ScepConstants.CT_X509_CA_CERT; respBytes = responder.getCaEmulator().getCaCertBytes(); } else { ct = ScepConstants.CT_X509_CA_RA_CERT; CMSSignedDataGenerator cmsSignedDataGen = new CMSSignedDataGenerator(); try { cmsSignedDataGen.addCertificate(new X509CertificateHolder( responder.getCaEmulator().getCaCert())); ct = ScepConstants.CT_X509_CA_RA_CERT; cmsSignedDataGen.addCertificate(new X509CertificateHolder( responder.getRaEmulator().getRaCert())); CMSSignedData degenerateSignedData = cmsSignedDataGen.generate( new CMSAbsentContent()); respBytes = degenerateSignedData.getEncoded(); } catch (CMSException ex) { final String message = "system internal error"; LogUtil.error(LOG, ex, message); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentLength(0); auditMessage = message; auditStatus = AuditStatus.FAILED; return; } } // end if (responder.getRAEmulator() == null) { response.setContentType(ct); response.setContentLength(respBytes.length); respStream.write(respBytes); } else if (Operation.GetNextCACert.getCode().equalsIgnoreCase(operation)) { if (responder.getNextCaAndRa() == null) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentLength(0); auditMessage = "SCEP operation '" + operation + "' is not permitted"; auditStatus = AuditStatus.FAILED; return; } try { NextCaMessage nextCaMsg = new NextCaMessage(); nextCaMsg.setCaCert(X509Util.toX509Cert( responder.getNextCaAndRa().getCaCert())); if (responder.getNextCaAndRa().getRaCert() != null) { X509Certificate raCert = X509Util.toX509Cert( responder.getNextCaAndRa().getRaCert()); nextCaMsg.setRaCerts(Arrays.asList(raCert)); } ContentInfo signedData = responder.encode(nextCaMsg); byte[] respBytes = signedData.getEncoded(); response.setContentType(ScepConstants.CT_X509_NEXT_CA_CERT); response.setContentLength(respBytes.length); response.getOutputStream().write(respBytes); } catch (Exception ex) { final String message = "system internal error"; LogUtil.error(LOG, ex, message); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentLength(0); auditMessage = message; auditStatus = AuditStatus.FAILED; } } else { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentLength(0); auditMessage = "unknown SCEP operation '" + operation + "'"; auditStatus = AuditStatus.FAILED; } // end if ("PKIOperation".equalsIgnoreCase(operation)) } catch (EOFException ex) { final String message = "connection reset by peer"; LogUtil.warn(LOG, ex, message); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentLength(0); } catch (Throwable th) { final String message = "Throwable thrown, this should not happen!"; LogUtil.error(LOG, th, message); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentLength(0); auditLevel = AuditLevel.ERROR; auditStatus = AuditStatus.FAILED; auditMessage = "internal error"; } finally { try { response.flushBuffer(); } finally { audit(auditService, event, auditLevel, auditStatus, auditMessage); } } // end try } // method service protected PKIMessage generatePkiMessage(final InputStream is) throws IOException { ParamUtil.requireNonNull("is", is); ASN1InputStream asn1Stream = new ASN1InputStream(is); try { return PKIMessage.getInstance(asn1Stream.readObject()); } finally { try { asn1Stream.close(); } catch (Exception ex) { LOG.error("could not close stream: {}", ex.getMessage()); } } } static void audit(final AuditService auditService, final AuditEvent event, final AuditLevel auditLevel, final AuditStatus auditStatus, final String auditMessage) { if (auditLevel != null) { event.setLevel(auditLevel); } if (auditStatus != null) { event.setStatus(auditStatus); } if (auditMessage != null) { event.addEventData("message", auditMessage); } event.finish(); auditService.logEvent(event); } }