Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for certificate renewal via SCEP #3413

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions base/ca/shared/conf/CS.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ ca.scep._004=## ca.scep.nickname=
ca.scep._005=## ca.scep.tokenname=
ca.scep._006=##
ca.scep.enable=false
ca.scep.enableRenewal=false
ca.scep.hashAlgorithm=SHA256
ca.scep.allowedHashAlgorithms=SHA256,SHA512
ca.scep.encryptionAlgorithm=DES3
Expand Down
265 changes: 234 additions & 31 deletions base/ca/src/com/netscape/cms/servlet/cert/scep/CRSEnrollment.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,23 @@
// --- END COPYRIGHT BLOCK ---
package com.netscape.cms.servlet.cert.scep;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.*;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;
import java.security.cert.CertificateException;
import java.util.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.netscape.certsrv.dbs.certdb.ICertRecord;
import com.netscape.cmscore.dbs.CertRecord;
import org.dogtagpki.server.authentication.AuthToken;
import org.dogtagpki.server.authentication.IAuthSubsystem;
import org.dogtagpki.server.ca.CAEngine;
Expand Down Expand Up @@ -66,30 +64,10 @@
import org.mozilla.jss.netscape.security.pkcs.PKCS10Attributes;
import org.mozilla.jss.netscape.security.util.ObjectIdentifier;
import org.mozilla.jss.netscape.security.util.Utils;
import org.mozilla.jss.netscape.security.x509.AVA;
import org.mozilla.jss.netscape.security.x509.CertAttrSet;
import org.mozilla.jss.netscape.security.x509.CertificateChain;
import org.mozilla.jss.netscape.security.x509.CertificateExtensions;
import org.mozilla.jss.netscape.security.x509.CertificateSubjectName;
import org.mozilla.jss.netscape.security.x509.CertificateVersion;
import org.mozilla.jss.netscape.security.x509.CertificateX509Key;
import org.mozilla.jss.netscape.security.x509.DNSName;
import org.mozilla.jss.netscape.security.x509.Extension;
import org.mozilla.jss.netscape.security.x509.GeneralName;
import org.mozilla.jss.netscape.security.x509.GeneralNameInterface;
import org.mozilla.jss.netscape.security.x509.GeneralNames;
import org.mozilla.jss.netscape.security.x509.IPAddressName;
import org.mozilla.jss.netscape.security.x509.KeyUsageExtension;
import org.mozilla.jss.netscape.security.x509.OIDMap;
import org.mozilla.jss.netscape.security.x509.RDN;
import org.mozilla.jss.netscape.security.x509.SubjectAlternativeNameExtension;
import org.mozilla.jss.netscape.security.x509.X500Name;
import org.mozilla.jss.netscape.security.x509.X500NameAttrMap;
import org.mozilla.jss.netscape.security.x509.X509CertImpl;
import org.mozilla.jss.netscape.security.x509.X509CertInfo;
import org.mozilla.jss.netscape.security.x509.X509Key;
import org.mozilla.jss.netscape.security.x509.*;
import org.mozilla.jss.pkcs7.IssuerAndSerialNumber;
import org.mozilla.jss.pkix.cert.Certificate;
import org.mozilla.jss.pkix.primitive.Name;
import org.mozilla.jss.util.IncorrectPasswordException;
import org.mozilla.jss.util.PasswordCallback;

