Skip to content

Commit

Permalink
Merge pull request #2119 from Akila94/cert-revocation-synapse-improve…
Browse files Browse the repository at this point in the history
…ment

Do cert revocation improvement to validate with only one client certificate in the request when the issuer of that cert is present in the client trust store
  • Loading branch information
isudana authored Jan 29, 2024
2 parents e8e70f0 + d02fc90 commit 818856a
Show file tree
Hide file tree
Showing 21 changed files with 806 additions and 275 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.synapse.transport.certificatevalidation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.transport.certificatevalidation.cache.CertCache;
import org.apache.synapse.transport.certificatevalidation.crl.CRLCache;
import org.apache.synapse.transport.certificatevalidation.crl.CRLVerifier;
import org.apache.synapse.transport.certificatevalidation.ocsp.OCSPCache;
import org.apache.synapse.transport.certificatevalidation.ocsp.OCSPVerifier;
import org.apache.synapse.transport.certificatevalidation.pathvalidation.CertificatePathValidator;
import org.apache.synapse.transport.nhttp.config.TrustStoreHolder;

import java.io.ByteArrayInputStream;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Optional;

/**
* Manager class responsible for verifying certificates. This class will use the available verifiers according to
* a predefined policy.
*/
public class CertificateVerificationManager {

private int cacheSize = Constants.CACHE_DEFAULT_ALLOCATED_SIZE;
private int cacheDelayMins = Constants.CACHE_DEFAULT_DELAY_MINS;
private boolean isFullCertChainValidationEnabled = true;
private boolean isCertExpiryValidationEnabled = false;
private static final Log log = LogFactory.getLog(CertificateVerificationManager.class);

public CertificateVerificationManager(Integer cacheAllocatedSize, Integer cacheDelayMins) {

if (cacheAllocatedSize != null && cacheAllocatedSize > Constants.CACHE_MIN_ALLOCATED_SIZE
&& cacheAllocatedSize < Constants.CACHE_MAX_ALLOCATED_SIZE) {
this.cacheSize = cacheAllocatedSize;
} else {
log.warn("The cache size is out of range. Hence, using the default cache size value of "
+ Constants.CACHE_DEFAULT_ALLOCATED_SIZE + ".");
}
if (cacheDelayMins != null && cacheDelayMins > Constants.CACHE_MIN_DELAY_MINS
&& cacheDelayMins < Constants.CACHE_MAX_DELAY_MINS) {
this.cacheDelayMins = cacheDelayMins;
} else {
log.warn("The cache delay is out of range. Hence, using the default cache delay value of "
+ Constants.CACHE_DEFAULT_DELAY_MINS + ".");
}
}

public CertificateVerificationManager(Integer cacheAllocatedSize, Integer cacheDelayMins,
boolean isFullCertChainValidationEnabled,
boolean isCertExpiryValidationEnabled) {

if (cacheAllocatedSize != null && cacheAllocatedSize > Constants.CACHE_MIN_ALLOCATED_SIZE
&& cacheAllocatedSize < Constants.CACHE_MAX_ALLOCATED_SIZE) {
this.cacheSize = cacheAllocatedSize;
} else {
log.warn("The cache size is out of range. Hence, using the default cache size value of "
+ Constants.CACHE_DEFAULT_ALLOCATED_SIZE + ".");
}
if (cacheDelayMins != null && cacheDelayMins > Constants.CACHE_MIN_DELAY_MINS
&& cacheDelayMins < Constants.CACHE_MAX_DELAY_MINS) {
this.cacheDelayMins = cacheDelayMins;
} else {
log.warn("The cache delay is out of range. Hence, using the default cache delay value of "
+ Constants.CACHE_DEFAULT_DELAY_MINS + ".");
}

this.isFullCertChainValidationEnabled = isFullCertChainValidationEnabled;
this.isCertExpiryValidationEnabled = isCertExpiryValidationEnabled;
}

/**
* This method verifies the given certificate chain or given peer certificate for revocation based on the
* requirement of full certificate chain validation. If full chain validation is enabled (default),
* the full certificate chain will be validated before checking the chain for revocation. If full chain validation
* is disabled, this method expects a single peer certificate, and it is validated with the immediate issuer
* certificate in the truststore (The truststore must contain the immediate issuer of the peer certificate).
* In both cases, OCSP and CRL verifiers are used for revocation verification.
* It first tries to verify using OCSP since OCSP verification is faster. If that fails it tries to do the
* verification using CRL.
*
* @param peerCertificates javax.security.cert.X509Certificate[] array of peer certificate chain from peer/client.
* @throws CertificateVerificationException
*/
public void verifyCertificateValidity(javax.security.cert.X509Certificate[] peerCertificates)
throws CertificateVerificationException {

X509Certificate[] convertedCertificates = convert(peerCertificates);

Optional<X509Certificate> peerCertOpt;
X509Certificate peerCert = null;
X509Certificate issuerCert = null;
String alias;

if (!isFullCertChainValidationEnabled) {

if (log.isDebugEnabled()) {
log.debug("Retrieving the issuer certificate from client truststore since full certificate chain " +
"validation is disabled");
}

KeyStore trustStore = TrustStoreHolder.getInstance().getClientTrustStore();
Enumeration<String> aliases;

// When full chain validation is disabled, only one cert is expected
peerCertOpt = Arrays.stream(convertedCertificates).findFirst();
if (peerCertOpt.isPresent()) {
peerCert = peerCertOpt.get();
} else {
throw new CertificateVerificationException("Peer certificate is not provided");
}

// Get cert cache and initialize it
CertCache certCache = CertCache.getCache();

if (certCache.getCacheValue(peerCert.getSerialNumber().toString()) == null) {

try {
aliases = trustStore.aliases();
} catch (KeyStoreException e) {
throw new CertificateVerificationException("Error while retrieving aliases from truststore", e);
}

while (aliases.hasMoreElements()) {

alias = aliases.nextElement();
try {
issuerCert = (X509Certificate) trustStore.getCertificate(alias);
} catch (KeyStoreException e) {
throw new CertificateVerificationException("Unable to read the certificate from " +
"truststore with the alias: " + alias, e);
}

if (issuerCert == null) {
throw new CertificateVerificationException("Issuer certificate not found in truststore");
}

try {
peerCert.verify(issuerCert.getPublicKey());

log.debug("Valid issuer certificate found in the client truststore. Caching..");

// Store the valid issuer cert in cache for future use
certCache.setCacheValue(peerCert.getSerialNumber().toString(), issuerCert);
if (log.isDebugEnabled()) {
log.debug("Issuer certificate with serial number: " + issuerCert.getSerialNumber()
.toString() + " has been cached against the serial number: " + peerCert
.getSerialNumber().toString() + " of the peer certificate.");
}
break;
} catch (SignatureException | CertificateException | NoSuchAlgorithmException |
InvalidKeyException | NoSuchProviderException e) {
// Unable to verify the signature. Check with the next certificate in the next loop traversal.
}
}
} else {
X509Certificate cachedIssuerCert = certCache.getCacheValue(peerCert.getSerialNumber().toString());
try {
peerCert.verify(cachedIssuerCert.getPublicKey());
} catch (SignatureException | CertificateException | NoSuchAlgorithmException |
InvalidKeyException |
NoSuchProviderException e) {
// Unable to verify the signature.
throw new CertificateVerificationException("Unable to verify the signature of the certificate.");
}
}
}

OCSPCache ocspCache = OCSPCache.getCache(cacheSize, cacheDelayMins);
CRLCache crlCache = CRLCache.getCache(cacheSize, cacheDelayMins);

RevocationVerifier[] verifiers = {new OCSPVerifier(ocspCache), new CRLVerifier(crlCache)};

for (RevocationVerifier verifier : verifiers) {
try {
if (isFullCertChainValidationEnabled) {

if (isCertExpiryValidationEnabled) {
log.debug("Validating certificate chain for expiry");
if (isExpired(convertedCertificates)) {
throw new CertificateVerificationException("One of the provided certificates are expired");
}
}

log.debug("Doing full certificate chain validation");
CertificatePathValidator pathValidator = new CertificatePathValidator(convertedCertificates,
verifier);
pathValidator.validatePath();
} else {

if (isCertExpiryValidationEnabled) {
log.debug("Validating the client certificate for expiry");
if (isExpired(convertedCertificates)) {
throw new CertificateVerificationException("The provided certificate is expired");
}
}

log.debug("Validating client certificate with the issuer certificate retrieved from" +
"the trust store");
verifier.checkRevocationStatus(peerCert, issuerCert);
}
return;
} catch (Exception e) {
log.debug("Certificate verification with " + verifier.getClass().getSimpleName() + " failed. ", e);
}
}
throw new CertificateVerificationException("Path Verification Failed for both OCSP and CRL");
}

/**
* @param certs array of javax.security.cert.X509Certificate[] s.
* @return the converted array of java.security.cert.X509Certificate[] s.
* @throws CertificateVerificationException
*/
private X509Certificate[] convert(javax.security.cert.X509Certificate[] certs)
throws CertificateVerificationException {
X509Certificate[] certChain = new X509Certificate[certs.length];
Throwable exceptionThrown;
for (int i = 0; i < certs.length; i++) {
try {
byte[] encoded = certs[i].getEncoded();
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
java.security.cert.CertificateFactory cf
= java.security.cert.CertificateFactory.getInstance("X.509");
certChain[i]=((X509Certificate)cf.generateCertificate(bis));
continue;
} catch (java.security.cert.CertificateEncodingException e) {
exceptionThrown = e;
} catch (javax.security.cert.CertificateEncodingException e) {
exceptionThrown = e;
} catch (java.security.cert.CertificateException e) {
exceptionThrown = e;
}
throw new CertificateVerificationException("Cant Convert certificates from javax to java", exceptionThrown);
}
return certChain;
}

/**
* Checks whether a provided certificate is expired or not at the time it is validated.
*
* @param certificates certificates to be validated for expiry
* @return true if one of the certs are expired, false otherwise
*/
public boolean isExpired(X509Certificate[] certificates) {

for (X509Certificate cert : certificates) {
try {
cert.checkValidity();
} catch (CertificateExpiredException e) {
log.error("Peer certificate is expired");
return true;
} catch (CertificateNotYetValidException e) {
log.error("Peer certificate is not valid yet");
return true;
}
}
return false;
}
}
Loading

0 comments on commit 818856a

Please sign in to comment.