diff --git a/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java b/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java index 543a45cbad80578a4d245c7b5f9a7c75bb1d9cf0..787efaf5ea3da6e0f0eb59894ea418911d14edeb 100644 --- a/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java +++ b/jams-ca/src/main/java/net/jami/jams/ca/JamsCA.java @@ -37,6 +37,7 @@ import net.jami.jams.common.serialization.JsoniterRegistry; import org.bouncycastle.cert.X509CRLHolder; import org.bouncycastle.cert.ocsp.OCSPReq; import org.bouncycastle.cert.ocsp.OCSPResp; +import org.bouncycastle.jcajce.provider.asymmetric.X509; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; @@ -128,9 +129,8 @@ public class JamsCA implements CertificateAuthority { } } - @Override - public OCSPResp getOCSPResponse(OCSPReq ocspRequest) { - return ocspWorker.getOCSPResponse(ocspRequest); + public static OCSPResp getOCSPResponse(OCSPReq ocspRequest, X509Certificate cert) { + return ocspWorker.getOCSPResponse(ocspRequest, cert); } @Override diff --git a/jams-ca/src/main/java/net/jami/jams/ca/workers/ocsp/OCSPWorker.java b/jams-ca/src/main/java/net/jami/jams/ca/workers/ocsp/OCSPWorker.java index d60ae8fe43241ec398d4f7d219922b202a4780f5..416f24bd04f697f55370f8cc9a6858f542604cd2 100644 --- a/jams-ca/src/main/java/net/jami/jams/ca/workers/ocsp/OCSPWorker.java +++ b/jams-ca/src/main/java/net/jami/jams/ca/workers/ocsp/OCSPWorker.java @@ -55,6 +55,7 @@ import java.math.BigInteger; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -77,12 +78,12 @@ public class OCSPWorker extends X509Worker<String> { 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().getPublicKey().getEncoded()), digestCalculatorProvider.get(new DefaultDigestAlgorithmIdentifierFinder().find("SHA-1"))); + this.responderID = new RespID(SubjectPublicKeyInfo.getInstance(CA.getCertificate().getPublicKey().getEncoded()), 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) { + public OCSPResp getOCSPResponse(OCSPReq ocspRequest, X509Certificate cert) { try{ OCSPResp resp = validateRequest(ocspRequest); if(resp != null) return resp; //this means the request is invalid and we should notify the client. @@ -94,7 +95,7 @@ public class OCSPWorker extends X509Worker<String> { Extension[] extensions = responseExtensions.toArray(new Extension[responseExtensions.size()]); responseBuilder.setResponseExtensions(new Extensions(extensions)); for (Req request : ocspRequest.getRequestList()) { - addResponse(responseBuilder, request); + addResponse(responseBuilder, request, cert); } BasicOCSPResp basicResponse = responseBuilder.build( contentSigner, @@ -118,15 +119,31 @@ public class OCSPWorker extends X509Worker<String> { } - private CertificateSummary getCertificateSummary(BigInteger serial) { + private CertificateSummary getCertificateSummary(BigInteger serial, X509Certificate cert) { 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); + LocalDateTime expirationTime = LocalDateTime.ofInstant(cert.getNotAfter().toInstant(), ZoneId.systemDefault()); + + + if(x509CRLEntryHolder != null) return CertificateSummary.newBuilder() + .withStatus(CertificateStatus.REVOKED) + .withSerialNumber(serial) + .withRevocationTime(LocalDateTime.ofInstant(x509CRLEntryHolder.getRevocationDate().toInstant(), ZoneId.systemDefault())) + .build(); + else if (expirationTime.isBefore(LocalDateTime.now())) { + return CertificateSummary.newBuilder() + .withStatus(CertificateStatus.EXPIRED) + .withSerialNumber(serial) + .withExpirationTime(expirationTime) + .build(); + } + else return CertificateSummary.newBuilder() + .withStatus(CertificateStatus.VALID) + .withSerialNumber(serial) + .build(); } - private void addResponse(BasicOCSPRespBuilder responseBuilder, Req request) throws OCSPException{ + private void addResponse(BasicOCSPRespBuilder responseBuilder, Req request, X509Certificate cert) throws OCSPException{ CertificateID certificateID = request.getCertID(); // Build Extensions Extensions extensions = new Extensions(new Extension[]{}); @@ -136,7 +153,7 @@ public class OCSPWorker extends X509Worker<String> { if (nonceExtension != null) extensions = new Extensions(nonceExtension); } responseBuilder.addResponse(certificateID, - OCSPCertificateStatusMapper.getStatus(getCertificateSummary(request.getCertID().getSerialNumber())), + OCSPCertificateStatusMapper.getStatus(getCertificateSummary(request.getCertID().getSerialNumber(), cert)), new Date(), new Date(new Date().getTime() + crlLifetime), extensions); diff --git a/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java b/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java index 0d740e9477c577366d7cc5d5dc68f7f488de058a..dfd933c124010a02e185d97be35d7aecb575b151 100644 --- a/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java +++ b/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/CertificateAuthority.java @@ -42,7 +42,6 @@ public interface CertificateAuthority { void revokeCertificate(RevocationRequest revocationRequest); AtomicReference<X509CRLHolder> getLatestCRL(); String getLatestCRLPEMEncoded(); - OCSPResp getOCSPResponse(OCSPReq ocspRequest); X509Certificate getCA(); boolean shutdownThreads(); diff --git a/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/ocsp/CertificateSummary.java b/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/ocsp/CertificateSummary.java index 7289c3615203b5f9930879c90553befe8b59f481..d1f39b613c9c0891df227682f02400721b197303 100644 --- a/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/ocsp/CertificateSummary.java +++ b/jams-common/src/main/java/net/jami/jams/common/cryptoengineapi/ocsp/CertificateSummary.java @@ -31,31 +31,129 @@ import javax.security.auth.x500.X500Principal; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; @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; + private final CertificateStatus status; + private final LocalDateTime expirationTime; + private final LocalDateTime revocationTime; + private final RevocationReason revocationReason; + private final BigInteger serialNumber; + private final String fileName; + private final X500Principal subjectDN; - public CertificateSummary(CertificateStatus status, LocalDateTime revocationTime, BigInteger serialNumber) { - this.status = status; - this.revocationTime = revocationTime; - this.serialNumber = serialNumber; + private final LocalDateTime thisUpdateTime; + + public CertificateStatus getStatus() { + return status; + } + + public LocalDateTime getExpirationTime() { + return expirationTime; + } + + public LocalDateTime getRevocationTime() { + return revocationTime; + } + + public RevocationReason getRevocationReason() { + return revocationReason; + } + + public BigInteger getSerialNumber() { + return serialNumber; + } + + public String getFileName() { + return fileName; + } + + public X500Principal getSubjectDN() { + return subjectDN; + } + + public LocalDateTime getThisUpdateTime() { + return thisUpdateTime; } - public CertificateSummary(CertificateStatus status, BigInteger serialNumber) { - this.status = status; - this.serialNumber = serialNumber; + private CertificateSummary(Builder builder) { + status = builder.status; + expirationTime = builder.expirationTime; + revocationTime = builder.revocationTime; + revocationReason = builder.revocationReason; + serialNumber = builder.serialNumber; + fileName = builder.fileName; + subjectDN = builder.subjectDN; + thisUpdateTime = builder.thisUpdateTime == null ? LocalDateTime.now() : builder.thisUpdateTime; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static final class Builder { + private CertificateStatus status = CertificateStatus.UNKNOWN; + private LocalDateTime expirationTime = null; + private LocalDateTime revocationTime = null; + private RevocationReason revocationReason = null; + private BigInteger serialNumber = null; + private String fileName = null; + private X500Principal subjectDN = null; + + private LocalDateTime thisUpdateTime = null; + + private Builder() { + } + + public Builder withStatus(CertificateStatus val) { + status = val; + return this; + } + + public Builder withExpirationTime(LocalDateTime val) { + expirationTime = val; + return this; + } + + public Builder withRevocationTime(LocalDateTime val) { + revocationTime = val; + return this; + } + + public Builder withRevocationReason(RevocationReason val) { + revocationReason = val; + return this; + } + + public Builder withSerialNumber(BigInteger val) { + serialNumber = val; + return this; + } + + public Builder withFileName(String val) { + fileName = val; + return this; + } + + public Builder withSubjectDN(X500Principal val) { + subjectDN = val; + return this; + } + + public Builder withThisUpdateTime(LocalDateTime val) { + thisUpdateTime = val; + return this; + } + + public CertificateSummary build() { + return new CertificateSummary(this); + } } } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/x509/OCSPServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/x509/OCSPServlet.java index 00b716271ae04d3e6dfe23e7d57e74ee4f20bf09..0b17f4069b80b291957f16b61804b4d8760c60ed 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/x509/OCSPServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/x509/OCSPServlet.java @@ -1,37 +1,48 @@ /* -* Copyright (C) 2020 by Savoir-faire Linux -* Authors: William Enright <william.enright@savoirfairelinux.com> -* Ndeye Anna Ndiaye <anna.ndiaye@savoirfairelinux.com> -* Johnny Flores <johnny.flores@savoirfairelinux.com> -* Mohammed Raza <mohammed.raza@savoirfairelinux.com> -* Felix Sidokhine <felix.sidokhine@savoirfairelinux.com> -* -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ + * Copyright (C) 2020 by Savoir-faire Linux + * Authors: William Enright <william.enright@savoirfairelinux.com> + * Ndeye Anna Ndiaye <anna.ndiaye@savoirfairelinux.com> + * Johnny Flores <johnny.flores@savoirfairelinux.com> + * Mohammed Raza <mohammed.raza@savoirfairelinux.com> + * Felix Sidokhine <felix.sidokhine@savoirfairelinux.com> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package net.jami.jams.server.servlets.x509; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import net.jami.jams.ca.JamsCA; +import net.jami.jams.common.utils.X509Utils; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.cert.ocsp.CertificateID; import org.bouncycastle.cert.ocsp.OCSPReq; +import org.bouncycastle.cert.ocsp.OCSPReqBuilder; import org.bouncycastle.cert.ocsp.OCSPResp; +import org.bouncycastle.operator.DigestCalculator; +import org.bouncycastle.operator.DigestCalculatorProvider; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import java.io.IOException; - -import static net.jami.jams.server.Server.certificateAuthority; +import java.security.cert.X509Certificate; @WebServlet("/api/ocsp") public class OCSPServlet extends HttpServlet { @@ -44,13 +55,32 @@ public class OCSPServlet extends HttpServlet { for(int i=0;i<content.length;i++){ req.getInputStream().read(content); } - OCSPReq ocspReq = new OCSPReq(content); - OCSPResp ocspResp = certificateAuthority.getOCSPResponse(ocspReq); + JcaDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder(); + DigestCalculatorProvider digestCalculatorProvider = digestCalculatorProviderBuilder.build(); + DigestCalculator digestCalculator = digestCalculatorProvider.get(CertificateID.HASH_SHA1); + // Generate the id for the certificate we are looking for + X509Certificate targetCert = X509Utils.getCertificateFromPEMString(new String(content)); + + if (targetCert != null) { + CertificateID id = new CertificateID(digestCalculator, + new JcaX509CertificateHolder(JamsCA.CA.getCertificate()), targetCert.getSerialNumber()); + + // basic request generation with nonce + OCSPReqBuilder gen = new OCSPReqBuilder(); + gen.addRequest(id); - if (ocspResp != null) { - byte[] respBytes = ocspResp.getEncoded(); - resp.getOutputStream().write(respBytes); - } else resp.setStatus(404); + // create details for nonce extension + String nonce = String.valueOf(System.nanoTime()); + Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(new DEROctetString(nonce.getBytes()).getEncoded())); + gen.setRequestExtensions(new Extensions(new Extension[]{ext})); + OCSPReq ocspReq = gen.build(); + OCSPResp response = JamsCA.getOCSPResponse(ocspReq, targetCert); + + if (response != null) { + byte[] respBytes = response.getEncoded(); + resp.getOutputStream().write(respBytes); + } else resp.setStatus(404); + } } catch (Exception e) { resp.sendError(404, "Could not find the requested certificate!");