Skip to content
Snippets Groups Projects
Commit 0bed840a authored by Felix Sidokhine's avatar Felix Sidokhine
Browse files

ported OCSP needs testing

parent 64be0d5b
No related branches found
No related tags found
No related merge requests found
Showing with 377 additions and 2 deletions
......@@ -58,6 +58,12 @@ public class JamsCA implements CertificateAuthority {
userLifetime = config.getUserLifetime();
deviceLifetime = config.getDeviceLifetime();
crlWorker = new CRLWorker(CA.getPrivateKey(), CA.getCertificate());
try {
ocspWorker = new OCSPWorker(OCSP.getPrivateKey(), OCSP.getCertificate(), crlWorker);
}
catch (Exception e){
log.error("Could not start OCSP request processor with error {}",e.getMessage());
}
}
@Override
......@@ -101,7 +107,7 @@ public class JamsCA implements CertificateAuthority {
@Override
public OCSPResp getOCSPResponse(OCSPReq ocspRequest) {
return null;
return ocspWorker.getOCSPResponse(ocspRequest);
}
@Override
......
......@@ -3,6 +3,8 @@ package net.jami.jams.ca.workers;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPResp;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
......
......@@ -26,6 +26,7 @@ public class CRLWorker extends X509Worker<RevocationRequest> {
super(privateKey, certificate);
this.setDaemon(true);
this.start();
//TODO: The CRL needs to be loaded from memory
log.info("Instantiated & started a CRL Worker...");
}
......
......@@ -2,15 +2,112 @@ package net.jami.jams.ca.workers.ocsp;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.ca.workers.X509Worker;
import net.jami.jams.ca.workers.crl.CRLWorker;
import net.jami.jams.common.cryptoengineapi.ocsp.CertificateStatus;
import net.jami.jams.common.cryptoengineapi.ocsp.CertificateSummary;
import net.jami.jams.common.cryptoengineapi.ocsp.OCSPCertificateStatusMapper;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CRLEntryHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.ocsp.*;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import static net.jami.jams.ca.JamsCA.*;
@Slf4j
public class OCSPWorker extends X509Worker<String> {
public OCSPWorker(PrivateKey privateKey, X509Certificate certificate) {
private final CRLWorker crlWorker;
private final RespID responderID;
private final ContentSigner contentSigner;
private final JcaContentVerifierProviderBuilder contentVerifierProvider = new JcaContentVerifierProviderBuilder().setProvider("BC");
//To process OCSP requests we need access to the CRL, hence we might as well just pass it here.
public OCSPWorker(PrivateKey privateKey, X509Certificate certificate, CRLWorker crlWorker) throws Exception {
super(privateKey, certificate);
this.crlWorker = crlWorker;
DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
// only SHA-1 is supported for responder IDs.
this.responderID = new RespID(SubjectPublicKeyInfo.getInstance(CA.getCertificate()), digestCalculatorProvider.get(new DefaultDigestAlgorithmIdentifierFinder().find("SHA-1")));
this.contentSigner = new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(OCSP.getPrivateKey());
log.info("Instantiated OCSP Worker...");
}
public OCSPResp getOCSPResponse(OCSPReq ocspRequest) {
try{
OCSPResp resp = validateRequest(ocspRequest);
if(resp != null) return resp; //this means the request is invalid and we should notify the client.
//If the request was valid, we move on to other things.
BasicOCSPRespBuilder responseBuilder = new BasicOCSPRespBuilder(responderID);
Collection<Extension> responseExtensions = new ArrayList<>();
Extension nonceExtension = ocspRequest.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
if (nonceExtension != null) responseExtensions.add(nonceExtension);
Extension[] extensions = responseExtensions.toArray(new Extension[responseExtensions.size()]);
responseBuilder.setResponseExtensions(new Extensions(extensions));
for (Req request : ocspRequest.getRequestList()) {
addResponse(responseBuilder, request);
}
BasicOCSPResp basicResponse = responseBuilder.build(
contentSigner,
new X509CertificateHolder[]{new JcaX509CertificateHolder(OCSP.getCertificate())},
new Date()
);
return new OCSPRespBuilder().build(OCSPRespBuilder.SUCCESSFUL, basicResponse);
}
catch (Exception e){
log.error("Could not verify the signature on the OCSP request with error {}",e.getMessage());
return null;
}
}
private OCSPResp validateRequest(OCSPReq ocspRequest) throws Exception {
if (ocspRequest == null || (ocspRequest.isSigned() &&
!ocspRequest.isSignatureValid(contentVerifierProvider.build(ocspRequest.getCerts()[0])))) {
return new OCSPRespBuilder().build(OCSPRespBuilder.MALFORMED_REQUEST, null);
}
return null;
}
private CertificateSummary getCertificateSummary(BigInteger serial) {
X509CRLEntryHolder x509CRLEntryHolder = crlWorker.getExistingCRL().get().getRevokedCertificate(serial);
if (x509CRLEntryHolder != null)
return new CertificateSummary(CertificateStatus.REVOKED, LocalDateTime.from(x509CRLEntryHolder.getRevocationDate().toInstant()), serial);
else return new CertificateSummary(CertificateStatus.VALID, serial);
}
private void addResponse(BasicOCSPRespBuilder responseBuilder, Req request) throws OCSPException{
CertificateID certificateID = request.getCertID();
// Build Extensions
Extensions extensions = new Extensions(new Extension[]{});
Extensions requestExtensions = request.getSingleRequestExtensions();
if (requestExtensions != null) {
Extension nonceExtension = requestExtensions.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
if (nonceExtension != null) extensions = new Extensions(nonceExtension);
}
responseBuilder.addResponse(certificateID,
OCSPCertificateStatusMapper.getStatus(getCertificateSummary(request.getCertID().getSerialNumber())),
new Date(),
new Date(new Date().getTime() + crlLifetime),
extensions);
}
}
......@@ -51,6 +51,7 @@ module jams.common {
exports net.jami.jams.common.server;
exports net.jami.jams.common.authentication.local;
exports net.jami.jams.common.objects.responses;
exports net.jami.jams.common.cryptoengineapi.ocsp;
requires jdk.crypto.cryptoki;
requires java.base;
requires java.sql;
......
package net.jami.jams.common.cryptoengineapi.ocsp;
public enum CertificateStatus {
VALID, /** The certificate is valid **/
REVOKED, /** The certificate has been revoked **/
EXPIRED, /** The certificate is expired **/
UNKNOWN; /** The certificate is unknown **/
public static CertificateStatus fromString(String status) {
switch (status) {
case "V":
return VALID;
case "R":
return REVOKED;
case "E":
return EXPIRED;
default:
throw new IllegalArgumentException("Did not find valid status: " + status);
}
}
}
package net.jami.jams.common.cryptoengineapi.ocsp;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CertificateSummary {
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmssZ");
private CertificateStatus status;
private LocalDateTime expirationTime;
private LocalDateTime revocationTime;
private RevocationReason revocationReason;
private BigInteger serialNumber;
private String fileName;
private X500Principal subjectDN;
public CertificateSummary(CertificateStatus status, LocalDateTime revocationTime, BigInteger serialNumber) {
this.status = status;
this.revocationTime = revocationTime;
this.serialNumber = serialNumber;
}
public CertificateSummary(CertificateStatus status, BigInteger serialNumber) {
this.status = status;
this.serialNumber = serialNumber;
}
}
package net.jami.jams.common.cryptoengineapi.ocsp;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import java.time.ZoneId;
import java.util.Date;
import static net.jami.jams.common.cryptoengineapi.ocsp.RevocationReason.PRIVILEGE_WITHDRAWN;
import static net.jami.jams.common.cryptoengineapi.ocsp.RevocationReason.SUPERSEDED;
public class OCSPCertificateStatusMapper {
public static CertificateStatus getStatus(CertificateSummary certificateSummary){
switch (certificateSummary.getStatus()) {
case VALID:
return CertificateStatus.GOOD;
case REVOKED:
return new RevokedStatus(Date.from(certificateSummary.getRevocationTime().atZone(ZoneId.systemDefault()).toInstant()), PRIVILEGE_WITHDRAWN.getCode());
case EXPIRED:
return new RevokedStatus(Date.from(certificateSummary.getExpirationTime().atZone(ZoneId.systemDefault()).toInstant()), SUPERSEDED.getCode());
case UNKNOWN:
return new UnknownStatus();
default:
throw new IllegalArgumentException("Unknown status! " + certificateSummary.getStatus().name());
}
}
}
package net.jami.jams.common.cryptoengineapi.ocsp;
import java.util.HashMap;
/**
* Enumeration of certificate revocation reasons
*
* @author wdawson
* @see java.security.cert.CRLReason
*/
public enum RevocationReason {
/**
* Code = 0, Name = unspecified
* <p>
* Can be used to revoke certificates for reasons other than the specific codes.
* </p>
*/
UNSPECIFIED(0, "unspecified"),
/**
* Code = 1, Name = keyCompromise
* <p>
* Used in revoking an end-entity certificate. It indicates that it is known or
* suspected that the subject's private key, or other aspects of the subject
* validated in the certificate, have been compromised.
* </p>
*/
KEY_COMPROMISE(1, "keyCompromise"),
/**
* Code = 2, Name = CACompromise
* <p>
* Used in revoking a CA-certificate. It indicates that it is known or suspected
* that the subject's private key, or other aspects of the subject validated in
* the certificate, have been compromised.
* </p>
*/
CA_COMPROMISE(2, "CACompromise"),
/**
* Code = 3, Name = affiliationChanged
* <p>
* Indicates that the subject's name or other information in the certificate has
* been modified but there is no cause to suspect that the private key has been
* compromised.
* </p>
*/
AFFILIATION_CHANGED(3, "affiliationChanged"),
/**
* Code = 4, Name = superseded
* <p>
* Indicates that the certificate has been superseded but there is no cause to
* suspect that the private key has been compromised.
* </p>
*/
SUPERSEDED(4, "superseded"),
/**
* Code = 5, Name = cessationOfOperation
* <p>
* Indicates that the certificate is no longer needed for the purpose for which
* it was issued but there is no cause to suspect that the private key has been
* compromised.
* </p>
*/
CESSATION_OF_OPERATION(5, "cessationOfOperation"),
/**
* Code = 6, Name = certificateHold
* <p>
* Indicates that the certificate is temporarily revoked but there is no cause
* to suspect that the private kye has been compromised; the certificate can
* later be revoked with another reason code, or unrevoked and returned to use.
*/
CERTIFICATE_HOLD(6, "certificateHold"),
/**
* Code = 7, Name = unused
* <p>
* This code is obsolete and unused.
* </p>
*/
UNUSED(7, "unused"),
/**
* Code = 8, Name = removeFromCRL
* <p>
* Indicates that the certificate has been unrevoked. NOTE: This is specific to
* the CertificateHold reason and is only used in DeltaCRLs.
* </p>
*/
REMOVE_FROM_CRL(8, "removeFromCRL"),
/**
* Code = 9, Name = privilegeWithdrawn
* <p>
* Indicates that a certificate (public-key or attribute certificate) was
* revoked because a privilege contained within that certificate has been
* withdrawn. Unused in OpenSSL.
* </p>
*/
PRIVILEGE_WITHDRAWN(9, "privilegeWithdrawn"),
/**
* Code = 10, Name = AACompromise
* <p>
* Indicates that it is known or suspected that aspects of the AA validated in
* the attribute certificate, have been compromised. It applies to authority
* attribute (AA) certificates only. Unused in OpenSSL.
* </p>
*/
AA_COMPROMISE(10, "AACompromise");
private static final int NUM_CODES = 11;
private int code;
private String name;
RevocationReason(int code, String name) {
this.code = code;
this.name = name;
}
public int getCode() {
return code;
}
public String getName() {
return name;
}
// Map for quick lookups by name.
private static HashMap<String, RevocationReason> nameToReason = new HashMap<>();
/**
* Return the RevocationReason with the given code.
*
* @param code The code.
* @return The RevocationReason.
*/
public static RevocationReason fromCode(int code) {
if (code > NUM_CODES - 1 || code < 0 || code == 7) {
throw new IllegalArgumentException("CRL Reason code not recognized: " + code);
}
return RevocationReason.values()[code];
}
public static void initMap() {
nameToReason.put(UNSPECIFIED.getName(), UNSPECIFIED);
nameToReason.put(KEY_COMPROMISE.getName(), KEY_COMPROMISE);
nameToReason.put(CA_COMPROMISE.getName(), CA_COMPROMISE);
nameToReason.put(AFFILIATION_CHANGED.getName(), AFFILIATION_CHANGED);
nameToReason.put(SUPERSEDED.getName(), SUPERSEDED);
nameToReason.put(CESSATION_OF_OPERATION.getName(), CESSATION_OF_OPERATION);
nameToReason.put(CERTIFICATE_HOLD.getName(), CERTIFICATE_HOLD);
nameToReason.put(REMOVE_FROM_CRL.getName(), REMOVE_FROM_CRL);
nameToReason.put(PRIVILEGE_WITHDRAWN.getName(), PRIVILEGE_WITHDRAWN);
nameToReason.put(AA_COMPROMISE.getName(), AA_COMPROMISE);
}
/**
* Return the RevocationReason with the given name.
*
* @param name The name.
* @return The RevocationReason.
*/
public static RevocationReason fromName(String name) {
return nameToReason.get(name);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment