/*************************************************************************
* *
* EJBCA Community: 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;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.regex.Pattern;
import javax.ejb.ObjectNotFoundException;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.jce.netscape.NetscapeCertRequest;
import org.cesecore.CesecoreException;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authorization.AuthorizationDeniedException;
import org.cesecore.certificates.ca.CADoesntExistsException;
import org.cesecore.certificates.ca.CAInfo;
import org.cesecore.certificates.ca.CaSessionLocal;
import org.cesecore.certificates.ca.SignRequestSignatureException;
import org.cesecore.certificates.certificate.certextensions.CertificateExtensionException;
import org.cesecore.certificates.certificate.request.CVCRequestMessage;
import org.cesecore.certificates.certificate.request.PKCS10RequestMessage;
import org.cesecore.certificates.certificate.request.RequestMessageUtils;
import org.cesecore.certificates.certificate.request.ResponseMessage;
import org.cesecore.certificates.certificate.request.ResponseStatus;
import org.cesecore.certificates.certificate.request.X509ResponseMessage;
import org.cesecore.util.Base64;
import org.cesecore.util.CertTools;
import org.cesecore.util.StringTools;
import org.ejbca.core.EjbcaException;
import org.ejbca.core.ejb.ca.sign.SignSessionLocal;
import org.ejbca.cvc.CAReferenceField;
import org.ejbca.cvc.CardVerifiableCertificate;
import org.ejbca.cvc.HolderReferenceField;
import org.ejbca.ui.web.pub.ServletDebug;
import org.ejbca.ui.web.pub.ServletUtils;
import org.ejbca.util.HTMLTools;
/**
* Helper class for handling certificate request from browsers or general PKCS#10
*
* @version $Id: RequestHelper.java 28947 2018-05-17 08:51:45Z jekaterina_b_helmes $
*/
public class RequestHelper {
private static Logger log = Logger.getLogger(RequestHelper.class);
private AuthenticationToken administrator;
private ServletDebug debug;
private static final Pattern CLASSID = Pattern.compile("\\$CLASSID");
public static final String BEGIN_CERTIFICATE_REQUEST_WITH_NL = "-----BEGIN CERTIFICATE REQUEST-----\n";
public static final String END_CERTIFICATE_REQUEST_WITH_NL = "\n-----END CERTIFICATE REQUEST-----\n";
public static final String BEGIN_CRL_WITH_NL = "-----BEGIN X509 CRL-----\n";
public static final String END_CRL_WITH_NL = "\n-----END X509 CRL-----\n";
public static final String BEGIN_PKCS7 = "-----BEGIN PKCS7-----\n";
public static final String END_PKCS7 = "\n-----END PKCS7-----\n";
public static final String BEGIN_PKCS7_WITH_NL = "-----BEGIN PKCS7-----\n";
public static final String END_PKCS7_WITH_NL = "\n-----END PKCS7-----\n";
/** @deprecated Since 6.1.0, remove in 7.0.0. Use CertificateResponseType.ENCODED_CERTIFICATE instead */
@Deprecated
public static final int ENCODED_CERTIFICATE = 1;
/** @deprecated Since 6.1.0, remove in 7.0.0. Use CertificateResponseType.ENCODED_PKCS7 instead */
@Deprecated
public static final int ENCODED_PKCS7 = 2;
/** @deprecated Since 6.1.0, remove in 7.0.0. Use CertificateResponseType.BINARY_CERTIFICATE instead */
@Deprecated
public static final int BINARY_CERTIFICATE = 3;
/** @deprecated Since 6.1.0, remove in 7.0.0. Use CertificateResponseType.ENCODED_CERTIFICATE_CHAIN instead */
@Deprecated
public static final int ENCODED_CERTIFICATE_CHAIN = 4;
/** String reported by Firefox when the key generation fails */
private static final byte[] HIGHGRADE_STRING = "High Grade".getBytes();
private static final byte[] MEDIUMGRADE_STRING = "Medium Grade".getBytes();
/**
* Creates a new RequestHelper object.
*
* @param administrator Admin doing the request
* @param debug object to send debug to or null to disable
*/
public RequestHelper(AuthenticationToken administrator, ServletDebug debug) {
this.administrator = administrator;
this.debug = debug;
}
/**
* Handles Firefox certificate request (KEYGEN), these are constructed as:
* SignedPublicKeyAndChallenge ::= SEQUENCE { publicKeyAndChallenge PublicKeyAndChallenge,
* signatureAlgorithm AlgorithmIdentifier, signature BIT STRING }
PublicKey's
* encoded-format has to be RSA X.509.
*
* @param signsession EJB session to signature bean.
* @param reqBytes buffer holding te request from NS.
* @param username username in EJBCA for authoriation.
* @param password users password for authorization.
*
* @return byte[] containing DER-encoded certificate.
*
* @throws CesecoreException
* @throws AuthorizationDeniedException
* @throws EjbcaException
* @throws CADoesntExistsException
* @throws ObjectNotFoundException
* @throws CertificateEncodingException
* @throws NoSuchProviderException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public byte[] nsCertRequest(SignSessionLocal signsession, byte[] reqBytes, String username, String password) throws
ObjectNotFoundException, CADoesntExistsException, EjbcaException, AuthorizationDeniedException, CesecoreException,
CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException {
if (reqBytes == null || reqBytes.length == 0) {
throw new IllegalStateException("Invalid request sent from browser (null or zero length).");
}
if (Arrays.equals(reqBytes, HIGHGRADE_STRING) || Arrays.equals(reqBytes, MEDIUMGRADE_STRING)) {
throw new IllegalStateException("Key generation failed. If enrolling using a hardware token, please check the token and the token middleware.");
}
byte[] buffer = Base64.decode(reqBytes);
if (buffer == null) {
return null;
}
final ASN1Sequence spkac;
try (ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(buffer))) {
spkac = (ASN1Sequence) in.readObject();
} catch (IOException e) {
throw new IllegalStateException("Unexpected IOException was caught.", e);
}
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) {
throw new SignRequestSignatureException(
"Invalid signature in NetscapeCertRequest, popo-verification failed.");
}
if (log.isDebugEnabled()) {
log.debug("POPO verification successful");
}
X509Certificate cert = (X509Certificate) signsession.createCertificate(administrator,
username, password, nscr.getPublicKey());
if (log.isDebugEnabled()) {
log.debug("Created certificate for " + username);
}
if (debug != null) {
debug.print("
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 }
PublicKey's encoded-format has to be RSA X.509.
*
* @param signsession signsession to get certificate from
* @param caSession a reference to CaSessionBean
* @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.
* @param doSplitLines
* @return Base64 encoded byte[]
* @throws AuthorizationDeniedException
* @throws CesecoreException
* @throws EjbcaException
* @throws CertificateException
* @throws CertificateEncodingException
* @throws CertificateExtensionException if b64Encoded specified invalid extensions
*/
public CertificateRequestResponse pkcs10CertRequest(SignSessionLocal signsession, CaSessionLocal caSession, byte[] b64Encoded, String username, String password,
CertificateResponseType resulttype, boolean doSplitLines) throws EjbcaException, CesecoreException, AuthorizationDeniedException,
CertificateEncodingException, CertificateException, CertificateExtensionException {
byte[] encoded = null;
X509Certificate cert = null;
PKCS10RequestMessage req = RequestMessageUtils.genPKCS10RequestMessage(b64Encoded);
req.setUsername(username);
req.setPassword(password);
ResponseMessage resp = signsession.createCertificate(administrator, req, X509ResponseMessage.class, null);
cert = CertTools.getCertfromByteArray(resp.getResponseMessage(), X509Certificate.class);
switch (resulttype) {
case ENCODED_CERTIFICATE:
encoded = Base64.encode(cert.getEncoded(), doSplitLines);
break;
case ENCODED_CERTIFICATE_CHAIN:
CAInfo caInfo = signsession.getCAFromRequest(administrator, req, false).getCAInfo();
LinkedList