001: /*************************************************************************
002: * *
003: * EJBCA: The OpenSource Certificate Authority *
004: * *
005: * This software is free software; you can redistribute it and/or *
006: * modify it under the terms of the GNU Lesser General Public *
007: * License as published by the Free Software Foundation; either *
008: * version 2.1 of the License, or any later version. *
009: * *
010: * See terms of license at gnu.org. *
011: * *
012: *************************************************************************/package org.ejbca.ui.web.protocol;
013:
014: import java.io.ByteArrayOutputStream;
015: import java.io.IOException;
016: import java.security.cert.X509Certificate;
017: import java.util.Collection;
018: import java.util.Iterator;
019:
020: import javax.ejb.EJBException;
021: import javax.servlet.ServletConfig;
022: import javax.servlet.ServletException;
023: import javax.servlet.ServletInputStream;
024: import javax.servlet.http.HttpServlet;
025: import javax.servlet.http.HttpServletRequest;
026: import javax.servlet.http.HttpServletResponse;
027:
028: import org.apache.commons.lang.StringUtils;
029: import org.apache.log4j.Logger;
030: import org.ejbca.core.ejb.ServiceLocator;
031: import org.ejbca.core.ejb.ca.caadmin.ICAAdminSessionLocal;
032: import org.ejbca.core.ejb.ca.caadmin.ICAAdminSessionLocalHome;
033: import org.ejbca.core.ejb.ca.sign.ISignSessionLocal;
034: import org.ejbca.core.ejb.ca.sign.ISignSessionLocalHome;
035: import org.ejbca.core.model.InternalResources;
036: import org.ejbca.core.model.authorization.AuthorizationDeniedException;
037: import org.ejbca.core.model.ca.AuthLoginException;
038: import org.ejbca.core.model.ca.AuthStatusException;
039: import org.ejbca.core.model.ca.caadmin.CADoesntExistsException;
040: import org.ejbca.core.model.ca.caadmin.CAInfo;
041: import org.ejbca.core.model.log.Admin;
042: import org.ejbca.ui.web.RequestHelper;
043: import org.ejbca.util.Base64;
044: import org.ejbca.util.CertTools;
045:
046: /**
047: * Servlet implementing server side of the Simple Certificate Enrollment Protocol (SCEP)
048: * -----
049: * This processes does the following:
050: * 1. decode a PKCS#7 signed data message from the standard input
051: * 2. extract the signed attributes from the the message, which indicate the type of request
052: * 3. decrypt the enveloped data PKCS#7 inside
053: * 4. branch to different actions depending on the type of the message:
054: * - PKCSReq
055: * - GetCertInitial
056: * - GetCert
057: * - GetCRL
058: * - v2PKCSReq or Proxy request
059: * 5. envelop (PKCS#7) the reply data from the previous step
060: * 6. sign the reply data (PKCS#7) from the previous step
061: * 7. output the result as a der encoded block on stdout
062: * -----
063: *
064: * @version $Id: ScepServlet.java,v 1.10 2008/01/14 15:10:11 anatom Exp $
065: */
066: public class ScepServlet extends HttpServlet {
067: private static final Logger log = Logger
068: .getLogger(ScepServlet.class);
069: /** Internal localization of logs and errors */
070: private static final InternalResources intres = InternalResources
071: .getInstance();
072:
073: private ISignSessionLocal signsession = null;
074: private ICAAdminSessionLocal casession = null;
075:
076: private synchronized ISignSessionLocal getSignSession() {
077: if (signsession == null) {
078: try {
079: ISignSessionLocalHome signhome = (ISignSessionLocalHome) ServiceLocator
080: .getInstance().getLocalHome(
081: ISignSessionLocalHome.COMP_NAME);
082: signsession = signhome.create();
083: } catch (Exception e) {
084: throw new EJBException(e);
085: }
086: }
087: return signsession;
088: }
089:
090: private synchronized ICAAdminSessionLocal getCASession() {
091: if (casession == null) {
092: try {
093: ICAAdminSessionLocalHome cahome = (ICAAdminSessionLocalHome) ServiceLocator
094: .getInstance().getLocalHome(
095: ICAAdminSessionLocalHome.COMP_NAME);
096: casession = cahome.create();
097: } catch (Exception e) {
098: throw new EJBException(e);
099: }
100: }
101: return casession;
102: }
103:
104: /**
105: * Inits the SCEP servlet
106: *
107: * @param config servlet configuration
108: *
109: * @throws ServletException on error during initialization
110: */
111: public void init(ServletConfig config) throws ServletException {
112: super .init(config);
113: try {
114: // Install BouncyCastle provider
115: CertTools.installBCProvider();
116: } catch (Exception e) {
117: throw new ServletException(e);
118: }
119: }
120:
121: /**
122: * Handles HTTP post
123: *
124: * @param request java standard arg
125: * @param response java standard arg
126: *
127: * @throws IOException input/output error
128: * @throws ServletException if the post could not be handled
129: */
130: public void doPost(HttpServletRequest request,
131: HttpServletResponse response) throws IOException,
132: ServletException {
133: log.debug(">doPost()");
134: /*
135: If the remote CA supports it, any of the PKCS#7-encoded SCEP messages
136: may be sent via HTTP POST instead of HTTP GET. This is allowed for
137: any SCEP message except GetCACert, GetCACertChain, GetNextCACert,
138: or GetCACaps. In this form of the message, Base 64 encoding is not
139: used.
140:
141: POST /cgi-bin/pkiclient.exe?operation=PKIOperation
142: <binary PKCS7 data>
143: */
144: String operation = "PKIOperation";
145: ServletInputStream sin = request.getInputStream();
146: // This small code snippet is inspired/copied by apache IO utils to Tomas Gustavsson...
147: ByteArrayOutputStream output = new ByteArrayOutputStream();
148: byte[] buf = new byte[1024];
149: int n = 0;
150: while (-1 != (n = sin.read(buf))) {
151: output.write(buf, 0, n);
152: }
153: String message = new String(Base64.encode(output.toByteArray()));
154: service(operation, message, request.getRemoteAddr(), response);
155: log.debug("<doPost()");
156: } //doPost
157:
158: /**
159: * Handles HTTP get
160: *
161: * @param request java standard arg
162: * @param response java standard arg
163: *
164: * @throws IOException input/output error
165: * @throws ServletException if the post could not be handled
166: */
167: public void doGet(HttpServletRequest request,
168: HttpServletResponse response) throws java.io.IOException,
169: ServletException {
170: log.debug(">doGet()");
171:
172: log.debug("query string=" + request.getQueryString());
173:
174: // These are mandatory in SCEP GET
175: /*
176: GET /cgi-bin/pkiclient.exe?operation=PKIOperation&message=MIAGCSqGSIb3D
177: QEHA6CAMIACAQAxgDCBzAIBADB2MGIxETAPBgNVBAcTCE ......AAAAAA==
178: */
179: String operation = request.getParameter("operation");
180: String message = request.getParameter("message");
181: // Some clients don't url encode the + sign in the request
182: if (message != null) {
183: message = message.replace(' ', '+');
184: }
185:
186: service(operation, message, request.getRemoteAddr(), response);
187:
188: log.debug("<doGet()");
189: } // doGet
190:
191: private void service(String operation, String message,
192: String remoteAddr, HttpServletResponse response)
193: throws IOException {
194: try {
195: if ((operation == null) || (message == null)) {
196: String errMsg = intres.getLocalizedMessage(
197: "scep.errormissingparam", remoteAddr);
198: log.error(errMsg);
199: response.sendError(HttpServletResponse.SC_BAD_REQUEST,
200: errMsg);
201: return;
202: }
203:
204: Admin administrator = new Admin(Admin.TYPE_PUBLIC_WEB_USER,
205: remoteAddr);
206: log.debug("Got request '" + operation + "'");
207: log.debug("Message: " + message);
208: String iMsg = intres.getLocalizedMessage(
209: "scep.receivedmsg", remoteAddr);
210: log.info(iMsg);
211: if (operation.equals("PKIOperation")) {
212: byte[] scepmsg = Base64.decode(message.getBytes());
213: ISignSessionLocal signsession = getSignSession();
214: ScepPkiOpHelper helper = new ScepPkiOpHelper(
215: administrator, signsession);
216:
217: // Read the message end get the cert, this also checksauthorization
218: boolean includeCACert = true;
219: if (StringUtils.equals("0",
220: getInitParameter("includeCACert"))) {
221: includeCACert = false;
222: }
223: byte[] reply = helper.scepCertRequest(scepmsg,
224: includeCACert);
225: if (reply == null) {
226: // This is probably a getCert message?
227: response.sendError(
228: HttpServletResponse.SC_NOT_IMPLEMENTED,
229: "Can not handle request");
230: return;
231: }
232: // Send back Scep response, PKCS#7 which contains the end entity's certificate (or failure)
233: RequestHelper.sendBinaryBytes(reply, response,
234: "application/x-pki-message", null);
235: iMsg = intres.getLocalizedMessage(
236: "scep.sentresponsemsg", "PKIOperation",
237: remoteAddr);
238: log.info(iMsg);
239: } else if (operation.equals("GetCACert")) {
240: // The response has the content type tagged as application/x-x509-ca-cert.
241: // The body of the response is a DER encoded binary X.509 certificate.
242: // For example: "Content-Type:application/x-x509-ca-cert\n\n"<BER-encoded X509>
243:
244: // CA_IDENT is the message for this request to indicate which CA we are talking about
245: log.debug("Got SCEP cert request for CA '" + message
246: + "'");
247: Collection certs = null;
248: ICAAdminSessionLocal caadminsession = getCASession();
249: CAInfo cainfo = caadminsession.getCAInfo(administrator,
250: message);
251: if (cainfo != null) {
252: certs = cainfo.getCertificateChain();
253: }
254: if ((certs != null) && (certs.size() > 0)) {
255: // CAs certificate is in the first position in the Collection
256: Iterator iter = certs.iterator();
257: X509Certificate cert = (X509Certificate) iter
258: .next();
259: log.debug("Sent certificate for CA '" + message
260: + "' to SCEP client.");
261: RequestHelper.sendNewX509CaCert(cert.getEncoded(),
262: response);
263: iMsg = intres.getLocalizedMessage(
264: "scep.sentresponsemsg", "GetCACert",
265: remoteAddr);
266: log.info(iMsg);
267: } else {
268: String errMsg = intres.getLocalizedMessage(
269: "scep.errorunknownca", "cert");
270: log.error(errMsg);
271: response.sendError(
272: HttpServletResponse.SC_NOT_FOUND,
273: "No CA certificates found.");
274: }
275: } else if (operation.equals("GetCACertChain")) {
276: // The response for GetCACertChain is a certificates-only PKCS#7
277: // SignedDatato carry the certificates to the end entity, with a
278: // Content-Type of application/x-x509-ca-ra-cert-chain.
279:
280: // CA_IDENT is the message for this request to indicate which CA we are talking about
281: log.debug("Got SCEP pkcs7 request for CA '" + message
282: + "'");
283: ICAAdminSessionLocal caadminsession = getCASession();
284: CAInfo cainfo = caadminsession.getCAInfo(administrator,
285: message);
286: ISignSessionLocal signsession = getSignSession();
287: byte[] pkcs7 = signsession.createPKCS7(administrator,
288: cainfo.getCAId(), true);
289: if ((pkcs7 != null) && (pkcs7.length > 0)) {
290: log.debug("Sent PKCS7 for CA '" + message
291: + "' to SCEP client.");
292: RequestHelper
293: .sendBinaryBytes(
294: pkcs7,
295: response,
296: "application/x-x509-ca-ra-cert-chain",
297: null);
298: iMsg = intres.getLocalizedMessage(
299: "scep.sentresponsemsg", "GetCACertChain",
300: remoteAddr);
301: log.info(iMsg);
302: } else {
303: String errMsg = intres.getLocalizedMessage(
304: "scep.errorunknownca", "pkcs7");
305: log.error(errMsg);
306: response.sendError(
307: HttpServletResponse.SC_NOT_FOUND,
308: "No CA certificates found.");
309: }
310: } else if (operation.equals("GetCACaps")) {
311: // The response for GetCACaps is a <lf> separated list of capabilities
312:
313: /*
314: "GetNextCACert" CA Supports the GetNextCACert message.
315: "POSTPKIOperation" PKIOPeration messages may be sent via HTTP POST.
316: "SHA-1" CA Supports the SHA-1 hashing algorithm in
317: signatures and fingerprints. If present, the
318: client SHOULD use SHA-1. If absent, the client
319: MUST use MD5 to maintain backward compatability.
320: "Renewal" Clients may use current certificate and key to
321: authenticate an enrollment request for a new
322: certificate.
323: */
324: log.debug("Got SCEP CACaps request for CA '" + message
325: + "'");
326: response.setContentType("text/plain");
327: response.getOutputStream().print(
328: "POSTPKIOperation\nSHA-1");
329: } else {
330: log.error("Invalid parameter '" + operation);
331: // TODO: Send back proper Failure Response
332: response.sendError(HttpServletResponse.SC_BAD_REQUEST,
333: "Invalid parameter: " + operation);
334: }
335: } catch (CADoesntExistsException cae) {
336: String errMsg = intres.getLocalizedMessage(
337: "scep.errorunknownca", "cert");
338: log.error(errMsg, cae);
339: // TODO: Send back proper Failure Response
340: response.sendError(HttpServletResponse.SC_NOT_FOUND, cae
341: .getMessage());
342: } catch (java.lang.ArrayIndexOutOfBoundsException ae) {
343: String errMsg = intres
344: .getLocalizedMessage("scep.errorinvalidreq");
345: log.error(errMsg, ae);
346: // TODO: Send back proper Failure Response
347: response.sendError(HttpServletResponse.SC_BAD_REQUEST, ae
348: .getMessage());
349: } catch (AuthorizationDeniedException ae) {
350: String errMsg = intres
351: .getLocalizedMessage("scep.errorauth");
352: log.error(errMsg, ae);
353: // TODO: Send back proper Failure Response
354: response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ae
355: .getMessage());
356: } catch (AuthLoginException ae) {
357: String errMsg = intres
358: .getLocalizedMessage("scep.errorauth");
359: log.error(errMsg, ae);
360: // TODO: Send back proper Failure Response
361: response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ae
362: .getMessage());
363: } catch (AuthStatusException ae) {
364: String errMsg = intres
365: .getLocalizedMessage("scep.errorclientstatus");
366: log.error(errMsg, ae);
367: // TODO: Send back proper Failure Response
368: response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ae
369: .getMessage());
370: } catch (Exception e) {
371: String errMsg = intres
372: .getLocalizedMessage("scep.errorgeneral");
373: log.error(errMsg, e);
374: // TODO: Send back proper Failure Response
375: response.sendError(HttpServletResponse.SC_BAD_REQUEST, e
376: .getMessage());
377: }
378: }
379:
380: } // ScepServlet
|