/*************************************************************************
* *
* 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.externalra.gui;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.jce.netscape.NetscapeCertRequest;
import org.ejbca.config.InternalConfiguration;
import org.ejbca.core.model.SecConst;
import org.ejbca.extra.db.CertificateRequestRequest;
import org.ejbca.util.Base64;
import org.ejbca.util.CertTools;
import org.ejbca.util.FileTools;
import com.icesoft.faces.component.inputfile.InputFile;
import com.icesoft.faces.context.ByteArrayResource;
import com.icesoft.faces.context.Resource;
import com.icesoft.faces.context.effects.JavascriptContext;
import com.novosec.pkix.asn1.crmf.CertReqMessages;
/**
* This is the backing bean for the enrollment part of the External RA GUI.
*
* @version $Id: EnrollInterfaceBean.java 9330 2010-06-30 18:16:53Z anatom $
*/
public class EnrollInterfaceBean {
private static final String PEM_CSR_BEGIN = "-----BEGIN CERTIFICATE REQUEST-----";
private static final String PEM_CSR_END = "-----END CERTIFICATE REQUEST-----";
private static final String PEM_CSR_BEGIN_VISTA = "-----BEGIN NEW CERTIFICATE REQUEST-----";
private static final String PEM_CSR_END_VISTA = "-----END NEW CERTIFICATE REQUEST-----";
private static final String PEM_PKCS7_BEGIN = "-----BEGIN PKCS7-----";
private static final String PEM_PKCS7_END = "-----END PKCS7-----";
private static final Logger log = Logger.getLogger(EnrollInterfaceBean.class);
private IRequestDispatcher requestDispatcher = null;
// General variables
private String userAgentString = null;
private String username = null;
private String password = null;
private boolean showPassword = false;
private String filename = null;
private Resource resource = null;
private String mimeType = null;
// Variables used to get a certificate from a CSR
private String certificateRequest = PEM_CSR_BEGIN + "\n...base 64 encoded request...\n" + PEM_CSR_END;
private String requestedResponseType = "der";
private String certificateRequestType = null;
private String certificateResponse = "";
private String certificateResponseType = null;
private String cspSelectValue = null;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public boolean getShowPassword() { return showPassword; }
public void setShowPassword(boolean showPassword) { this.showPassword = showPassword; }
public boolean isDownloadAvailable() { return resource!=null; }
public String getFilename() { return filename; }
public Resource getResource() { return resource; }
public String getMimeType() { return mimeType; }
public String getCertificateRequest() { return certificateRequest; }
public void setCertificateRequest(String certificateRequest) { this.certificateRequest = certificateRequest; }
public String getRequestedResponseType() { return requestedResponseType; }
public void setRequestedResponseType(String requestedResponseType) { this.requestedResponseType = requestedResponseType; }
public String getCertificateRequestType() { return certificateRequestType; }
public void setCertificateRequestType(String certificateRequestType) { this.certificateRequestType = certificateRequestType; }
public String getCertificateResponseType() { return certificateResponseType; }
public String getCertificateResponse() { return certificateResponse; }
public void setCertificateResponse(String certificateResponse) { this.certificateResponse = certificateResponse; }
public String getCspSelectValue() { return cspSelectValue; }
public void setCspSelectValue(String cspSelectValue) { this.cspSelectValue = cspSelectValue; }
public String getKeySpec() { return ExternalRaGuiConfiguration.getKeySpec(); }
public String getExportable() { return ExternalRaGuiConfiguration.getExportable() ? "1" : "0"; }
public String getVersionString() { return InternalConfiguration.getAppVersionNumber() + " (" + InternalConfiguration.getSvnRevision() + ")"; }
public String getHelpUrl() { return ExternalRaGuiConfiguration.getHelpUrl(); }
/** @return The host's name or "unknown" if it could not be determined. */
public String getHostName() {
String hostname = "unknown";
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
// Ignored
}
return hostname;
}
/** @return true if the user-agent String contains MSIE **/
public boolean isInternetExplorer() {
return getUserAgenString().indexOf("MSIE") != -1;
}
/**
* XEnroll:
* "Windows NT 5.1" = Win XP x86
* "Windows NT 5.2" = Win XP x64, Server 2003
* CertEnroll:
* "Windows NT 6.0" = Win Vista, Server 2008
* "Windows NT 6.1" = Windows 7, Server 2008 R2
*/
public boolean isWindowsNT5() {
return getUserAgenString().indexOf("Windows NT 5.") != -1;
}
/**
* Prevent the web framework from rendering an empty table if there are no global messages
* by using this method. Empty tables results in error messages in the Safari log.
*/
public boolean isMessagesPending() {
return FacesContext.getCurrentInstance().getMessages(null).hasNext();
}
/**
* @return the current user-agent string that identifies the clients browser and operating system.
*/
private String getUserAgenString() {
if (userAgentString == null) {
userAgentString = (String) FacesContext.getCurrentInstance().getExternalContext().getRequestHeaderMap().get("user-agent");
log.debug("User agent: " + userAgentString);
}
return userAgentString;
}
/**
* @return an implementation for communication with the EJBCA instance.
*/
private IRequestDispatcher getRequestDispatcher() {
if (requestDispatcher == null) {
// Change this to read the class name from the externalra-gui configuration if we add additional implementations.
String className = ExternalRARequestDispatcher.class.getName();
try {
requestDispatcher = (IRequestDispatcher) Class.forName(className).newInstance();
} catch (ClassNotFoundException e) {
log.error("Could not find request implementaion :" + className);
} catch (InstantiationException e) {
log.error("Could not instantiate request implementaion :" + className);
} catch (IllegalAccessException e) {
log.error("Could not access request implementaion :" + className);
}
}
return requestDispatcher;
}
/**
* Used for reading a Certificate Signing Request file upload into the certificate request String.
* @param actionEvent is the parameter from the web framework containing the file.
*/
public void uploadActionListener(ActionEvent actionEvent) {
InputFile inputFile = (InputFile) actionEvent.getSource();
FacesContext context = FacesContext.getCurrentInstance();
if (inputFile.getFileInfo().isSaved()) {
// Validate that it is a CSR..
File f = inputFile.getFileInfo().getFile();
// Assume this is a small file.. it should be..
long len = f.length();
if (len < 16*1024L) {
byte[] buf = new byte[(int) len];
try {
FileInputStream in = new FileInputStream(f);
in.read(buf);
in.close();
} catch (IOException e) {
context.addMessage(null /*actionEvent.getComponent().getClientId(context)*/, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.csrcert.uploadfailed"), null));
log.debug("Rejected uploaded file due to IOException.");
return;
}
try {
// See if it was a PEM
buf = FileTools.getBytesFromPEM(buf, PEM_CSR_BEGIN, PEM_CSR_END);
} catch (IOException e) {
log.debug("Uploaded file was not a PEM.. tryin to parse it as a DER encoded request.");
}
// See if it as a PKCS10
try {
new PKCS10CertificationRequest(buf);
} catch (Exception e) {
context.addMessage(null /*actionEvent.getComponent().getClientId(context)*/, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.csrcert.uploadfailednotpkcs10"), null));
log.debug("Rejected uploaded file since it's not a valid PKCS#10 request.");
return;
}
// Convert it back to a PEM
String pem = PEM_CSR_BEGIN + "\n" + new String(Base64.encode(buf)) + "\n" + PEM_CSR_END;
certificateRequest = pem;
context.addMessage(null /*actionEvent.getComponent().getClientId(context)*/, new FacesMessage(FacesMessage.SEVERITY_INFO, getMessage("enroll.csrcert.uploadok"), null));
} else {
context.addMessage(null /*actionEvent.getComponent().getClientId(context)*/, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.csrcert.uploadfailedtoolarge"), null));
}
} else {
log.debug("File upload failed: " + inputFile.getFileInfo().getException().getMessage());
context.addMessage(null /*actionEvent.getComponent().getClientId(context)*/, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.csrcert.uploadfailed"), null));
}
}
/**
* Action that requests a KeyStore from EJBCA using the given credentials.
*/
public void createKeystore() {
log.info("Recieved a KeyStore request for username '" + username + "' from " + getRemoteAddress());
FacesContext context = FacesContext.getCurrentInstance();
if (username==null || username.length()==0 || password==null || password.length()==0) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.incompletefields"), null));
return;
}
// Request the KeyStore from the CA
ResponseData keyStoreResponse = getRequestDispatcher().getKeyStoreResponse(username, password);
// Check if got a valid result
if (keyStoreResponse == null) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.noresponse"), null));
log.error("KeyStore request for '" + username + "' failed. No response from CA.");
return;
} else if (keyStoreResponse.getErrorMessage() != null) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.keystore.couldnotcreate"), null));
log.info("KeyStore request for '" + username + "' failed. " + keyStoreResponse.getErrorMessage());
return;
}
// Handle response
resource = new ByteArrayResource(keyStoreResponse.getResponseData());
switch (keyStoreResponse.getResponseType()) {
case SecConst.TOKEN_SOFT_JKS:
filename = username + ".jks";
break;
case SecConst.TOKEN_SOFT_P12:
filename = username + ".p12";
break;
case SecConst.TOKEN_SOFT_PEM:
filename = username + ".pem";
break;
default:
filename = username + ".unknown";
break;
}
mimeType = "application/octet-stream";
log.info("KeyStore request with response-type " + keyStoreResponse.getResponseType() + " for '" + username + "' was successful.");
}
/**
* Action that requests a certificate from EJBCA using the given credentials and the Certificate Signing Request.
*/
public void createCertFromCSR() {
log.info("Recieved a certificate signing request for username '" + username + "' from " + getRemoteAddress());
if (log.isDebugEnabled()) {
log.debug("certificateRequest: " + certificateRequest);
}
FacesContext context = FacesContext.getCurrentInstance();
if (username==null || username.length()==0 || password==null || password.length()==0 || certificateRequest==null || certificateRequest.length()==0) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.incompletefields"), null));
return;
}
// Verify that we got a valid Certificate Signing Request
try {
// Clean it up if windows has messed it up..
byte[] buf = (PEM_CSR_BEGIN + certificateRequest.replaceFirst(PEM_CSR_BEGIN, "").replaceFirst(PEM_CSR_END, "").replaceAll(" ", "").replaceAll("\r", "")+PEM_CSR_END).getBytes();
// See if it is a PEM
buf = FileTools.getBytesFromPEM(buf, PEM_CSR_BEGIN, PEM_CSR_END);
certificateRequest = PEM_CSR_BEGIN + "\n" + new String(Base64.encode(buf)) + "\n" + PEM_CSR_END;
if (log.isDebugEnabled()) {
log.debug("cleaned req: " + certificateRequest);
}
new PKCS10CertificationRequest(buf);
} catch (Exception e) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.invalidreqdata"), null));
return;
}
// Determine what kind of response the user has requested
int responseType = CertificateRequestRequest.RESPONSE_TYPE_ENCODED;
if ("pkcs7".equals(requestedResponseType)) {
responseType = CertificateRequestRequest.RESPONSE_TYPE_PKCS7;
}
// Request the certificate from the CA
ResponseData csrResponse = getRequestDispatcher().getCertificateSigningRequestResponse(username, password, certificateRequest, responseType);
// Check if got a valid result
if (csrResponse == null) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.noresponse"), null));
log.error("Certificate request for '" + username + "' failed. No response from CA.");
return;
} else if (csrResponse.getErrorMessage() != null) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.csrcert.couldnotcreate"), null));
log.info("Certificate request for '" + username + "' failed. " + csrResponse.getErrorMessage());
return;
}
// Handle response
switch (csrResponse.getResponseType()) {
case CertificateRequestRequest.RESPONSE_TYPE_ENCODED:
if ("pem".equals(requestedResponseType)) {
Certificate[] certs = new Certificate[1];
try {
certs[0] = CertTools.getCertfromByteArray(csrResponse.getResponseData());
resource = new ByteArrayResource(CertTools.getPEMFromCerts(CertTools.getCertCollectionFromArray(certs, "BC")));
filename = username + ".pem";
mimeType = "application/x-pem-file";
} catch (Exception e) {
log.error("",e);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.invalidresponse"), null));
}
} else {
resource = new ByteArrayResource(csrResponse.getResponseData());
filename = username + ".der";
mimeType = "application/pkix-cert";
}
break;
case CertificateRequestRequest.RESPONSE_TYPE_PKCS7:
resource = new ByteArrayResource(csrResponse.getResponseData());
filename = username + ".p7b";
mimeType = "application/x-pkcs7-certificates";
break;
default:
filename = username + ".unknown";
mimeType = "application/octet-stream";
break;
}
log.info("Certificate request with response-type " + csrResponse.getResponseType() + " for '" + username + "' was successful.");
}
/**
* Action that requests a certificate from EJBCA using the given credentials and the Certificate Signing Request created by the browser.
*/
public void createCertFromBrowser() {
log.info("Recieved a browser generated certificate request of type " + certificateRequestType + " for username '" + username + "' from " + getRemoteAddress());
if (log.isDebugEnabled()) {
log.debug("certificateRequest: " + certificateRequest);
}
FacesContext context = FacesContext.getCurrentInstance();
if (username==null || username.length()==0 || password==null || password.length()==0 || certificateRequest==null || certificateRequest.length()==0
|| certificateRequestType==null || certificateRequestType.length()==0) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.incompletefields"), null));
return;
}
// Verify that we got a valid certificate request and determine response type
byte[] buf = null;
int requestType = Integer.parseInt(certificateRequestType);
int responseType;
switch (requestType) {
case CertificateRequestRequest.REQUEST_TYPE_CRMF:
responseType = CertificateRequestRequest.RESPONSE_TYPE_PKCS7;
buf = Base64.decode(certificateRequest.getBytes());
ASN1InputStream asn1InputStream = new ASN1InputStream(buf);
try {
// Verify that we can parse this as a CRMF object
CertReqMessages.getInstance(asn1InputStream.readObject()).getCertReqMsg(0);
} catch (IOException e) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.invalidreqdata"), null));
log.error("",e);
}
break;
case CertificateRequestRequest.REQUEST_TYPE_PKCS10:
responseType = CertificateRequestRequest.RESPONSE_TYPE_PKCS7;
try {
if (!isWindowsNT5()) {
responseType = CertificateRequestRequest.RESPONSE_TYPE_UNSIGNEDPKCS7;
}
// Replace Vista PEM markers
certificateRequest = certificateRequest.replaceAll(PEM_CSR_BEGIN_VISTA, PEM_CSR_BEGIN);
certificateRequest = certificateRequest.replaceAll(PEM_CSR_END_VISTA, PEM_CSR_END);
if (certificateRequest.indexOf(PEM_CSR_BEGIN) == -1) {
certificateRequest = PEM_CSR_BEGIN + "\n" + certificateRequest + "\n" + PEM_CSR_END;
}
buf = FileTools.getBytesFromPEM(certificateRequest.getBytes(), PEM_CSR_BEGIN, PEM_CSR_END);
new PKCS10CertificationRequest(buf);
} catch (Exception e) {
log.error("",e);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.invalidreqdata"), null));
return;
}
break;
case CertificateRequestRequest.REQUEST_TYPE_KEYGEN:
responseType = CertificateRequestRequest.RESPONSE_TYPE_PKCS7;
try {
buf = Base64.decode(certificateRequest.getBytes());
ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(buf));
ASN1Sequence spkac = (ASN1Sequence) in.readObject();
in.close();
NetscapeCertRequest nscr = new NetscapeCertRequest(spkac);
// Verify POPO, we don't care about the challenge, it's not important.
nscr.setChallenge("challenge");
if (nscr.verify("challenge") == false) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.invalidreqdata"), null));
return;
}
} catch (Exception e) {
log.error("",e);
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.invalidreqdata"), null));
return;
}
break;
case -1:
// This is a workaround to hide errors when we use the KeyGenServlet..
return;
default:
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.unknownrequesttype"), null));
return;
}
// Request the certificate from the CA
if (log.isDebugEnabled()) {
log.debug("Got requestType " + requestType + " and is expecting responseType " + responseType + " for user " + username);
}
ResponseData responseData = getRequestDispatcher().getCertificateResponse(username, password, requestType, buf, responseType);
// Check if got a valid result
if (responseData == null) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.noresponse"), null));
log.error("Certificate request for '" + username + "' failed. No response from CA.");
return;
} else if (responseData.getErrorMessage() != null) {
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.browsercert.couldnotcreate"), null));
log.info("Certificate request for '" + username + "' failed. " + responseData.getErrorMessage());
return;
}
// Handle response
certificateResponseType = "" + responseData.getResponseType();
switch (responseData.getResponseType()) {
case CertificateRequestRequest.RESPONSE_TYPE_PKCS7:
if (isInternetExplorer()) {
// Working for XP+IE7
certificateResponse = new String(Base64.encode(responseData.getResponseData(), false));
} else {
resource = new ByteArrayResource(responseData.getResponseData());
mimeType = "application/x-x509-user-cert";
}
break;
case CertificateRequestRequest.RESPONSE_TYPE_UNSIGNEDPKCS7:
// Working for Vista+IE8
certificateResponse = new String(Base64.encode(responseData.getResponseData(), false));
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String pkcs7 = PEM_PKCS7_BEGIN + "\n" + new String(Base64.encode(responseData.getResponseData(), true)) + "\n" + PEM_PKCS7_END + "\n";
log.debug("pkcs7="+pkcs7);
CertPath certPath = cf.generateCertPath(new ByteArrayInputStream(responseData.getResponseData()), "PKCS7");
List<? extends Certificate> certList = certPath.getCertificates();
Certificate caCert = certList.get(certList.size()-1);
String caCertificate = new String(Base64.encode(caCert.getEncoded(), false));
resource = new ByteArrayResource(caCertificate.getBytes());
mimeType = "application/x-x509-ca-cert";
} catch (CertificateException e) {
e.printStackTrace();
}
if (log.isDebugEnabled()) {
log.debug("certificateResponse: " + certificateResponse);
}
break;
default:
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, getMessage("enroll.unknownresponsetype"), null));
log.error("Unknown result type: " + certificateResponseType);
break;
}
log.info("Certificate request with response-type " + responseData.getResponseType() + " for '" + username + "' was successful.");
}
/**
* Adds a JavaScript that removes the HTML from where it was called. Expects to be included inside a div with id "form:downloadDiv2".
*/
public void removeLinks() {
// Hide the installer links once the cert is installed
JavascriptContext.addJavascriptCall(FacesContext.getCurrentInstance(), "if (document.getElementById('form:certificateInstalled').value == 'true') { document.getElementById('form:downloadLinkDiv2').innerHTML = 'Installed.';}");
}
/**
* Action that does absolutely nothing. Used when we want a commandLink to just trigger an onclick or something similar.
*/
public void noOp() { }
/**
* Get localized message from the message-bundle.
*/
private String getMessage(String key){
String text = null;
FacesContext context = FacesContext.getCurrentInstance();
ResourceBundle bundle = ResourceBundle.getBundle(context.getApplication().getMessageBundle(), context.getViewRoot().getLocale(), Thread.currentThread().getContextClassLoader());
try{
text = bundle.getString(key);
} catch(MissingResourceException e){
text = "?? key " + key + " not found ??";
}
return text;
}
private String getRemoteAddress() {
return ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getRemoteAddr();
}
}