Skip to content

Commit

Permalink
Add trust store holder and cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Akila94 committed Jan 4, 2024
1 parent 1f3e35c commit 2d245cb
Show file tree
Hide file tree
Showing 9 changed files with 459 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@

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.util.ConfigurationBuilderUtil;
import org.wso2.securevault.KeyStoreType;
import org.apache.synapse.transport.nhttp.config.TrustStoreHolder;

import java.io.ByteArrayInputStream;
import java.security.InvalidKeyException;
Expand All @@ -52,6 +52,18 @@ public class RevocationVerificationManager {
private boolean isFullCertChainValidationEnabled = false;
private static final Log log = LogFactory.getLog(RevocationVerificationManager.class);

public RevocationVerificationManager(Integer cacheAllocatedSize, Integer cacheDelayMins) {

if (cacheAllocatedSize != null && cacheAllocatedSize > Constants.CACHE_MIN_ALLOCATED_SIZE
&& cacheAllocatedSize < Constants.CACHE_MAX_ALLOCATED_SIZE) {
this.cacheSize = cacheAllocatedSize;
}
if (cacheDelayMins != null && cacheDelayMins > Constants.CACHE_MIN_DELAY_MINS
&& cacheDelayMins < Constants.CACHE_MAX_DELAY_MINS) {
this.cacheDelayMins = cacheDelayMins;
}
}

public RevocationVerificationManager(Integer cacheAllocatedSize, Integer cacheDelayMins,
boolean isFullCertChainValidationEnabled) {

Expand All @@ -67,8 +79,15 @@ public RevocationVerificationManager(Integer cacheAllocatedSize, Integer cacheDe
}

/**
* This method first tries to verify the given certificate chain using OCSP since OCSP verification is
* faster. If that fails it tries to do the verification using CRL.
* 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
*/
Expand All @@ -89,53 +108,71 @@ public void verifyRevocationStatus(javax.security.cert.X509Certificate[] peerCer
"validation is disabled");
}

KeyStore trustStore;
KeyStore trustStore = TrustStoreHolder.getInstance().getClientTrustStore();
Enumeration<String> aliases;
String truststorePath = System.getProperty("javax.net.ssl.trustStore");
String truststorePassword = System.getProperty("javax.net.ssl.trustStorePassword");;

try {
trustStore = ConfigurationBuilderUtil.getKeyStore(truststorePath, truststorePassword,
KeyStoreType.JKS.toString());
} catch (KeyStoreException e) {
throw new CertificateVerificationException("Error loading the truststore", e);
// 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");
}

try {
aliases = trustStore.aliases();
} catch (KeyStoreException e) {
throw new CertificateVerificationException("Error while retrieving aliases from truststore", e);
}
// Get cert cache and initialize it
CertCache certCache = CertCache.getCache();
certCache.init(cacheSize,cacheDelayMins);

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

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

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

// 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");
while (aliases.hasMoreElements()) {
try {
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");
}

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.
//todo: change this to a debug log after testing.
log.error("Unable to verify the signature, checking with the next certificate...");
}
}

} else {
X509Certificate cachedIssuerCert = certCache.getCacheValue(peerCert.getSerialNumber().toString());
try {
peerCert.verify(issuerCert.getPublicKey());
log.debug("Valid issuer certificate found in the client truststore");
break;
} catch (SignatureException | CertificateException | NoSuchAlgorithmException | InvalidKeyException |
peerCert.verify(cachedIssuerCert.getPublicKey());
} catch (SignatureException | CertificateException | NoSuchAlgorithmException |
InvalidKeyException |
NoSuchProviderException e) {
// Unable to verify the signature. Check with the next certificate.
log.error("Signature not matching for alias : " + alias + ", validate with next cert");
log.error("Signature not matching for alias validate with next cert");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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.cache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.commons.jmx.MBeanRegistrar;

import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* This is a cache to store a certificate against a unique string (can be a serial number). This is a singleton since
* more than one cache of this kind should not be allowed. This cache can be used by any place where certificate
* caching is needed.
*/
public class CertCache implements ManageableCache {

private static volatile CertCache cache;
private static volatile Map<String, TempCacheValue> hashMap = new ConcurrentHashMap<String, TempCacheValue>();
private static volatile Iterator<Map.Entry<String, TempCacheValue>> iterator = hashMap.entrySet().iterator();
private static volatile CacheManager cacheManager;
private static final Log log = LogFactory.getLog(CertCache.class);

private CertCache() {
}

public static CertCache getCache() {
//Double-checked locking
if (cache == null) {
synchronized (CertCache.class) {
if (cache == null) {
cache = new CertCache();
}
}
}
return cache;
}

/**
* This initialize the Cache with a CacheManager. If this method is called, a cache manager will not be used.
*
* @param size max size of the cache
* @param delay defines how frequently the CacheManager will be started
*/
public void init(int size, int delay) {
if (cacheManager == null) {
synchronized (CertCache.class) {
if (cacheManager == null) {
cacheManager = new CacheManager(cache, size, delay);
CacheController mbean = new CacheController(cache,cacheManager);
MBeanRegistrar.getInstance().registerMBean(mbean, "CacheController", "CertCacheController");
}
}
}
}

public synchronized X509Certificate getCacheValue(String serialNumber) {
CertCache.TempCacheValue cacheValue = hashMap.get(serialNumber);
if (cacheValue != null) {
return cacheValue.getValue();
} else
return null;
}

@Override
public ManageableCacheValue getNextCacheValue() {

//changes to the map are reflected on the keySet. And its iterator is weakly consistent. so will never
//throw concurrent modification exception.
if (iterator.hasNext()) {
return hashMap.get(iterator.next().getKey());
} else {
resetIterator();
return null;
}
}

@Override
public int getCacheSize() {

return hashMap.size();
}

@Override
public void resetIterator() {

iterator = hashMap.entrySet().iterator();
}

public static void resetCache() {

hashMap.clear();
}

public synchronized void setCacheValue(String serialNumber, X509Certificate cert) {
CertCache.TempCacheValue cacheValue = new CertCache.TempCacheValue(serialNumber, cert);

if (log.isDebugEnabled()) {
log.debug("Before set - HashMap size " + hashMap.size());
}
hashMap.put(serialNumber, cacheValue);
if (log.isDebugEnabled()) {
log.debug("After set - HashMap size " + hashMap.size());
}
}

public synchronized void removeCacheValue(String serialNumber) {

if (log.isDebugEnabled()) {
log.debug("Before remove - HashMap size " + hashMap.size());
}
hashMap.remove(serialNumber);
if (log.isDebugEnabled()) {
log.debug("After remove - HashMap size " + hashMap.size());
}
}

/**
* This is the wrapper class of the actual cache value which is a X509CRL.
*/
private class TempCacheValue implements ManageableCacheValue {

private final String serialNumber;
private final X509Certificate issuerCertificate;
private final long timeStamp = System.currentTimeMillis();

public TempCacheValue(String serialNumber, X509Certificate issuerCertificate) {

this.serialNumber = serialNumber;
this.issuerCertificate = issuerCertificate;
}

public X509Certificate getValue() {

return issuerCertificate;
}

public String getKey() {

return serialNumber;
}

/**
* CRL has a validity period. We can reuse a downloaded CRL within that period.
*/
public boolean isValid() {

// Will be always return true since we only set defined data.
return true;
}

public long getTimeStamp() {

return timeStamp;
}

/**
* Used by cacheManager to remove invalid entries.
*/
public void removeThisCacheValue() {

removeCacheValue(serialNumber);
}

public void updateCacheWithNewValue() {
// No implementation needed since there are no scenarios of the cache value being invalid.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,35 @@
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.asn1.*;
import org.apache.synapse.transport.certificatevalidation.CertificateVerificationException;
import org.apache.synapse.transport.certificatevalidation.Constants;
import org.apache.synapse.transport.certificatevalidation.RevocationStatus;
import org.apache.synapse.transport.certificatevalidation.RevocationVerifier;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.*;
import org.apache.synapse.transport.certificatevalidation.*;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;

import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
Expand Down
Loading

0 comments on commit 2d245cb

Please sign in to comment.