/*
*
* 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.ca.server.impl.scep;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
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.ASN1OutputStream;
import org.bouncycastle.asn1.cmp.PKIMessage;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.cms.CMSSignedData;
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.AuditServiceRegister;
import org.xipki.commons.audit.AuditStatus;
import org.xipki.commons.common.util.IoUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.RandomUtil;
import org.xipki.pki.ca.api.OperationException;
import org.xipki.pki.ca.api.OperationException.ErrorCode;
import org.xipki.pki.ca.api.RequestType;
import org.xipki.pki.ca.server.impl.CaAuditConstants;
import org.xipki.pki.ca.server.impl.CaManagerImpl;
import org.xipki.pki.ca.server.mgmt.api.CaStatus;
import org.xipki.pki.scep.exception.MessageDecodingException;
import org.xipki.pki.scep.transaction.Operation;
import org.xipki.pki.scep.util.ScepConstants;
/**
* URL http://host:port/scep/<name>/<profile-alias>/pkiclient.ext
*
* @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 CGI_PROGRAM = "/pkiclient.exe";
private static final int CGI_PROGRAM_LEN = CGI_PROGRAM.length();
private static final String CT_RESPONSE = ScepConstants.CT_PKI_MESSAGE;
private AuditServiceRegister auditServiceRegister;
private CaManagerImpl responderManager;
public ScepServlet() {
}
@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 requestUri = request.getRequestURI();
String servletPath = request.getServletPath();
int len = servletPath.length();
String scepName = null;
String certProfileName = null;
if (requestUri.length() > len + 1) {
String scepPath = URLDecoder.decode(requestUri.substring(len + 1), "UTF-8");
if (scepPath.endsWith(CGI_PROGRAM)) {
String path = scepPath.substring(0, scepPath.length() - CGI_PROGRAM_LEN);
String[] tokens = path.split("/");
if (tokens.length == 2) {
scepName = tokens[0];
certProfileName = tokens[1].toUpperCase();
}
} // end if
} // end if
if (scepName == null || certProfileName == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
AuditService auditService = auditServiceRegister.getAuditService();
AuditEvent event = new AuditEvent(new Date());
event.setApplicationName("SCEP");
event.setName(CaAuditConstants.NAME_PERF);
event.addEventData(CaAuditConstants.NAME_SCEP_name, scepName + "/" + certProfileName);
event.addEventData(CaAuditConstants.NAME_reqType, RequestType.SCEP.name());
String msgId = RandomUtil.nextHexLong();
event.addEventData(CaAuditConstants.NAME_mid, msgId);
AuditLevel auditLevel = AuditLevel.INFO;
AuditStatus auditStatus = AuditStatus.SUCCESSFUL;
String auditMessage = null;
try {
if (responderManager == null) {
auditMessage = "responderManager in servlet not configured";
LOG.error(auditMessage);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentLength(0);
auditLevel = AuditLevel.ERROR;
auditStatus = AuditStatus.FAILED;
return;
}
Scep responder = responderManager.getScep(scepName);
if (responder == null || responder.getStatus() != CaStatus.ACTIVE
|| !responder.supportsCertProfile(certProfileName)) {
auditMessage = "unknown SCEP '" + scepName + "/" + certProfileName + "'";
LOG.warn(auditMessage);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.setContentLength(0);
auditStatus = AuditStatus.FAILED;
return;
}
String operation = request.getParameter("operation");
event.addEventData(CaAuditConstants.NAME_SCEP_operation, operation);
if ("PKIOperation".equalsIgnoreCase(operation)) {
CMSSignedData reqMessage;
// parse the request
try {
byte[] content;
if (post) {
content = IoUtil.read(request.getInputStream());
} else {
String b64 = request.getParameter("message");
content = Base64.decode(b64);
}
reqMessage = new CMSSignedData(content);
} catch (Exception ex) {
final String msg = "invalid request";
LogUtil.error(LOG, ex, msg);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentLength(0);
auditMessage = msg;
auditStatus = AuditStatus.FAILED;
return;
}
ContentInfo ci;
try {
ci = responder.servicePkiOperation(reqMessage, certProfileName, msgId, event);
} catch (MessageDecodingException ex) {
final String msg = "could not decrypt and/or verify the request";
LogUtil.error(LOG, ex, msg);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentLength(0);
auditMessage = msg;
auditStatus = AuditStatus.FAILED;
return;
} catch (OperationException ex) {
ErrorCode code = ex.getErrorCode();
int httpCode;
switch (code) {
case ALREADY_ISSUED:
case CERT_REVOKED:
case CERT_UNREVOKED:
httpCode = HttpServletResponse.SC_FORBIDDEN;
break;
case BAD_CERT_TEMPLATE:
case BAD_REQUEST:
case BAD_POP:
case INVALID_EXTENSION:
case UNKNOWN_CERT:
case UNKNOWN_CERT_PROFILE:
httpCode = HttpServletResponse.SC_BAD_REQUEST;
break;
case NOT_PERMITTED:
httpCode = HttpServletResponse.SC_UNAUTHORIZED;
break;
case SYSTEM_UNAVAILABLE:
httpCode = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
break;
case CRL_FAILURE:
case DATABASE_FAILURE:
case SYSTEM_FAILURE:
httpCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
break;
default:
httpCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
break;
}
final String msg = ex.getMessage();
LogUtil.error(LOG, ex, msg);
response.setStatus(httpCode);
response.setContentLength(0);
auditMessage = msg;
auditStatus = AuditStatus.FAILED;
return;
}
response.setContentType(CT_RESPONSE);
ASN1OutputStream asn1Out = new ASN1OutputStream(response.getOutputStream());
asn1Out.writeObject(ci);
asn1Out.flush();
} else if (Operation.GetCACaps.getCode().equalsIgnoreCase(operation)) {
// CA-Ident is ignored
response.setContentType(ScepConstants.CT_TEXT_PLAIN);
byte[] caCapsBytes = responder.getCaCaps().getBytes();
response.getOutputStream().write(caCapsBytes);
} else if (Operation.GetCACert.getCode().equalsIgnoreCase(operation)) {
// CA-Ident is ignored
byte[] respBytes = responder.getCaCertResp().getBytes();
response.setContentType(ScepConstants.CT_X509_CA_RA_CERT);
response.setContentLength(respBytes.length);
response.getOutputStream().write(respBytes);
} else if (Operation.GetNextCACert.getCode().equalsIgnoreCase(operation)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentLength(0);
auditMessage = "SCEP operation '" + operation + "' is not permitted";
auditStatus = AuditStatus.FAILED;
return;
} else {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentLength(0);
auditMessage = "unknown SCEP operation '" + operation + "'";
auditStatus = AuditStatus.FAILED;
return;
}
} catch (EOFException ex) {
final String msg = "connection reset by peer";
if (LOG.isWarnEnabled()) {
LogUtil.warn(LOG, ex, msg);
}
LOG.debug(msg, ex);
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);
}
}
} // method service
protected PKIMessage generatePkiMessage(final InputStream is) throws IOException {
ASN1InputStream asn1Stream = new ASN1InputStream(is);
try {
return PKIMessage.getInstance(asn1Stream.readObject());
} finally {
try {
asn1Stream.close();
} catch (Exception ex) {
LOG.error("could not close ASN1 stream: {}", asn1Stream);
}
}
} // method generatePKIMessage
public void setResponderManager(final CaManagerImpl responderManager) {
this.responderManager = responderManager;
}
public void setAuditServiceRegister(final AuditServiceRegister auditServiceRegister) {
this.auditServiceRegister = auditServiceRegister;
}
private static void audit(final AuditService auditService, final AuditEvent event,
final AuditLevel auditLevel, final AuditStatus auditStatus, final String auditMessage) {
event.setLevel(auditLevel);
if (auditStatus != null) {
event.setStatus(auditStatus);
}
if (auditMessage != null) {
event.addEventData(CaAuditConstants.NAME_message, auditMessage);
}
event.finish();
auditService.logEvent(event);
} // method audit
}