Expand Down Expand Up @@ -166,6 +144,7 @@ public class CRSEnrollment extends HttpServlet {
private String mAuthManagerName;
private String mSubstoreName;
private boolean mEnabled = false;
private boolean mRenewalEnabled = false;
private boolean mUseCA = true;
private String mNickname = null;
private String mTokenName = "";
Expand Down Expand Up @@ -254,6 +233,7 @@ public void init(ServletConfig sc) {
mAllowedHashAlgorithm = mHashAlgorithmList.split(",");
mEncryptionAlgorithmList = scepConfig.getString("allowedEncryptionAlgorithms", "DES3");
mAllowedEncryptionAlgorithm = mEncryptionAlgorithmList.split(",");
mRenewalEnabled = scepConfig.getBoolean("enableRenewal", false);
mNickname = scepConfig.getString("nickname", ca.getNickname());
if (mNickname.equals(ca.getNickname())) {
mTokenName = ca.getSigningUnit().getTokenName();
Expand All @@ -273,6 +253,7 @@ public void init(ServletConfig sc) {
}
mEncryptionAlgorithm = mConfiguredEncryptionAlgorithm;
logger.debug("CRSEnrollment: init: SCEP support is " + ((mEnabled) ? "enabled" : "disabled") + ".");
logger.debug("CRSEnrollment: init: SCEP RenewalReq support is " + ((mRenewalEnabled) ? "enabled" : "disabled") + ".");
logger.debug("CRSEnrollment: init: SCEP nickname: " + mNickname);
logger.debug("CRSEnrollment: init: CA nickname: " + ca.getNickname());
logger.debug("CRSEnrollment: init: Token name: " + mTokenName);
Expand Down Expand Up @@ -900,7 +881,21 @@ public void handlePKIOperation(HttpServletRequest httpReq,
}

// now run appropriate code, depending on message type
if (mt.equals(CRSPKIMessage.mType_PKCSReq)) {
if (mRenewalEnabled && (mt.equals(CRSPKIMessage.mType_RenewalReq)
|| (mt.equals(CRSPKIMessage.mType_PKCSReq) && !authorizeSignerCertificate(req)))) {
logger.debug("Processing RenewalReq");
try {
// The same checks as for PKCSReq below.
IRequest cmsRequest = findRequestByTransactionID(req.getTransactionID(), true);

// If there was no request (with a cert) with this transaction ID,
// process it as a new request
cert = handleRenewalReq(httpReq, cmsRequest, req, crsResp, cx);

} catch (CRSFailureException e) {
throw new ServletException("Couldn't handle CEP request (PKCSReq) - " + e.getMessage());
}
} else if (mt.equals(CRSPKIMessage.mType_PKCSReq)) {
logger.debug("Processing PKCSReq");
try {
// Check if there is an existing request. If this returns non-null,
Expand Down Expand Up @@ -1049,6 +1044,14 @@ public void verifyRequest(CRSPKIMessage req, CryptoContext cx)
@SuppressWarnings("unused")
byte[] reqAAsig = req.getAADigest(); // check for errors

// new code below:
logger.debug("Verifying SCEP request");
try {
req.verify();
} catch (Exception e) {
throw new CRSInvalidSignatureException("An exception occurred while verifying SCEP request signature", e);
}
logger.debug("Request verification successful");
}

/**
Expand Down Expand Up @@ -1141,6 +1144,11 @@ public void unwrapPKCS10(CRSPKIMessage req, CryptoContext cx)
logger.debug(Debug.dump(decryptedP10bytes));

req.setP10(new PKCS10(decryptedP10bytes));

// debug the pkcs10 request
OutputStream output = new ByteArrayOutputStream();
req.getP10().print(new PrintStream(output));
logger.debug(output.toString());
} catch (Exception e) {
logger.error("failed to unwrap PKCS10 " + e.getMessage(), e);
throw new CRSFailureException("Could not unwrap PKCS10 blob: " + e.getMessage());
Expand Down Expand Up @@ -1396,6 +1404,7 @@ private SubjectAlternativeNameExtension makeDefaultSubjectAltName(Hashtable<Stri
private boolean authenticateUser(CRSPKIMessage req) {
boolean authenticationFailed = true;

logger.debug("mAuthManagerName: " + mAuthManagerName);
if (mAuthManagerName == null) {
return false;
}
Expand Down Expand Up @@ -1442,6 +1451,34 @@ private boolean authenticateUser(CRSPKIMessage req) {
return authenticationFailed;
}

// checks that the signer's cert is our own certificate
// returns true if authorization failed.
private boolean authorizeSignerCertificate(CRSPKIMessage req) {
boolean authorizationFailed = true;

try {
IssuerAndSerialNumber issuerAndSerialNumber = req.getSgnIssuerAndSerialNumber();
Name signerIssuer = issuerAndSerialNumber.getIssuer();

byte[] signerIssuerBytes = ASN1Util.encode(signerIssuer);
byte[] caSubjectBytes = mAuthority.getCACert().getSubjectObj().getX500Name().getEncoded();
logger.debug("Ensuring that the signer cert was issued by this CA:");
logger.debug("Request signer issuer bytes:");
logger.debug(Debug.dump(signerIssuerBytes));
logger.debug("CA subject bytes:");
logger.debug(Debug.dump(caSubjectBytes));

authorizationFailed = !(new String(signerIssuerBytes).equals(new String(caSubjectBytes)));

} catch (IOException e) {
logger.error(e.getMessage());
} catch (EBaseException e) {
logger.error(e.getMessage());
}

return authorizationFailed;
}

private boolean areFingerprintsEqual(IRequest req, Hashtable<String, byte[]> fingerprints) {

Hashtable<String, String> old_fprints = req.getExtDataInHashtable(IRequest.FINGERPRINTS);
Expand Down Expand Up @@ -1522,6 +1559,87 @@ public X509CertImpl handlePKCSReq(HttpServletRequest httpReq,
return null;
}

public X509CertImpl handleRenewalReq(HttpServletRequest httpReq,
IRequest cmsRequest, CRSPKIMessage req,
CRSPKIMessage crsResp, CryptoContext cx)
throws Exception {

try {
unwrapPKCS10(req, cx);
Hashtable<String, byte[]> fingerprints = makeFingerPrints(req);

if (cmsRequest != null) {
if (areFingerprintsEqual(cmsRequest, fingerprints)) {
logger.debug("created response from request");
return makeResponseFromRequest(req, crsResp, cmsRequest);
} else {
logger.warn("CRSEnrollment: " + CMS.getLogMessage("CMSGW_ENROLL_FAIL_DUP_TRANS_ID"));
crsResp.setFailInfo(CRSPKIMessage.mFailInfo_badRequest);
crsResp.setPKIStatus(CRSPKIMessage.mStatus_FAILURE);
return null;
}
}

getDetailFromRequest(req, crsResp);
boolean authFailed = authenticateUser(req);
boolean certAuthFailed = authorizeSignerCertificate(req);

if (authFailed) {
logger.warn("CRSEnrollment: " + CMS.getLogMessage("CMSGW_ENROLL_FAIL_NO_AUTH"));
crsResp.setFailInfo(CRSPKIMessage.mFailInfo_badIdentity);
crsResp.setPKIStatus(CRSPKIMessage.mStatus_FAILURE);

// perform audit log
String auditMessage = CMS.getLogMessage(
AuditEvent.NON_PROFILE_CERT_REQUEST,
httpReq.getRemoteAddr(),
ILogger.FAILURE,
req.getTransactionID(),
"CRSEnrollment",
ILogger.SIGNED_AUDIT_EMPTY_VALUE);
signedAuditLogger.log(auditMessage);

return null;

} else if (certAuthFailed) {
logger.warn("CRSEnrollment: signer's certificate authorization failed (cert not issued by this CA)");
crsResp.setFailInfo(CRSPKIMessage.mFailInfo_badIdentity);
crsResp.setPKIStatus(CRSPKIMessage.mStatus_FAILURE);

// perform audit log
String auditMessage = CMS.getLogMessage(
AuditEvent.NON_PROFILE_CERT_REQUEST,
httpReq.getRemoteAddr(),
ILogger.FAILURE,
req.getTransactionID(),
"CRSEnrollment",
ILogger.SIGNED_AUDIT_EMPTY_VALUE);
signedAuditLogger.log(auditMessage);

return null;

} else {
// bypass SCEP handling via profile
mProfileId = null;
IRequest ireq = postRenewalRequest(httpReq, req, crsResp);

logger.debug("created response");
return makeResponseFromRequest(req, crsResp, ireq);
}
} catch (CryptoContext.CryptoContextException e) {
logger.warn("CRSEnrollment: " + CMS.getLogMessage("CMSGW_ENROLL_FAIL_NO_DECRYPT_PKCS10",
e.getMessage()), e);
crsResp.setFailInfo(CRSPKIMessage.mFailInfo_badMessageCheck);
crsResp.setPKIStatus(CRSPKIMessage.mStatus_FAILURE);
} catch (EBaseException e) {
logger.warn("CRSEnrollment: " + CMS.getLogMessage("CMSGW_ERNOLL_FAIL_NO_NEW_REQUEST_POSTED",
e.getMessage()), e);
crsResp.setFailInfo(CRSPKIMessage.mFailInfo_internalCAError);
crsResp.setPKIStatus(CRSPKIMessage.mStatus_FAILURE);
}
return null;
}

////// post the request

/*
Expand Down Expand Up @@ -1691,6 +1809,88 @@ private IRequest postRequest(HttpServletRequest httpReq, CRSPKIMessage req, CRSP
return pkiReq;
}

private IRequest postRenewalRequest(HttpServletRequest httpReq, CRSPKIMessage req, CRSPKIMessage crsResp)
throws Exception {

// retrieve old cert
IssuerAndSerialNumber oldIssuerAndSerialNumber = req.getSgnIssuerAndSerialNumber();
INTEGER oldSerialNumber = oldIssuerAndSerialNumber.getSerialNumber();
X509CertImpl oldCert = mAuthority.getCertificateRepository().getX509Certificate(oldSerialNumber);
if (oldCert == null) {
logger.error("CSREnrollment::postRenewalRequest() - cannot retrieve signers cert!");
return null;
}
// get old cert status
CertRecord cRecord = (CertRecord)
mAuthority.getCertificateRepository().readCertificateRecord(oldSerialNumber);
if (cRecord == null) {
logger.error("CSREnrollment::postRenewalRequest() - cannot retrieve signers cert record!");
return null;
}
String status = cRecord.getStatus();
if (status.equals(ICertRecord.STATUS_REVOKED) ||
status.equals(ICertRecord.STATUS_REVOKED_EXPIRED)) {
logger.error("CSREnrollment::postRenewalRequest() - signers cert expired or revoked!");
return null;
}

// request new cert based on old cert info
IRequestQueue rq = ca.getRequestQueue();
IRequest pkiReq = rq.newRequest(IRequest.RENEWAL_REQUEST);
pkiReq.setExtData(IRequest.OLD_SERIALS, new BigInteger[] { oldSerialNumber });
pkiReq.setExtData(IRequest.OLD_CERTS, new X509CertImpl[] { oldCert });

X509CertInfo oldCertInfo = (X509CertInfo) oldCert.get(X509CertImpl.NAME + "." + X509CertImpl.INFO);
X509CertInfo newCertInfo = new X509CertInfo(oldCertInfo.getEncodedInfo());
try {
newCertInfo.set(X509CertInfo.ISSUER, oldCertInfo.get(X509CertInfo.ISSUER));
newCertInfo.set(X509CertInfo.VALIDITY, new CertificateValidity(new Date(0), new Date(0)));
// set the public key from the cert request, not the old certificate
CertificateX509Key csrKey = new CertificateX509Key(new ByteArrayInputStream(req.getP10().getSubjectPublicKeyInfo().getEncoded()));
newCertInfo.set(X509CertInfo.KEY, csrKey);
} catch (CertificateException e) {
logger.error("CSREnrollment::postRenewalRequest() - cannot set certificate issuer, validity or public key", e);
return null;
} catch (IOException e) {
logger.error("CSREnrollment::postRenewalRequest() - cannot set certificate issuer, validity or public key", e);
return null;
}
pkiReq.setExtData(IRequest.CERT_INFO, new X509CertInfo[] { newCertInfo });
pkiReq.setExtData(IRequest.HTTP_PARAMS, IRequest.CERT_TYPE, IRequest.CEP_CERT);
pkiReq.setExtData("cepsubstore", mSubstoreName);

Hashtable<?, ?> fingerprints = (Hashtable<?, ?>) req.get(IRequest.FINGERPRINTS);
if (fingerprints.size() > 0) {
Hashtable<String, String> encodedPrints = new Hashtable<String, String>(fingerprints.size());
Enumeration<?> e = fingerprints.keys();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
byte[] value = (byte[]) fingerprints.get(key);
encodedPrints.put(key, Utils.base64encode(value, true));
}
pkiReq.setExtData(IRequest.FINGERPRINTS, encodedPrints);
}

pkiReq.setSourceId(req.getTransactionID());

rq.processRequest(pkiReq);

crsResp.setPKIStatus(CRSPKIMessage.mStatus_SUCCESS);

logger.info(
AuditFormat.RENEWALFORMAT,
pkiReq.getRequestId(),
AuditFormat.FROMROUTER,
mAuthManagerName == null ? AuditFormat.NOAUTH : mAuthManagerName,
"pending",
oldCert.getSubjectDN(),
oldSerialNumber.toString(16),
""
);

return pkiReq;
}

public Hashtable<String, byte[]> makeFingerPrints(CRSPKIMessage req) {
Hashtable<String, byte[]> fingerprints = new Hashtable<String, byte[]>();

Expand Down Expand Up @@ -2099,6 +2299,9 @@ public CRSInvalidSignatureException() {
public CRSInvalidSignatureException(String s) {
super(s);
}

public CRSInvalidSignatureException(String s, Exception e) { super(s, e); }

}

class CRSPolicyException extends Exception {
Expand Down
Loading