/*************************************************************************
* *
* 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.ui.web.pub;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.ObjectNotFoundException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.cesecore.core.ejb.ca.store.CertificateProfileSessionLocal;
import org.ejbca.core.ejb.ca.caadmin.CAAdminSession;
import org.ejbca.core.ejb.ca.caadmin.CAAdminSessionLocal;
import org.ejbca.core.ejb.ca.sign.SignSession;
import org.ejbca.core.ejb.ca.sign.SignSessionLocal;
import org.ejbca.core.ejb.ca.store.CertificateStoreSessionLocal;
import org.ejbca.core.ejb.hardtoken.HardTokenSessionLocal;
import org.ejbca.core.ejb.ra.UserAdminSessionLocal;
import org.ejbca.core.model.SecConst;
import org.ejbca.core.model.approval.ApprovalException;
import org.ejbca.core.model.approval.WaitingForApprovalException;
import org.ejbca.core.model.ca.AuthLoginException;
import org.ejbca.core.model.ca.AuthStatusException;
import org.ejbca.core.model.ca.IllegalKeyException;
import org.ejbca.core.model.ca.SignRequestException;
import org.ejbca.core.model.ca.SignRequestSignatureException;
import org.ejbca.core.model.ca.caadmin.CAInfo;
import org.ejbca.core.model.ca.crl.RevokedCertInfo;
import org.ejbca.core.model.hardtoken.profiles.EIDProfile;
import org.ejbca.core.model.hardtoken.profiles.HardTokenProfile;
import org.ejbca.core.model.hardtoken.profiles.SwedishEIDProfile;
import org.ejbca.core.model.log.Admin;
import org.ejbca.core.model.ra.UserDataConstants;
import org.ejbca.core.model.ra.UserDataVO;
import org.ejbca.core.protocol.IResponseMessage;
import org.ejbca.core.protocol.PKCS10RequestMessage;
import org.ejbca.util.Base64;
import org.ejbca.util.CertTools;
import org.ejbca.util.CryptoProviderTools;
import org.ejbca.util.RequestMessageUtils;
/**
* Servlet used to install a private key with a corresponding certificate in a browser. A new
* certificate is installed in the browser in following steps:<br>
* 1. The key pair is generated by the browser. <br>
* 2. The public part is sent to the servlet in a POST together with user info ("pkcs10|keygen",
* "inst", "user", "password"). For internet explorer the public key is sent as a PKCS10
* certificate request. <br>
* 3. The new certificate is created by calling the RSASignSession session bean. <br>
* 4. A page containing the new certificate and a script that installs it is returned to the
* browser. <br>
*
* <p></p>
*
* <p>
* The following initiation parameters are needed by this servlet: <br>
* "responseTemplate" file that defines the response to the user (IE). It should have one line
* with the text "cert =". This line is replaced with the new certificate. "keyStorePass".
* Password needed to load the key-store. If this parameter is none existing it is assumed that no
* password is needed. The path could be absolute or relative.<br>
* </p>
*
* @author Original code by Lars Silven
* @version $Id: CardCertReqServlet.java 11601 2011-03-24 18:29:15Z jeklund $
*/
public class CardCertReqServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final static Logger log = Logger.getLogger(CardCertReqServlet.class);
@EJB
private CAAdminSessionLocal caAdminSession;
@EJB
private CertificateStoreSessionLocal certificateStoreSession;
@EJB
private CertificateProfileSessionLocal certificateProfileSession;
@EJB
private HardTokenSessionLocal hardTokenSession;
@EJB
private SignSessionLocal signSession;
@EJB
private UserAdminSessionLocal userAdminSession;
/**
* Servlet init
*
* @param config servlet configuration
*
* @throws ServletException on error
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
// Install BouncyCastle provider
CryptoProviderTools.installBCProvider();
} catch( Exception e ) {
throw new ServletException(e);
}
}
/**
* Handles HTTP POST
*
* @param request servlet request
* @param response servlet response
*
* @throws IOException input/output error
* @throws ServletException on error
*/
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
final ServletDebug debug = new ServletDebug(request, response);
boolean usekeyrecovery = false;
try {
Admin administrator = new Admin(Admin.TYPE_RA_USER);
final String username; {
Object o = request.getAttribute("javax.servlet.request.X509Certificate");
final X509Certificate[] certs;
if ( o!=null && o instanceof X509Certificate[] ) {
certs = (X509Certificate[])o;
}
else {
throw new AuthLoginException("No authenicating certificate");
}
boolean isRevoked = certificateStoreSession.isRevoked(certs[0].getIssuerDN().getName(),certs[0].getSerialNumber());
if (isRevoked) {
throw new UserCertificateRevokedException(certs[0]);
}
username = certificateStoreSession.findUsernameByCertSerno(administrator, certs[0].getSerialNumber(), certs[0].getIssuerX500Principal().toString());
if ( username==null || username.length()==0 ) {
throw new ObjectNotFoundException("Not possible to retrieve user name");
}
}
log.debug("Got request for " + username + ".");
debug.print("<h3>username: " + username + "</h3>");
final UserDataVO data = userAdminSession.findUser(administrator, username);
final X509Certificate notRevokedCerts[]; {
Set<X509Certificate> set = new HashSet<X509Certificate>();
for( Iterator<java.security.cert.Certificate> i = certificateStoreSession.findCertificatesByUsername(administrator, username).iterator(); i.hasNext(); ) {
Object o = i.next();
if ( o instanceof X509Certificate ) {
X509Certificate cert = (X509Certificate)o;
boolean isRevoked = certificateStoreSession.isRevoked(cert.getIssuerDN().getName(), cert.getSerialNumber());
if (!isRevoked) {
set.add(cert);
}
}
}
notRevokedCerts = (X509Certificate[])set.toArray(new X509Certificate[0]);
}
if (data == null) {
throw new ObjectNotFoundException();
}
final String authReq = request.getParameter("authpkcs10");
final String signReq = request.getParameter("signpkcs10");
if ( authReq!=null && signReq!=null ) {
final int authCertProfile;
final int signCertProfile;
final HardTokenProfile hardTokenProfile = hardTokenSession.getHardTokenProfile(administrator, data.getTokenType());
{
CertProfileID certProfileID = new CertProfileID(data, administrator, hardTokenProfile);
authCertProfile = certProfileID.getProfileID("authCertProfile", SwedishEIDProfile.CERTUSAGE_AUTHENC);
signCertProfile = certProfileID.getProfileID("signCertProfile", SwedishEIDProfile.CERTUSAGE_SIGN);
}
final int authCA;
final int signCA;
{
CAID caid = new CAID(data,administrator, hardTokenProfile, caAdminSession);
authCA = caid.getProfileID("authCA", SwedishEIDProfile.CERTUSAGE_AUTHENC);
signCA = caid.getProfileID("signCA", SwedishEIDProfile.CERTUSAGE_SIGN);
}
// if not IE, check if it's manual request
final byte[] authReqBytes = authReq.getBytes();
final byte[] signReqBytes = signReq.getBytes();
if ( authReqBytes!=null && signReqBytes!=null) {
try {
userAdminSession.changeUser(administrator, username,data.getPassword(), data.getDN(), data.getSubjectAltName(),
data.getEmail(), true, data.getEndEntityProfileId(), authCertProfile, data.getType(),
SecConst.TOKEN_SOFT_BROWSERGEN, 0, data.getStatus(), authCA);
final byte[] authb64cert=pkcs10CertRequest(administrator, signSession, authReqBytes, username, data.getPassword());
userAdminSession.changeUser(administrator, username, data.getPassword(), data.getDN(), data.getSubjectAltName(),
data.getEmail(), true, data.getEndEntityProfileId(), signCertProfile, data.getType(),
SecConst.TOKEN_SOFT_BROWSERGEN, 0, UserDataConstants.STATUS_NEW, signCA);
final byte[] signb64cert=pkcs10CertRequest(administrator, signSession, signReqBytes, username, data.getPassword());
for (int i=0; i<notRevokedCerts.length; i++) {
try {
userAdminSession.revokeCert(administrator, notRevokedCerts[i].getSerialNumber(),
notRevokedCerts[i].getIssuerDN().toString(), RevokedCertInfo.REVOCATION_REASON_SUPERSEDED);
} catch (WaitingForApprovalException e) {
log.info("A request for approval to revoke " + username + "'s old certificate "+
notRevokedCerts[i].getSerialNumber().toString(16)+" was added.");
} catch (ApprovalException e) {
log.info("A request for approval to revoke " + username + "'s old certificate "+
notRevokedCerts[i].getSerialNumber().toString(16)+" already exists.");
}
}
sendCertificates(authb64cert, signb64cert, response, getServletContext(),
getInitParameter("responseTemplate"), notRevokedCerts);
} catch( Throwable t ) {
if (t instanceof Exception) {
throw (Exception)t;
}
else {
throw new Error(t);
}
} finally {
data.setStatus(UserDataConstants.STATUS_GENERATED);
userAdminSession.changeUser(administrator, data, true); // set back to original values
}
}
}
} catch( UserCertificateRevokedException e) {
log.error("An error revoking certificaates occured: ", e);
debug.printMessage(e.getMessage());
debug.printDebugInfo();
return;
} catch (ObjectNotFoundException oe) {
log.error("Non existent username!", oe);
debug.printMessage("Non existent username!");
debug.printDebugInfo();
return;
} catch (AuthStatusException ase) {
log.error("Wrong user status!", ase);
debug.printMessage("Wrong user status!");
if (usekeyrecovery) {
debug.printMessage(
"To generate a certificate for a user the user must have status new, failed or inprocess.");
} else {
debug.printMessage(
"To generate a certificate for a user the user must have status new, failed or inprocess.");
}
debug.printDebugInfo();
return;
} catch (AuthLoginException ale) {
log.error("Wrong password for user!", ale);
debug.printMessage("Wrong username or password!");
debug.printDebugInfo();
return;
} catch (SignRequestException re) {
log.error("Invalid request!", re);
debug.printMessage("Invalid request!");
debug.printMessage("Please supply a correct request.");
debug.printDebugInfo();
return;
} catch (SignRequestSignatureException se) {
log.error("Invalid signature on certificate request!", se);
debug.printMessage("Invalid signature on certificate request!");
debug.printMessage("Please supply a correctly signed request.");
debug.printDebugInfo();
return;
} catch (java.lang.ArrayIndexOutOfBoundsException ae) {
log.error("Empty or invalid request received.", ae);
debug.printMessage("Empty or invalid request!");
debug.printMessage("Please supply a correct request.");
debug.printDebugInfo();
return;
} catch (IllegalKeyException e) {
log.error("Illegal Key received: ", e);
debug.printMessage("Invalid Key in request: "+e.getMessage());
debug.printMessage("Please supply a correct request.");
debug.printDebugInfo();
return;
} catch (Exception e) {
log.error("Exception occured: ", e);
debug.print("<h3>parameter name and values: </h3>");
Enumeration paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String name = paramNames.nextElement().toString();
String parameter = request.getParameter(name);
debug.print("<h4>" + name + ":</h4>" + parameter + "<br>");
}
debug.takeCareOfException(e);
debug.printDebugInfo();
}
} //doPost
private class UserCertificateRevokedException extends Exception {
private static final long serialVersionUID = 1L;
UserCertificateRevokedException(X509Certificate cert) {
super("User certificate with serial number "+cert.getSerialNumber() +
" from issuer \'"+cert.getIssuerX500Principal()+"\' is revoked.");
}
}
private class CAID extends BaseID {
final private CAAdminSession caAdminSession;
CAID(UserDataVO d, Admin a, HardTokenProfile hardTokenProfile, CAAdminSession caAdminSession) {
super(d, a, hardTokenProfile);
this.caAdminSession = caAdminSession;
}
protected int getFromName(String name) {
CAInfo caInfo = caAdminSession.getCAInfo(administrator, name);
if ( caInfo!=null ) {
return caInfo.getCAId();
} else {
return 0;
}
}
protected int getFromOldData() {
return data.getCAId();
}
protected int getFromHardToken(int keyType) {
final int id = hardTokenProfile.getCAId(keyType);
if ( id!=EIDProfile.CAID_USEUSERDEFINED ) {
return id;
} else {
return data.getCAId();
}
}
}
private class CertProfileID extends BaseID {
CertProfileID(UserDataVO d, Admin a,
HardTokenProfile hardTokenProfile) {
super(d, a, hardTokenProfile);
}
protected int getFromName(String name) {
return certificateProfileSession.getCertificateProfileId(administrator, name);
}
protected int getFromOldData() {
return data.getCertificateProfileId();
}
protected int getFromHardToken(int keyType) {
return hardTokenProfile.getCertificateProfileId(keyType);
}
}
private abstract class BaseID {
final UserDataVO data;
final Admin administrator;
final EIDProfile hardTokenProfile;
protected abstract int getFromHardToken(int keyType);
protected abstract int getFromName(String name);
protected abstract int getFromOldData();
BaseID(UserDataVO d, Admin a, HardTokenProfile htp) {
data = d;
administrator = a;
if ( htp!=null && htp instanceof EIDProfile ) {
hardTokenProfile = (EIDProfile)htp;
} else {
hardTokenProfile = null;
}
}
public int getProfileID(String parameterName, int keyType) {
if ( hardTokenProfile!=null ) {
return getFromHardToken(keyType);
}
String name = CardCertReqServlet.this.getInitParameter(parameterName);
if ( name!=null && name.length()>0 ) {
final int id = getFromName(name);
log.debug("parameter name "+parameterName+" has ID "+id);
if (id!=0) {
return id;
}
}
return getFromOldData();
}
}
/**
* Handles HTTP GET
*
* @param request servlet request
* @param response servlet response
*
* @throws IOException input/output error
* @throws ServletException on error
*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
log.trace(">doGet()");
response.setHeader("Allow", "POST");
ServletDebug debug = new ServletDebug(request, response);
debug.print("The certificate request servlet only handles POST method.");
debug.printDebugInfo();
log.trace("<doGet()");
}
/**
* Reads template and inserts cert to send back to netid for installation of cert
*
* @param b64cert cert to be installed in netid
* @param response utput stream to send to
* @param sc serveltcontext
* @param responseTemplate path to responseTemplate
* @param notRevokedCerts
* @param classid replace
*
* @throws Exception on error
*/
private static void sendCertificates(byte[] authb64cert,byte[] signb64cert, HttpServletResponse response, ServletContext sc,
String responseTemplate, X509Certificate[] notRevokedCerts) throws Exception {
if (authb64cert.length == 0 || signb64cert.length == 0) {
log.error("0 length certificate can not be sent to client!");
return;
}
StringWriter sw = new StringWriter();
{
BufferedReader br = new BufferedReader(new InputStreamReader(sc.getResourceAsStream(responseTemplate)));
PrintWriter pw = new PrintWriter(sw);
while (true) {
String line = br.readLine();
if (line == null) {
break;
}
line = line.replaceAll("TAG_authb64cert",new String(authb64cert));
line = line.replaceAll("TAG_signb64cert",new String(signb64cert));
if ( notRevokedCerts.length > 0 ) {
line = line.replaceAll("TAG_certToRemove1",new String(Base64.encode(notRevokedCerts[0].getEncoded(),false)));
}
if ( notRevokedCerts.length > 1 ) {
line = line.replaceAll("TAG_certToRemove2",new String(Base64.encode(notRevokedCerts[1].getEncoded(),false)));
}
if ( notRevokedCerts.length > 2 ) {
line = line.replaceAll("TAG_certToRemove3",new String(Base64.encode(notRevokedCerts[2].getEncoded(),false)));
}
if ( notRevokedCerts.length > 3 ) {
line = line.replaceAll("TAG_certToRemove4",new String(Base64.encode(notRevokedCerts[3].getEncoded(),false)));
}
pw.println(line);
}
pw.close();
sw.flush();
}
{
OutputStream out = response.getOutputStream();
PrintWriter pw = new PrintWriter(out);
log.debug(sw);
pw.print(sw);
pw.close();
out.flush();
}
} // sendCertificates
/**
* Handles PKCS10 certificate request, these are constructed as: <code> CertificationRequest
* ::= SEQUENCE { certificationRequestInfo CertificationRequestInfo, signatureAlgorithm
* AlgorithmIdentifier{{ SignatureAlgorithms }}, signature BIT STRING }
* CertificationRequestInfo ::= SEQUENCE { version INTEGER { v1(0) } (v1,...),
* subject Name, subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
* attributes [0] Attributes{{ CRIAttributes }}} SubjectPublicKeyInfo { ALGORITHM :
* IOSet} ::= SEQUENCE { algorithm AlgorithmIdentifier {{IOSet}}, subjectPublicKey
* BIT STRING }</code> PublicKey's encoded-format has to be RSA X.509.
*
* @param signsession signsession to get certificate from
* @param b64Encoded base64 encoded pkcs10 request message
* @param username username of requesting user
* @param password password of requesting user
* @param resulttype should indicate if a PKCS7 or just the certificate is wanted.
*
* @return Base64 encoded byte[]
*/
private byte[] pkcs10CertRequest(Admin administrator, SignSession signsession, byte[] b64Encoded,
String username, String password) throws Exception {
byte[] result = null;
Certificate cert=null;
PKCS10RequestMessage req = RequestMessageUtils.genPKCS10RequestMessage(b64Encoded);
req.setUsername(username);
req.setPassword(password);
IResponseMessage resp = signsession.createCertificate(administrator, req, org.ejbca.core.protocol.X509ResponseMessage.class, null);
cert = CertTools.getCertfromByteArray(resp.getResponseMessage());
result = cert.getEncoded();
return Base64.encode(result, false);
}
}