diff --git a/src/main/java/cz/tomasdvorak/eet/client/security/MerlinWithCRLDistributionPointsExtension.java b/src/main/java/cz/tomasdvorak/eet/client/security/MerlinWithCRLDistributionPointsExtension.java index 65373287..66eaa4e8 100644 --- a/src/main/java/cz/tomasdvorak/eet/client/security/MerlinWithCRLDistributionPointsExtension.java +++ b/src/main/java/cz/tomasdvorak/eet/client/security/MerlinWithCRLDistributionPointsExtension.java @@ -1,58 +1,30 @@ package cz.tomasdvorak.eet.client.security; -import cz.tomasdvorak.eet.client.exceptions.RevocationListException; -import cz.tomasdvorak.eet.client.security.crl.InMemoryCRLStore; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.wss4j.common.crypto.Merlin; -import org.apache.wss4j.common.ext.WSSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; -import java.util.regex.Pattern; +import java.security.Security; -/** - * On the fly parsing, download and set of CRLs list, based on CRL Distribution Points extension of the certificate. - * - * see also: https://security.stackexchange.com/questions/72570/is-publishing-crls-over-http-a-potential-vulnerability - */ class MerlinWithCRLDistributionPointsExtension extends Merlin { - private static final Logger logger = org.apache.logging.log4j.LogManager.getLogger(MerlinWithCRLDistributionPointsExtension.class); + private static final Logger logger = LogManager.getLogger(MerlinWithCRLDistributionPointsExtension.class); + public MerlinWithCRLDistributionPointsExtension() { + configureSystemProperties(); + } - @Override - public void verifyTrust(final X509Certificate[] certs, final boolean enableRevocation, final Collection subjectCertConstraints) throws WSSecurityException { - if (enableRevocation) { - final List x509Certificates = new ArrayList(Arrays.asList(certs)); - try { - try { - final KeyStore keyStore = this.getTrustStore(); - final Enumeration aliases = keyStore.aliases(); - while(aliases.hasMoreElements()) { - final String alias = aliases.nextElement(); - final Certificate cert = keyStore.getCertificate(alias); - if(cert instanceof X509Certificate) { - x509Certificates.add((X509Certificate) cert); - } - } - } catch (final KeyStoreException e) { - e.printStackTrace(); - } + private void configureSystemProperties() { + final boolean crlDownloadEnabled = Boolean.getBoolean("com.sun.security.enableCRLDP"); + final boolean checkRevocationEnabled = Boolean.getBoolean("com.sun.net.ssl.checkRevocation"); + final String value = Security.getProperty("com.sun.security.onlyCheckRevocationOfEECert"); + final boolean onlyCheckRevocationOfEECert = (value != null) && value.equalsIgnoreCase("true"); - this.setCRLCertStore(InMemoryCRLStore.INSTANCE.getCRLStore(x509Certificates.toArray(new X509Certificate[x509Certificates.size()]))); - } catch (final RevocationListException e) { - throw new WSSecurityException(WSSecurityException.ErrorCode.SECURITY_ERROR, e); - } - } else { - logger.warn("Certificate revocation lists checking is disabled."); + if (!crlDownloadEnabled || !checkRevocationEnabled || !onlyCheckRevocationOfEECert) { + logger.info("System properties will be configured to enable certificate revocation checks."); + System.setProperty("com.sun.security.enableCRLDP", "true"); + System.setProperty("com.sun.net.ssl.checkRevocation", "true"); + Security.setProperty("com.sun.security.onlyCheckRevocationOfEECert", "true"); // verify only revocation of the last cert in path (the EET cert) } - super.verifyTrust(certs, enableRevocation, subjectCertConstraints); } } diff --git a/src/main/java/cz/tomasdvorak/eet/client/security/crl/CRLUtils.java b/src/main/java/cz/tomasdvorak/eet/client/security/crl/CRLUtils.java deleted file mode 100644 index 0b906c81..00000000 --- a/src/main/java/cz/tomasdvorak/eet/client/security/crl/CRLUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -package cz.tomasdvorak.eet.client.security.crl; - -import cz.tomasdvorak.eet.client.exceptions.RevocationListException; -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.ASN1OctetString; -import org.bouncycastle.asn1.DERIA5String; -import org.bouncycastle.asn1.x509.CRLDistPoint; -import org.bouncycastle.asn1.x509.DistributionPoint; -import org.bouncycastle.asn1.x509.DistributionPointName; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.GeneralName; -import org.bouncycastle.asn1.x509.GeneralNames; - -import java.net.URI; -import java.net.URISyntaxException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Read all the CRLs from a X509Certificate instance. Based on bouncycastle rather than deprecated sun.security classes. - */ -final class CRLUtils { - - private CRLUtils() {} // utility class, no instance - - static List getCRLs(final X509Certificate cert) throws RevocationListException { - final List distributionPoints = getDistributionPoints(cert); - final List result = new ArrayList(); - for(final DistributionPoint point : distributionPoints) { - result.addAll(parseURI(point)); - } - return result; - } - - private static List parseURI(final DistributionPoint dp) throws RevocationListException { - final List result = new ArrayList(); - final DistributionPointName dpn = dp.getDistributionPoint(); - if(isValidDistributionPoint(dpn)) { - final List generalNames = getGeneralNames(dpn); - for(final GeneralName name : generalNames) { - if(name.getTagNo() == GeneralName.uniformResourceIdentifier) { - final ASN1Encodable asn1Encodable = name.getName(); - final String uri = DERIA5String.getInstance(asn1Encodable).getString(); - result.add(toURI(uri)); - } - } - } - return result; - } - - private static boolean isValidDistributionPoint(final DistributionPointName dpn) { - return dpn != null && dpn.getType() == DistributionPointName.FULL_NAME; - } - - private static List getDistributionPoints(final X509Certificate cert) { - final List result = new ArrayList(); - final byte[] extensionValue = cert.getExtensionValue(Extension.cRLDistributionPoints.getId()); - - if(extensionValue != null) { - final ASN1OctetString instance = ASN1OctetString.getInstance(extensionValue); - final byte[] octets = instance.getOctets(); - final CRLDistPoint distPoint = CRLDistPoint.getInstance(octets); - final DistributionPoint[] distributionPoints = distPoint.getDistributionPoints(); - Collections.addAll(result, distributionPoints); - } - return result; - } - - private static List getGeneralNames(final DistributionPointName dpn) { - final ASN1Encodable name = dpn.getName(); - return Arrays.asList(GeneralNames.getInstance(name).getNames()); - } - - private static URI toURI(final String uri) throws RevocationListException { - try { - return new URI(uri); - } catch (final URISyntaxException e) { - throw new RevocationListException("Failed to parse CRL URI", e); - } - } -} diff --git a/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java b/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java deleted file mode 100644 index 42f1796b..00000000 --- a/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java +++ /dev/null @@ -1,129 +0,0 @@ -package cz.tomasdvorak.eet.client.security.crl; - -import cz.tomasdvorak.eet.client.exceptions.RevocationListException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CRLException; -import java.security.cert.CertStore; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.CollectionCertStoreParameters; -import java.security.cert.X509CRL; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.concurrent.*; - -/** - * CertStore created on the fly, containing all the CRLs obtained from an {@link X509Certificate certificate instance}. - * The CRLs are downloaded and persisted in memory, respecting the nextUpdate time. This makes validation of second and every other - * response signed with the same cert much faster, without the need of re-downloading CRLs again. - * - * CLRs are downloaded in parallel and the code is thread safe, synchronized. - */ -public class InMemoryCRLStore { - - private static final Logger logger = LogManager.getLogger(InMemoryCRLStore.class); - - public static final InMemoryCRLStore INSTANCE = new InMemoryCRLStore(TimeUnit.SECONDS.toMillis(2)); - - private final long timeoutInMillis; - private final Map crlCache; - - protected InMemoryCRLStore(long timeoutInMillis) { - this.timeoutInMillis = timeoutInMillis; - this.crlCache = new ConcurrentHashMap(); - } - - public synchronized CertStore getCRLStore(final X509Certificate... certificates) throws RevocationListException { - final ExecutorService executorService = Executors.newCachedThreadPool(); - try { - final List> x509CRLs = new ArrayList>(); - for (final X509Certificate cert : certificates) { - x509CRLs.addAll(getCrls(cert)); - } - final List> futures = executorService.invokeAll(x509CRLs, timeoutInMillis, TimeUnit.MILLISECONDS); - final List crls = new ArrayList(); - for (Future future : futures) { - crls.add(future.get()); - } - return CertStore.getInstance("Collection", new CollectionCertStoreParameters(crls)); - } catch (final InvalidAlgorithmParameterException e) { - throw new RevocationListException(e); - } catch (final NoSuchAlgorithmException e) { - throw new RevocationListException(e); - } catch (InterruptedException e) { - throw new RevocationListException(e); - } catch (ExecutionException e) { - throw new RevocationListException(e); - } finally { - executorService.shutdownNow(); - } - } - - private List> getCrls(final X509Certificate cert) throws RevocationListException { - final List uris = CRLUtils.getCRLs(cert); - final List> callables = new ArrayList>(); - for (final URI uri : uris) { - callables.add(new Callable() { - @Override - public X509CRL call() throws RevocationListException { - return getCRL(uri); - } - }); - } - return callables; - } - - private X509CRL getCRL(final URI address) throws RevocationListException { - if(crlCache.containsKey(address)) { - final X509CRL x509CRL = crlCache.get(address); - final Date nextUpdate = x509CRL.getNextUpdate(); - if(new Date().before(nextUpdate)) { - logger.debug("CRL from URI " + address.toString() + " is up-to-date, using cached variant. Next update: " + nextUpdate + "."); - return x509CRL; - } else { - logger.debug("CRL from URI " + address.toString() + " is stale, updating now"); - } - } - final X509CRL x509CRL = downloadCRL(address); - logger.info("CRL loaded from URI " + address.toString() + ", storing in cache. Next update: " + x509CRL.getNextUpdate()); - crlCache.put(address, x509CRL); - return x509CRL; - } - - private X509CRL downloadCRL(final URI address) throws RevocationListException { - try { - final URL url = new URL(address.toString()); - final CertificateFactory cf = CertificateFactory.getInstance("X.509"); - InputStream inStream = null; - try { - inStream = url.openStream(); - return (X509CRL) cf.generateCRL(inStream); - } finally { - if(inStream != null) { - inStream.close(); - } - } - } catch (final CertificateException e) { - throw createCrlException(e); - } catch (final CRLException e) { - throw createCrlException(e); - } catch (final IOException e) { - throw createCrlException(e); - } - } - - private RevocationListException createCrlException(final Exception e) { - return new RevocationListException("Failed to obtain certificate revocation list to be able to validate EET response", e); - } -} diff --git a/src/test/java/cz/tomasdvorak/eet/client/security/crl/CRLUtilsTest.java b/src/test/java/cz/tomasdvorak/eet/client/security/crl/CRLUtilsTest.java deleted file mode 100644 index 9d3130d0..00000000 --- a/src/test/java/cz/tomasdvorak/eet/client/security/crl/CRLUtilsTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package cz.tomasdvorak.eet.client.security.crl; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.InputStream; -import java.net.URI; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class CRLUtilsTest { - - private X509Certificate certificate; - - @Before - public void setUp() throws Exception { - final InputStream is = getClass().getResourceAsStream("/keys/crls-demo-cert.pem"); - final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - certificate = (X509Certificate) certificateFactory.generateCertificate(is); - } - - @Test - public void getCRLs() throws Exception { - final List crLs = CRLUtils.getCRLs(certificate); - final List actual = new ArrayList(); - for(final URI uri : crLs) { - actual.add(uri.toString()); - } - - final List expected = Arrays.asList( - "http://qcrldp1.ica.cz/qica09.crl", - "http://qcrldp2.ica.cz/qica09.crl", - "http://qcrldp3.ica.cz/qica09.crl"); - - Assert.assertEquals("Failed to read all CRLs from a certificate", expected, actual); - } - -} \ No newline at end of file