diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/CertificateVerificationManager.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/CertificateVerificationManager.java new file mode 100644 index 0000000000..a1c9a1a831 --- /dev/null +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/CertificateVerificationManager.java @@ -0,0 +1,291 @@ +/* + * 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) { + + this(cacheAllocatedSize, cacheDelayMins, true, false); + } + + 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); + + X509Certificate peerCert = null; + X509Certificate issuerCert = null; + + if (!isFullCertChainValidationEnabled) { + peerCert = getPeerCertificate(convertedCertificates); + issuerCert = getVerifiedIssuerCertOfPeerCert(peerCert, CertCache.getCache()); + } + + OCSPCache ocspCache = OCSPCache.getCache(cacheSize, cacheDelayMins); + CRLCache crlCache = CRLCache.getCache(cacheSize, cacheDelayMins); + + RevocationVerifier[] verifiers = {new OCSPVerifier(ocspCache), new CRLVerifier(crlCache)}; + RevocationStatus revocationStatus = null; + + 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(); + return; + } 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"); + revocationStatus = verifier.checkRevocationStatus(peerCert, issuerCert); + if (RevocationStatus.GOOD.toString().equals(revocationStatus.toString())) { + 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", e); + return true; + } catch (CertificateNotYetValidException e) { + log.error("Peer certificate is not valid yet", e); + return true; + } + } + return false; + } + + public X509Certificate getPeerCertificate(X509Certificate[] convertedCertificates) + throws CertificateVerificationException { + + Optional peerCertOpt = Arrays.stream(convertedCertificates).findFirst(); + if (peerCertOpt.isPresent()) { + return peerCertOpt.get(); + } else { + throw new CertificateVerificationException("Peer certificate is not provided"); + } + } + + public X509Certificate getVerifiedIssuerCertOfPeerCert(X509Certificate peerCert, CertCache certCache) + throws CertificateVerificationException { + + if (certCache.getCacheValue(peerCert.getSerialNumber().toString()) != null) { + + X509Certificate cachedIssuerCert = certCache.getCacheValue(peerCert.getSerialNumber().toString()); + if (!isPeerCertVerified(peerCert, cachedIssuerCert)) { + throw new CertificateVerificationException("Unable to verify the signature of the certificate."); + } else { + return cachedIssuerCert; + } + } else { + boolean isIssuerCertVerified = false; + KeyStore trustStore = TrustStoreHolder.getInstance().getClientTrustStore(); + Enumeration aliases; + X509Certificate issuerCert = null; + + try { + aliases = trustStore.aliases(); + } catch (KeyStoreException e) { + throw new CertificateVerificationException("Error while retrieving aliases from truststore", e); + } + + while (aliases.hasMoreElements()) { + + String 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()); + isIssuerCertVerified = true; + break; + } catch (SignatureException | CertificateException | NoSuchAlgorithmException | + InvalidKeyException | NoSuchProviderException e) { + // Unable to verify the signature. Check with the next certificate in the next loop traversal. + } + } + + if (isIssuerCertVerified) { + 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."); + } + return issuerCert; + } else { + throw new CertificateVerificationException("Certificate verification failed."); + } + } + } + + public boolean isPeerCertVerified(X509Certificate peerCert, X509Certificate issuerCert) { + + try { + peerCert.verify(issuerCert.getPublicKey()); + return true; + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException + | SignatureException e) { + return false; + } + } +} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationManager.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationManager.java deleted file mode 100644 index 338b8a8a36..0000000000 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationManager.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.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 java.io.ByteArrayInputStream; -import java.security.cert.X509Certificate; - -/** - * Manager class responsible for verifying certificates. This class will use the available verifiers according to - * a predefined policy. - */ -public class RevocationVerificationManager { - - private int cacheSize = Constants.CACHE_DEFAULT_ALLOCATED_SIZE; - private int cacheDelayMins = Constants.CACHE_DEFAULT_DELAY_MINS; - 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; - } - } - - /** - * 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. - * @param peerCertificates javax.security.cert.X509Certificate[] array of peer certificate chain from peer/client. - * @throws CertificateVerificationException - */ - public void verifyRevocationStatus(javax.security.cert.X509Certificate[] peerCertificates) - throws CertificateVerificationException { - - X509Certificate[] convertedCertificates = convert(peerCertificates); - - long start = System.currentTimeMillis(); - - OCSPCache ocspCache = OCSPCache.getCache(); - ocspCache.init(cacheSize, cacheDelayMins); - CRLCache crlCache = CRLCache.getCache(); - crlCache.init(cacheSize, cacheDelayMins); - - RevocationVerifier[] verifiers = {new OCSPVerifier(ocspCache), new CRLVerifier(crlCache)}; - - for (RevocationVerifier verifier : verifiers) { - try { - CertificatePathValidator pathValidator = new CertificatePathValidator(convertedCertificates, verifier); - pathValidator.validatePath(); - log.info("Path verification Successful. Took " + (System.currentTimeMillis() - start) + " ms."); - return; - } catch (Exception e) { - log.info(verifier.getClass().getSimpleName() + " failed."); - 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; - } -} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/cache/CertCache.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/cache/CertCache.java new file mode 100644 index 0000000000..37c47e7aef --- /dev/null +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/cache/CertCache.java @@ -0,0 +1,167 @@ +/* + * 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.transport.certificatevalidation.Constants; + +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 hashMap = new ConcurrentHashMap(); + private static volatile Iterator> 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(); + cacheManager = new CacheManager(cache, Constants.CACHE_DEFAULT_ALLOCATED_SIZE, + Constants.CACHE_DEFAULT_DELAY_MINS); + } + } + } + return cache; + } + + public synchronized X509Certificate getCacheValue(String serialNumber) { + CertCacheValue cacheValue = hashMap.get(serialNumber); + if (cacheValue != null) { + return cacheValue.getValue(); + } else + return null; + } + + @Override + public ManageableCacheValue getNextCacheValue() { + + 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) { + CertCacheValue cacheValue = new CertCacheValue(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. + */ + private class CertCacheValue implements ManageableCacheValue { + + private final String serialNumber; + private final X509Certificate issuerCertificate; + private final long timeStamp = System.currentTimeMillis(); + + public CertCacheValue(String serialNumber, X509Certificate issuerCertificate) { + + this.serialNumber = serialNumber; + this.issuerCertificate = issuerCertificate; + } + + public X509Certificate getValue() { + + return issuerCertificate; + } + + public String getKey() { + + return serialNumber; + } + + 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. + } + } +} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/crl/CRLCache.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/crl/CRLCache.java index 11db425524..357ecf10ce 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/crl/CRLCache.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/crl/CRLCache.java @@ -20,7 +20,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.apache.synapse.commons.jmx.MBeanRegistrar; import org.apache.synapse.transport.certificatevalidation.cache.CacheController; import org.apache.synapse.transport.certificatevalidation.cache.CacheManager; @@ -50,12 +49,13 @@ public class CRLCache implements ManageableCache { private CRLCache() { } - public static CRLCache getCache() { + public static CRLCache getCache(int cacheSize, int cacheDelayMins) { //Double checked locking if (cache == null) { synchronized (CRLCache.class) { if (cache == null) { cache = new CRLCache(); + cacheManager = new CacheManager(cache, cacheSize, cacheDelayMins); } } } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPCache.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPCache.java index b01d509867..819e077580 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPCache.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPCache.java @@ -27,7 +27,10 @@ import org.apache.synapse.transport.certificatevalidation.cache.ManageableCache; import org.apache.synapse.transport.certificatevalidation.cache.ManageableCacheValue; import org.bouncycastle.asn1.ocsp.OCSPResponseStatus; -import org.bouncycastle.cert.ocsp.*; +import org.bouncycastle.cert.ocsp.BasicOCSPResp; +import org.bouncycastle.cert.ocsp.OCSPReq; +import org.bouncycastle.cert.ocsp.OCSPResp; +import org.bouncycastle.cert.ocsp.SingleResp; import java.math.BigInteger; import java.util.Date; @@ -51,12 +54,14 @@ public class OCSPCache implements ManageableCache { private OCSPCache() {} - public static OCSPCache getCache() { + public static OCSPCache getCache(int cacheSize, int cacheDelayMins) { //Double checked locking if (cache == null) { synchronized (OCSPCache.class) { - if (cache == null) + if (cache == null) { cache = new OCSPCache(); + cacheManager = new CacheManager(cache, cacheSize, cacheDelayMins); + } } } return cache; diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPVerifier.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPVerifier.java index d9a3a11039..4ba283f9e3 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPVerifier.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/certificatevalidation/ocsp/OCSPVerifier.java @@ -20,20 +20,42 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.bouncycastle.asn1.*; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +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; @@ -52,6 +74,13 @@ public OCSPVerifier(OCSPCache cache) { this.cache = cache; } + public static final String CONTENT_TYPE = "Content-Type"; + public static final String JSON_TYPE ="application/json"; + public static final String ACCEPT_TYPE = "Accept"; + public static final String OCSP_REQUEST_TYPE = "application/ocsp-request"; + public static final String OCSP_RESPONSE_TYPE = "application/ocsp-response"; + + /** * Gets the revocation status (Good, Revoked or Unknown) of the given peer certificate. * @@ -130,35 +159,31 @@ private RevocationStatus getRevocationStatus(SingleResp resp) throws Certificate */ protected OCSPResp getOCSPResponce(String serviceUrl, OCSPReq request) throws CertificateVerificationException { - try { - //Todo: Use http client. - byte[] array = request.getEncoded(); - if (serviceUrl.startsWith("http")) { - HttpURLConnection con; - URL url = new URL(serviceUrl); - con = (HttpURLConnection) url.openConnection(); - con.setRequestProperty("Content-Type", "application/ocsp-request"); - con.setRequestProperty("Accept", "application/ocsp-response"); - con.setDoOutput(true); - OutputStream out = con.getOutputStream(); - DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out)); - dataOut.write(array); - - dataOut.flush(); - dataOut.close(); - - //Check errors in response: - if (con.getResponseCode() / 100 != 2) { - throw new CertificateVerificationException("Error getting ocsp response." + - "Response code is " + con.getResponseCode()); - } + if (log.isDebugEnabled()) { + log.debug("Initiating HTTP request to URL: " + serviceUrl + " to get the OCSP response"); + } + + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + HttpPost httpPost = new HttpPost(serviceUrl); - //Get Response - InputStream in = (InputStream) con.getContent(); - return new OCSPResp(in); - } else { - throw new CertificateVerificationException("Only http is supported for ocsp calls"); + // adding request timeout configurations + if (httpPost.getConfig() == null) { + httpPost.setConfig(RequestConfig.custom().build()); + } + httpPost.addHeader(CONTENT_TYPE, OCSP_REQUEST_TYPE); + httpPost.addHeader(ACCEPT_TYPE, OCSP_RESPONSE_TYPE); + httpPost.setEntity(new ByteArrayEntity(request.getEncoded(), ContentType.create(JSON_TYPE))); + HttpResponse httpResponse = client.execute(httpPost); + + //Check errors in response, if response status code is not 200 (success) range, throws exception + // eg: if response code is 200 (success) or 201 (accepted) return true, + // if response code is 404 (not found) or 500 throw exception + if (httpResponse.getStatusLine().getStatusCode() / 100 != 2) { + throw new CertificateVerificationException("Error getting ocsp response." + + "Response code is " + httpResponse.getStatusLine().getStatusCode()); } + InputStream in = httpResponse.getEntity().getContent(); + return new OCSPResp(in); } catch (IOException e) { throw new CertificateVerificationException("Cannot get ocspResponse from url: " + serviceUrl, e); } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ClientSSLSetupHandler.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ClientSSLSetupHandler.java index 24977a7115..caf0c87e73 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ClientSSLSetupHandler.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ClientSSLSetupHandler.java @@ -18,22 +18,21 @@ */ package org.apache.synapse.transport.http.conn; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; - import org.apache.http.conn.ssl.AbstractVerifier; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.nio.reactor.IOSession; import org.apache.http.nio.reactor.ssl.SSLSetupHandler; import org.apache.synapse.transport.certificatevalidation.CertificateVerificationException; -import org.apache.synapse.transport.certificatevalidation.RevocationVerificationManager; +import org.apache.synapse.transport.certificatevalidation.CertificateVerificationManager; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; public class ClientSSLSetupHandler implements SSLSetupHandler { @@ -139,10 +138,10 @@ public void verify( }; private final X509HostnameVerifier hostnameVerifier; - private final RevocationVerificationManager verificationManager; + private final CertificateVerificationManager verificationManager; public ClientSSLSetupHandler(final X509HostnameVerifier hostnameVerifier, - final RevocationVerificationManager verificationManager) { + final CertificateVerificationManager verificationManager) { this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : DEFAULT; this.verificationManager = verificationManager; } @@ -184,7 +183,7 @@ public void verify(IOSession iosession, SSLSession sslsession) throws SSLExcepti if (verificationManager!=null) { try { - verificationManager.verifyRevocationStatus(sslsession.getPeerCertificateChain()); + verificationManager.verifyCertificateValidity(sslsession.getPeerCertificateChain()); } catch (CertificateVerificationException e) { throw new SSLException("Certificate Chain Validation failed for host : " + address, e); } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ServerSSLSetupHandler.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ServerSSLSetupHandler.java index a4bc324ce1..9898979455 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ServerSSLSetupHandler.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/http/conn/ServerSSLSetupHandler.java @@ -18,15 +18,14 @@ */ package org.apache.synapse.transport.http.conn; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; - import org.apache.http.nio.reactor.IOSession; import org.apache.http.nio.reactor.ssl.SSLSetupHandler; import org.apache.synapse.transport.certificatevalidation.CertificateVerificationException; -import org.apache.synapse.transport.certificatevalidation.RevocationVerificationManager; +import org.apache.synapse.transport.certificatevalidation.CertificateVerificationManager; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -36,12 +35,12 @@ public class ServerSSLSetupHandler implements SSLSetupHandler { private final SSLClientAuth clientAuth; /** Enabled SSL handshake protocols (e.g. SSLv3, TLSv1) */ private final String[] httpsProtocols; - private RevocationVerificationManager verificationManager; + private CertificateVerificationManager verificationManager; /** Ciphers enabled in axis2.xml, enabled all if null*/ private final String[] preferredCiphers; public ServerSSLSetupHandler(final SSLClientAuth clientAuth, final String[] httpsProtocols, - final RevocationVerificationManager verificationManager, final String[] preferredCiphers) { + final CertificateVerificationManager verificationManager, final String[] preferredCiphers) { this.clientAuth = clientAuth; this.httpsProtocols = httpsProtocols; this.verificationManager = verificationManager; @@ -78,7 +77,7 @@ public void verify( if (verificationManager != null) { try { - verificationManager.verifyRevocationStatus(sslsession.getPeerCertificateChain()); + verificationManager.verifyCertificateValidity(sslsession.getPeerCertificateChain()); } catch (CertificateVerificationException e) { SocketAddress remoteAddress = iosession.getRemoteAddress(); String address; diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java index bf504b9999..f076479c7d 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java @@ -19,27 +19,6 @@ package org.apache.synapse.transport.nhttp.config; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; - -import org.apache.axiom.om.OMAttribute; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.axis2.AxisFault; @@ -52,8 +31,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.params.HttpParams; -import org.apache.synapse.commons.crypto.CryptoConstants; -import org.apache.synapse.transport.certificatevalidation.RevocationVerificationManager; +import org.apache.synapse.transport.certificatevalidation.CertificateVerificationManager; import org.apache.synapse.transport.exceptions.InvalidConfigurationException; import org.apache.synapse.transport.http.conn.ClientConnFactory; import org.apache.synapse.transport.http.conn.ClientSSLSetupHandler; @@ -63,8 +41,25 @@ import org.apache.synapse.transport.nhttp.util.SecureVaultValueReader; import org.wso2.securevault.SecretResolver; import org.wso2.securevault.SecretResolverFactory; -import org.wso2.securevault.SecureVaultException; -import org.wso2.securevault.commons.MiscellaneousUtil; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; public class ClientConnFactoryBuilder { @@ -87,7 +82,7 @@ public ClientConnFactoryBuilder(final TransportOutDescription transportOut) { this.transportOut = transportOut; this.name = transportOut.getName().toUpperCase(Locale.US); } - + public ClientConnFactoryBuilder parseSSL() throws AxisFault { Parameter keyParam = transportOut.getParameter("keystore"); Parameter trustParam = transportOut.getParameter("truststore"); @@ -133,7 +128,7 @@ public ClientConnFactoryBuilder parseSSL() throws AxisFault { final Parameter cvp = transportOut.getParameter("CertificateRevocationVerifier"); final String cvEnable = cvp != null ? cvp.getParameterElement().getAttribute(new QName("enable")).getAttributeValue() : null; - RevocationVerificationManager revocationVerifier = null; + CertificateVerificationManager certificateVerifier = null; if ("true".equalsIgnoreCase(cvEnable)) { String cacheSizeString = cvp.getParameterElement().getFirstChildWithName(new QName("CacheSize")).getText(); @@ -146,7 +141,7 @@ public ClientConnFactoryBuilder parseSSL() throws AxisFault { cacheDelay = new Integer(cacheDelayString); } catch (NumberFormatException e) { } - revocationVerifier = new RevocationVerificationManager(cacheSize, cacheDelay); + certificateVerifier = new CertificateVerificationManager(cacheSize, cacheDelay); } // Process HttpProtocols @@ -167,7 +162,7 @@ public ClientConnFactoryBuilder parseSSL() throws AxisFault { } // Initiated separately to cater setting https protocols - ClientSSLSetupHandler clientSSLSetupHandler = new ClientSSLSetupHandler(hostnameVerifier, revocationVerifier); + ClientSSLSetupHandler clientSSLSetupHandler = new ClientSSLSetupHandler(hostnameVerifier, certificateVerifier); if (null != httpsProtocols) { clientSSLSetupHandler.setHttpsProtocols(httpsProtocols); @@ -374,7 +369,6 @@ private SSLContext createSSLContext(OMElement keyStoreElt, OMElement trustStoreE TrustManagerFactory.getDefaultAlgorithm()); trustManagerfactory.init(trustStore); trustManagers = trustManagerfactory.getTrustManagers(); - } catch (GeneralSecurityException gse) { log.error(name + " Error loading Key store : " + location, gse); throw new AxisFault("Error loading Key store : " + location, gse); @@ -424,9 +418,9 @@ private SSLContext createSSLContext(OMElement keyStoreElt, OMElement trustStoreE keyStoreElt.getFirstChildWithName(new QName("Password"))); String keyPassword = SecureVaultValueReader.getSecureVaultValue(secretResolver, keyStoreElt.getFirstChildWithName(new QName("KeyPassword"))); - - try (FileInputStream fis = new FileInputStream(location)) { - KeyStore keyStore = KeyStore.getInstance(type); + + try (FileInputStream fis = new FileInputStream(location)) { + KeyStore keyStore = KeyStore.getInstance(type); if (log.isDebugEnabled()) { log.debug(name + " Loading Identity Keystore from : " + location); } @@ -475,7 +469,7 @@ private SSLContext createSSLContext(OMElement keyStoreElt, OMElement trustStoreE } catch (IOException ioe) { log.error(name + " Error opening Key store : " + location, ioe); throw new AxisFault("Error opening Key store : " + location, ioe); - } + } } else if (novalidatecert) { if (log.isWarnEnabled()) { log.warn(name + " Server certificate validation (trust) has been disabled. " + diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ServerConnFactoryBuilder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ServerConnFactoryBuilder.java index 817c11e99b..b8d92ea26c 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ServerConnFactoryBuilder.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ServerConnFactoryBuilder.java @@ -26,11 +26,12 @@ import org.apache.axis2.description.Parameter; import org.apache.axis2.description.TransportInDescription; import org.apache.axis2.transport.base.ParamUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; import org.apache.http.params.HttpParams; -import org.apache.synapse.transport.certificatevalidation.RevocationVerificationManager; +import org.apache.synapse.transport.certificatevalidation.CertificateVerificationManager; import org.apache.synapse.transport.http.conn.SSLClientAuth; import org.apache.synapse.transport.http.conn.SSLContextDetails; import org.apache.synapse.transport.http.conn.ServerConnFactory; @@ -40,6 +41,13 @@ import org.wso2.securevault.SecretResolver; import org.wso2.securevault.SecretResolverFactory; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.xml.namespace.QName; import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; @@ -54,14 +62,6 @@ import java.util.Locale; import java.util.Map; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509KeyManager; -import javax.xml.namespace.QName; - public class ServerConnFactoryBuilder { private final Log log = LogFactory.getLog(ServerConnFactoryBuilder.class); @@ -73,6 +73,7 @@ public class ServerConnFactoryBuilder { protected SSLContextDetails ssl; private Map sslByIPMap = null; private ConfigurationContext configurationContext; + CertificateVerificationManager certificateVerifier = null; public ServerConnFactoryBuilder(final TransportInDescription transportIn, final HttpHost host, ConfigurationContext configurationContext) { @@ -89,13 +90,13 @@ public ServerConnFactoryBuilder(final TransportInDescription transportIn, final } protected SSLContextDetails createSSLContext( - final OMElement keyStoreEl, - final OMElement trustStoreEl, - final OMElement cientAuthEl, - final OMElement httpsProtocolsEl, - final OMElement preferredCiphersEl, - final RevocationVerificationManager verificationManager, - final String sslProtocol) throws AxisFault { + final OMElement keyStoreEl, + final OMElement trustStoreEl, + final OMElement cientAuthEl, + final OMElement httpsProtocolsEl, + final OMElement preferredCiphersEl, + final CertificateVerificationManager verificationManager, + final String sslProtocol) throws AxisFault { SecretResolver secretResolver; if (configurationContext != null && configurationContext.getAxisConfiguration() != null) { @@ -114,7 +115,7 @@ protected SSLContextDetails createSSLContext( final OMElement cientAuthEl, final OMElement httpsProtocolsEl, final OMElement preferredCiphersEl, - final RevocationVerificationManager verificationManager, + final CertificateVerificationManager verificationManager, final String sslProtocol, final SecretResolver secretResolver) throws AxisFault { KeyManager[] keymanagers = null; @@ -145,7 +146,7 @@ protected SSLContextDetails createSSLContext( keyStore.load(fis, storePassword.toCharArray()); KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( - KeyManagerFactory.getDefaultAlgorithm()); + KeyManagerFactory.getDefaultAlgorithm()); kmfactory.init(keyStore, keyPassword.toCharArray()); keymanagers = kmfactory.getKeyManagers(); if (log.isInfoEnabled() && keymanagers != null) { @@ -182,6 +183,7 @@ protected SSLContextDetails createSSLContext( } if (trustStoreEl != null) { + String location = getValueOfElementWithLocalName(trustStoreEl, "Location"); String type = getValueOfElementWithLocalName(trustStoreEl, "Type"); OMElement storePasswordEl = trustStoreEl.getFirstChildWithName(new QName("Password")); @@ -203,7 +205,7 @@ protected SSLContextDetails createSSLContext( TrustManagerFactory.getDefaultAlgorithm()); trustManagerfactory.init(trustStore); trustManagers = trustManagerfactory.getTrustManagers(); - + TrustStoreHolder.getInstance().setClientTrustStore(trustStore); } catch (GeneralSecurityException gse) { log.error(name + " Error loading Key store : " + location, gse); throw new AxisFault("Error loading Key store : " + location, gse); @@ -293,8 +295,7 @@ public ServerConnFactoryBuilder parseSSL() throws AxisFault { final Parameter cvp = transportIn.getParameter("CertificateRevocationVerifier"); final String cvEnable = cvp != null ? - cvp.getParameterElement().getAttribute(new QName("enable")).getAttributeValue() : null; - RevocationVerificationManager revocationVerifier = null; + cvp.getParameterElement().getAttribute(new QName("enable")).getAttributeValue() : null; if ("true".equalsIgnoreCase(cvEnable)) { String cacheSizeString = cvp.getParameterElement().getFirstChildWithName(new QName("CacheSize")).getText(); @@ -305,12 +306,33 @@ public ServerConnFactoryBuilder parseSSL() throws AxisFault { cacheSize = new Integer(cacheSizeString); cacheDelay = new Integer(cacheDelayString); } - catch (NumberFormatException e) {} - revocationVerifier = new RevocationVerificationManager(cacheSize, cacheDelay); + catch (NumberFormatException e) { + throw new AxisFault("Cache size or Cache delay values are malformed", e); + } + + // Checking whether the full certificate chain validation is enabled or not. + boolean isFullCertChainValidationEnabled = true; + boolean isCertExpiryValidationEnabled = false; + OMElement fullCertChainValidationConfig = cvp.getParameterElement() + .getFirstChildWithName(new QName("FullChainValidation")); + OMElement certExpiryValidationConfig = cvp.getParameterElement() + .getFirstChildWithName(new QName("ExpiryValidation")); + + if (fullCertChainValidationConfig != null + && StringUtils.equals("false", fullCertChainValidationConfig.getText())) { + isFullCertChainValidationEnabled = false; + } + + if (certExpiryValidationConfig != null && StringUtils.equals("true", certExpiryValidationConfig.getText())) { + isCertExpiryValidationEnabled = true; + } + + certificateVerifier = new CertificateVerificationManager(cacheSize, cacheDelay, + isFullCertChainValidationEnabled, isCertExpiryValidationEnabled); } ssl = createSSLContext(keyStoreEl, trustStoreEl, clientAuthEl, httpsProtocolsEl, preferredCiphersEl, - revocationVerifier, sslProtocol); + certificateVerifier, sslProtocol); return this; } @@ -341,8 +363,57 @@ public ServerConnFactoryBuilder parseMultiProfileSSL() throws AxisFault { OMElement preferredCiphersEl = profileEl.getFirstChildWithName(new QName(NhttpConstants.PREFERRED_CIPHERS)); final Parameter sslpParameter = transportIn.getParameter("SSLProtocol"); final String sslProtocol = sslpParameter != null ? sslpParameter.getValue().toString() : "TLS"; + + /* If multi SSL profiles are configured, checking whether the certificate revocation verifier is + configured and full certificate chain validation is enabled or not. */ + if (profileEl.getFirstChildWithName(new QName("CertificateRevocationVerifier")) != null) { + + Integer cacheSize; + Integer cacheDelay; + boolean isFullCertChainValidationEnabled = true; + boolean isCertExpiryValidationEnabled = false; + + OMElement revocationVerifierConfig = profileEl + .getFirstChildWithName(new QName("CertificateRevocationVerifier")); + OMElement revocationEnabled = revocationVerifierConfig + .getFirstChildWithName(new QName("Enable")); + + if (revocationEnabled != null && "true".equals(revocationEnabled.getText())) { + String cacheSizeString = revocationVerifierConfig + .getFirstChildWithName(new QName("CacheSize")).getText(); + String cacheDelayString = revocationVerifierConfig + .getFirstChildWithName(new QName("CacheDelay")).getText(); + + try { + cacheSize = new Integer(cacheSizeString); + cacheDelay = new Integer(cacheDelayString); + } catch (NumberFormatException e) { + throw new AxisFault("Cache size or Cache delay values are malformed", e); + } + + OMElement fullCertChainValidationConfig = revocationVerifierConfig + .getFirstChildWithName(new QName("FullChainValidation")); + + OMElement certExpiryValidationConfig = revocationVerifierConfig + .getFirstChildWithName(new QName("ExpiryValidation")); + + if (fullCertChainValidationConfig != null + && StringUtils.equals("false", fullCertChainValidationConfig.getText())) { + isFullCertChainValidationEnabled = false; + } + + if (certExpiryValidationConfig != null + && StringUtils.equals("true", certExpiryValidationConfig.getText())) { + isCertExpiryValidationEnabled = true; + } + + certificateVerifier = new CertificateVerificationManager(cacheSize, cacheDelay, + isFullCertChainValidationEnabled, isCertExpiryValidationEnabled); + } + } + SSLContextDetails ssl = createSSLContext(keyStoreEl, trustStoreEl, clientAuthEl, httpsProtocolsEl, - preferredCiphersEl, null, sslProtocol, secretResolver); + preferredCiphersEl, certificateVerifier, sslProtocol, secretResolver); if (sslByIPMap == null) { sslByIPMap = new HashMap(); } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/TrustStoreHolder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/TrustStoreHolder.java new file mode 100644 index 0000000000..697dd6eba6 --- /dev/null +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/TrustStoreHolder.java @@ -0,0 +1,62 @@ +/* + * 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.nhttp.config; + +import java.security.KeyStore; + +/** + * A data holder class to store the client trust store. + */ +public class TrustStoreHolder { + + private static volatile TrustStoreHolder instance; + private KeyStore clientTrustStore; + + private TrustStoreHolder() {} + + public static TrustStoreHolder getInstance() { + + if (instance == null) { + synchronized (TrustStoreHolder.class) { + if (instance == null) { + instance = new TrustStoreHolder(); + } + } + } + return instance; + } + + public KeyStore getClientTrustStore() { + + return clientTrustStore; + } + + public void setClientTrustStore(KeyStore clientTrustStore) { + + this.clientTrustStore = clientTrustStore; + } + + /** + * Reset the instance. + */ + public static void resetInstance() { + + instance = null; + } +} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpMultiSSLListener.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpMultiSSLListener.java index 208b364178..43fadef786 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpMultiSSLListener.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpMultiSSLListener.java @@ -5,10 +5,12 @@ import org.apache.axis2.description.ParameterInclude; import org.apache.axis2.description.TransportInDescription; import org.apache.http.HttpHost; -import org.apache.synapse.transport.http.conn.Scheme; -import org.apache.synapse.transport.nhttp.config.ServerConnFactoryBuilder; +import org.apache.synapse.transport.certificatevalidation.cache.CertCache; import org.apache.synapse.transport.dynamicconfigurations.ListenerProfileReloader; import org.apache.synapse.transport.dynamicconfigurations.SSLProfileLoader; +import org.apache.synapse.transport.http.conn.Scheme; +import org.apache.synapse.transport.nhttp.config.ServerConnFactoryBuilder; +import org.apache.synapse.transport.nhttp.config.TrustStoreHolder; public class PassThroughHttpMultiSSLListener extends PassThroughHttpListener implements SSLProfileLoader{ @@ -39,6 +41,8 @@ protected ServerConnFactoryBuilder initConnFactoryBuilder(final TransportInDescr * @throws AxisFault */ public void reloadConfig(ParameterInclude transport) throws AxisFault { + CertCache.resetCache(); + TrustStoreHolder.resetInstance(); reloadDynamicSSLConfig((TransportInDescription) transport); } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java index f14d690efd..5fe55383d0 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java @@ -20,10 +20,12 @@ import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.description.ParameterInclude; import org.apache.axis2.description.TransportOutDescription; -import org.apache.synapse.transport.http.conn.Scheme; -import org.apache.synapse.transport.nhttp.config.ClientConnFactoryBuilder; +import org.apache.synapse.transport.certificatevalidation.cache.CertCache; import org.apache.synapse.transport.dynamicconfigurations.SSLProfileLoader; import org.apache.synapse.transport.dynamicconfigurations.SenderProfileReloader; +import org.apache.synapse.transport.http.conn.Scheme; +import org.apache.synapse.transport.nhttp.config.ClientConnFactoryBuilder; +import org.apache.synapse.transport.nhttp.config.TrustStoreHolder; public class PassThroughHttpSSLSender extends PassThroughHttpSender implements SSLProfileLoader { @@ -53,6 +55,8 @@ protected ClientConnFactoryBuilder initConnFactoryBuilder( * @throws AxisFault */ public void reloadConfig(ParameterInclude transport) throws AxisFault { + CertCache.resetCache(); + TrustStoreHolder.resetInstance(); reloadDynamicSSLConfig((TransportOutDescription) transport); } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/core/ssl/SSLServerConnFactoryBuilder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/core/ssl/SSLServerConnFactoryBuilder.java index 282900b956..8ea5433d4f 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/core/ssl/SSLServerConnFactoryBuilder.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/core/ssl/SSLServerConnFactoryBuilder.java @@ -19,12 +19,11 @@ import org.apache.axiom.om.OMElement; import org.apache.axis2.AxisFault; -import org.apache.axis2.description.Parameter; import org.apache.axis2.description.TransportInDescription; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; -import org.apache.synapse.transport.certificatevalidation.RevocationVerificationManager; +import org.apache.synapse.transport.certificatevalidation.CertificateVerificationManager; import org.apache.synapse.transport.nhttp.config.ServerConnFactoryBuilder; import javax.xml.namespace.QName; @@ -46,7 +45,7 @@ public ServerConnFactoryBuilder parseSSL(OMElement keyStoreEl, OMElement trustSt AxisFault { final String cvEnable = cvp != null ? cvp.getAttribute(new QName("enable")).getAttributeValue() : null; - RevocationVerificationManager revocationVerifier = null; + CertificateVerificationManager revocationVerifier = null; if ("true".equalsIgnoreCase(cvEnable)) { Iterator iterator = cvp.getChildElements(); @@ -70,7 +69,7 @@ public ServerConnFactoryBuilder parseSSL(OMElement keyStoreEl, OMElement trustSt } catch (NumberFormatException e) { log.error("Please specify correct Integer numbers for CacheDelay and CacheSize"); } - revocationVerifier = new RevocationVerificationManager(cacheSize, cacheDelay); + revocationVerifier = new CertificateVerificationManager(cacheSize, cacheDelay); } ssl = createSSLContext(keyStoreEl, trustStoreEl, clientAuthEl, httpsProtocolsEl, preferredCiphers, revocationVerifier, diff --git a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/CRLVerifierTest.java b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/CRLVerifierTest.java index 38d7964b8a..7f6d8d1bfb 100644 --- a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/CRLVerifierTest.java +++ b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/CRLVerifierTest.java @@ -88,7 +88,7 @@ public void testRevokedCertificate() throws Exception { //Create a crl with fakeRevokedCertificate marked as revoked. X509CRL x509CRL = createCRL(fakeCACert, caKeyPair.getPrivate(), revokedSerialNumber); - CRLCache cache = CRLCache.getCache(); + CRLCache cache = CRLCache.getCache(5, 5); cache.init(5, 5); cache.setCacheValue(crlDistributionPointUrl, x509CRL); diff --git a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/OCSPVerifierTest.java b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/OCSPVerifierTest.java index 9c2299bb2b..91b607a6bc 100644 --- a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/OCSPVerifierTest.java +++ b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/OCSPVerifierTest.java @@ -107,7 +107,7 @@ public void testOCSPVerifier() throws Exception{ OCSPResp response = generateOCSPResponse(request, certificateHolder, caKeyPair.getPrivate(), caKeyPair.getPublic(), revokedID); SingleResp singleResp = ((BasicOCSPResp)response.getResponseObject()).getResponses()[0]; - OCSPCache cache = OCSPCache.getCache(); + OCSPCache cache = OCSPCache.getCache(5, 5); cache.init(5,5); cache.setCacheValue(revokedSerialNumber,singleResp, request, null); diff --git a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationTest.java b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationTest.java index 7f282dd86c..09aa70b9f9 100644 --- a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationTest.java +++ b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/certificatevalidation/RevocationVerificationTest.java @@ -19,13 +19,21 @@ package org.apache.synapse.transport.certificatevalidation; import junit.framework.TestCase; +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.mockito.Mockito; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.Security; +import java.security.SignatureException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class RevocationVerificationTest extends TestCase { @@ -114,9 +122,32 @@ public void testOCSPPathValidationWithFakeCerts() throws Exception { assertNotNull(throwable); } + public void testGetPeerCertificate() throws CertificateVerificationException { + + X509Certificate mockCert = Mockito.mock(X509Certificate.class); + X509Certificate[] certArray = {mockCert}; + CertificateVerificationManager verificationManager = new CertificateVerificationManager(5, 5); + Object peerCert = verificationManager.getPeerCertificate(certArray); + assertNotNull(peerCert); + } + + public void testGetVerifiedIssuerCertOfPeerCertCached() + throws CertificateVerificationException, CertificateException, NoSuchAlgorithmException, + SignatureException, InvalidKeyException, NoSuchProviderException { + + CertCache mockCertCache = Mockito.mock(CertCache.class); + X509Certificate peerCert = Mockito.mock(X509Certificate.class); + X509Certificate cachedIssuerCert = Mockito.mock(X509Certificate.class); + Mockito.when(peerCert.getSerialNumber()).thenReturn(BigInteger.valueOf(1L)); + Mockito.when(mockCertCache.getCacheValue(Mockito.anyString())).thenReturn(cachedIssuerCert); + CertificateVerificationManager mockVerificationManager = new CertificateVerificationManager(5, 5); + Mockito.doNothing().when(peerCert).verify(Mockito.any()); + assertNotNull(mockVerificationManager.getVerifiedIssuerCertOfPeerCert(peerCert, mockCertCache)); + } + private void crlPathValidation(X509Certificate[] certChain) throws Exception { - CRLCache crlCache = CRLCache.getCache(); + CRLCache crlCache = CRLCache.getCache(5, 5); crlCache.init(5, 5); RevocationVerifier verifier = new CRLVerifier(crlCache); CertificatePathValidator pathValidator = new CertificatePathValidator(certChain, verifier); @@ -125,7 +156,7 @@ private void crlPathValidation(X509Certificate[] certChain) throws Exception { private void ocspPathValidation(X509Certificate[] certChain) throws Exception { - OCSPCache ocspCache = OCSPCache.getCache(); + OCSPCache ocspCache = OCSPCache.getCache(5, 5); ocspCache.init(5, 5); RevocationVerifier verifier = new OCSPVerifier(ocspCache); CertificatePathValidator pathValidator = new CertificatePathValidator(certChain, verifier);