From 7278240b194461642a529b0d621258eb67e691fc Mon Sep 17 00:00:00 2001 From: David Hook Date: Sat, 6 Aug 2022 16:38:49 +1000 Subject: [PATCH] added LDAP support --- .../bouncycastle/pkix/jcajce/CrlCache.java | 193 ++++++++++++++++++ .../pkix/jcajce/X509RevocationChecker.java | 113 +++++----- 2 files changed, 257 insertions(+), 49 deletions(-) create mode 100644 pkix/src/main/java/org/bouncycastle/pkix/jcajce/CrlCache.java diff --git a/pkix/src/main/java/org/bouncycastle/pkix/jcajce/CrlCache.java b/pkix/src/main/java/org/bouncycastle/pkix/jcajce/CrlCache.java new file mode 100644 index 0000000000..eb910a70ad --- /dev/null +++ b/pkix/src/main/java/org/bouncycastle/pkix/jcajce/CrlCache.java @@ -0,0 +1,193 @@ +package org.bouncycastle.pkix.jcajce; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.HttpURLConnection; +import java.net.URI; +import java.security.cert.CRL; +import java.security.cert.CRLException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.bouncycastle.jcajce.PKIXCRLStore; +import org.bouncycastle.util.CollectionStore; +import org.bouncycastle.util.Iterable; +import org.bouncycastle.util.Selector; +import org.bouncycastle.util.Store; + +class CrlCache +{ + private static final int DEFAULT_TIMEOUT = 15000; + + private static Map> cache = + Collections.synchronizedMap(new WeakHashMap>()); + + static synchronized PKIXCRLStore getCrl(CertificateFactory certFact, Date validDate, URI distributionPoint) + throws IOException, CRLException + { + PKIXCRLStore crlStore = null; + + WeakReference markerRef = (WeakReference)cache.get(distributionPoint); + if (markerRef != null) + { + crlStore = (PKIXCRLStore)markerRef.get(); + } + + if (crlStore != null) + { + boolean isExpired = false; + for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();) + { + X509CRL crl = (X509CRL)it.next(); + + Date nextUpdate = crl.getNextUpdate(); + if (nextUpdate != null && nextUpdate.before(validDate)) + { + isExpired = true; + break; + } + } + + if (!isExpired) + { + return crlStore; + } + } + + Collection crls; + + if (distributionPoint.getScheme().equals("ldap")) + { + crls = getCrlsFromLDAP(certFact, distributionPoint); + } + else + { + // http, https, ftp + crls = getCrls(certFact, distributionPoint); + } + + LocalCRLStore localCRLStore = new LocalCRLStore(new CollectionStore(crls)); + + cache.put(distributionPoint, new WeakReference(localCRLStore)); + + return localCRLStore; + } + + private static Collection getCrlsFromLDAP(CertificateFactory certFact, URI distributionPoint) + throws IOException, CRLException + { + Map env = new Hashtable(); + + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, distributionPoint.toString()); + + byte[] val = null; + try + { + DirContext ctx = new InitialDirContext((Hashtable)env); + Attributes avals = ctx.getAttributes(""); + Attribute aval = avals.get("certificateRevocationList;binary"); + val = (byte[])aval.get(); + } + catch (NamingException e) + { + throw new CRLException("issue connecting to: " + distributionPoint.toString(), e); + } + + if ((val == null) || (val.length == 0)) + { + throw new CRLException("no CRL returned from: " + distributionPoint); + } + else + { + return certFact.generateCRLs(new ByteArrayInputStream(val)); + } + } + + private static Collection getCrls(CertificateFactory certFact, URI distributionPoint) + throws IOException, CRLException + { + HttpURLConnection crlCon = (HttpURLConnection)distributionPoint.toURL().openConnection(); + crlCon.setConnectTimeout(DEFAULT_TIMEOUT); + crlCon.setReadTimeout(DEFAULT_TIMEOUT); + + InputStream crlIn = crlCon.getInputStream(); + + Collection crls = certFact.generateCRLs(crlIn); + + crlIn.close(); + + return crls; + } + + private static class LocalCRLStore + implements PKIXCRLStore, Iterable + { + private Collection _local; + + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + public LocalCRLStore( + Store collection) + { + _local = new ArrayList(collection.getMatches(null)); + } + + /** + * Return the matches in the collection for the passed in selector. + * + * @param selector the selector to match against. + * @return a possibly empty collection of matching objects. + */ + public Collection getMatches(Selector selector) + { + if (selector == null) + { + return new ArrayList(_local); + } + else + { + List col = new ArrayList(); + Iterator iter = _local.iterator(); + + while (iter.hasNext()) + { + CRL obj = iter.next(); + + if (selector.match(obj)) + { + col.add(obj); + } + } + + return col; + } + } + + public Iterator iterator() + { + return getMatches(null).iterator(); + } + } +} diff --git a/pkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java b/pkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java index 61bf786fd4..3cb1fe79b2 100644 --- a/pkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java +++ b/pkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java @@ -1,9 +1,6 @@ package org.bouncycastle.pkix.jcajce; -import java.io.BufferedInputStream; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.net.URL; +import java.net.URI; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -34,13 +31,13 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; -import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1String; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.CRLDistPoint; import org.bouncycastle.asn1.x509.DistributionPoint; @@ -49,6 +46,7 @@ import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.jcajce.PKIXCRLStore; +import org.bouncycastle.jcajce.PKIXCRLStoreSelector; import org.bouncycastle.jcajce.PKIXExtendedParameters; import org.bouncycastle.jcajce.util.DefaultJcaJceHelper; import org.bouncycastle.jcajce.util.JcaJceHelper; @@ -102,6 +100,7 @@ public static class Builder private boolean canSoftFail; private long failLogMaxTime; private long failHardMaxTime; + private Date validityDate = new Date(); /** * Base constructor. @@ -170,6 +169,19 @@ public Builder addCrls(Store crls) return this; } + /** + * Set the current date for checking if not today. + * + * @param validityDate date we are validating for. + * @return the current builder instance. + */ + public Builder setDate(Date validityDate) + { + this.validityDate = new Date(validityDate.getTime()); + + return this; + } + /** * @param isTrue true if only end-entities should be checked, false otherwise. * @return the current builder instance. @@ -274,8 +286,6 @@ public X509RevocationChecker build() } private static Logger LOG = Logger.getLogger(X509RevocationChecker.class.getName()); - private static final Map> crlCache = Collections.synchronizedMap( - new WeakHashMap>()); private final Map failures = new HashMap(); private final Set trustAnchors; @@ -287,6 +297,7 @@ public X509RevocationChecker build() private final boolean canSoftFail; private final long failLogMaxTime; private final long failHardMaxTime; + private final Date validationDate; private Date currentDate; private X500Principal workingIssuerName; @@ -303,6 +314,7 @@ private X509RevocationChecker(Builder bldr) this.canSoftFail = bldr.canSoftFail; this.failLogMaxTime = bldr.failLogMaxTime; this.failHardMaxTime = bldr.failHardMaxTime; + this.validationDate = bldr.validityDate; if (bldr.provider != null) { @@ -387,7 +399,7 @@ public void check(Certificate certificate, Collection collection) { PKIXParameters pkixParams = new PKIXParameters(trustAnchors); pkixParams.setRevocationEnabled(false); - pkixParams.setDate(currentDate); + pkixParams.setDate(validationDate); for (int i = 0; i != crlCertStores.size(); i++) { @@ -436,7 +448,7 @@ public void check(Certificate certificate, Collection collection) PKIXExtendedParameters pkixParams = pkixBuilder.build(); - Date validityDate = RevocationUtilities.getValidityDate(pkixParams, currentDate); + Date validityDate = RevocationUtilities.getValidityDate(pkixParams, validationDate); try { @@ -453,10 +465,10 @@ public void check(Certificate certificate, Collection collection) throw e; } - CRL crl; + Set crls; try { - crl = downloadCRLs(cert.getIssuerX500Principal(), currentDate, + crls = downloadCRLs(cert.getIssuerX500Principal(), validityDate, RevocationUtilities.getExtensionValue(cert, Extension.cRLDistributionPoints), helper); } catch(AnnotatedException e1) @@ -464,15 +476,15 @@ public void check(Certificate certificate, Collection collection) throw new CertPathValidatorException(e1.getMessage(), e1.getCause()); } - if (crl != null) + if (!crls.isEmpty()) { try { - pkixBuilder.addCRLStore(new LocalCRLStore(new CollectionStore(Collections.singleton(crl)))); + pkixBuilder.addCRLStore(new LocalCRLStore(new CollectionStore(crls))); pkixParams = pkixBuilder.build(); - validityDate = RevocationUtilities.getValidityDate(pkixParams, currentDate); + validityDate = RevocationUtilities.getValidityDate(pkixParams, validationDate); checkCRLs(pkixParams, currentDate, validityDate, cert, signingCert, workingPublicKey, new ArrayList(), helper); @@ -562,16 +574,40 @@ public Object clone() }); } - private CRL downloadCRLs(X500Principal issuer, Date currentDate, ASN1Primitive crlDpPrimitive, JcaJceHelper helper) + private Set downloadCRLs(X500Principal issuer, Date currentDate, ASN1Primitive crlDpPrimitive, JcaJceHelper helper) { CRLDistPoint crlDp = CRLDistPoint.getInstance(crlDpPrimitive); DistributionPoint[] points = crlDp.getDistributionPoints(); + CertificateFactory certFact; + try + { + certFact = helper.createCertificateFactory("X.509"); + } + catch (Exception e) + { + if (LOG.isLoggable(Level.FINE)) + { + LOG.log(Level.FINE, "could not create certFact: " + e.getMessage(), e); + } + else + { + LOG.log(Level.INFO, "could not create certFact: " + e.getMessage()); + } + return null; + } + + X509CRLSelector crlSelector = new X509CRLSelector(); + crlSelector.addIssuer(issuer); + + PKIXCRLStoreSelector crlselect = new PKIXCRLStoreSelector.Builder(crlSelector).build(); + Set crls = new HashSet(); + for (int i = 0; i != points.length; i++) { DistributionPoint dp = points[i]; - DistributionPointName dpn = dp.getDistributionPoint(); + if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) { GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames(); @@ -581,42 +617,21 @@ private CRL downloadCRLs(X500Principal issuer, Date currentDate, ASN1Primitive c GeneralName name = names[n]; if (name.getTagNo() == GeneralName.uniformResourceIdentifier) { - X509CRL crl; - - WeakReference crlRef = crlCache.get(name); - if (crlRef != null) - { - crl = crlRef.get(); - if (crl != null - && !currentDate.before(crl.getThisUpdate()) - && !currentDate.after(crl.getNextUpdate())) - { - return crl; - } - crlCache.remove(name); // delete expired/out-of-range entry - } - - URL url = null; + URI url = null; try { - url = new URL(name.getName().toString()); - - CertificateFactory certFact = helper.createCertificateFactory("X.509"); - - InputStream urlStream = url.openStream(); - - crl = (X509CRL)certFact.generateCRL(new BufferedInputStream(urlStream)); + url = new URI(((ASN1String)name.getName()).getString()); - urlStream.close(); + PKIXCRLStore store = CrlCache.getCrl(certFact, validationDate, url); - LOG.log(Level.INFO, "downloaded CRL from CrlDP " + url + " for issuer \"" + issuer + "\""); - - crlCache.put(name, new WeakReference(crl)); - - return crl; + if (store != null) + { + crls.addAll(PKIXCRLUtil.findCRLs(crlselect, currentDate, Collections.EMPTY_LIST, + Collections.singletonList(store))); + } } catch (Exception e) - { + { e.printStackTrace(); if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "CrlDP " + url + " ignored: " + e.getMessage(), e); @@ -631,7 +646,7 @@ private CRL downloadCRLs(X500Principal issuer, Date currentDate, ASN1Primitive c } } - return null; + return crls; } protected static final String[] crlReasons = new String[]{ @@ -694,7 +709,7 @@ static List getAdditionalStoresFromCRLDistributionPoint(CRLDistPoi * * @param pkixParams PKIX parameters. * @param cert Certificate to check if it is revoked. - * @param validDate The date when the certificate revocation status should be + * @param validityDate The date when the certificate revocation status should be * checked. * @param sign The issuer certificate of the certificate cert. * @param workingPublicKey The public key of the issuer certificate sign. @@ -770,7 +785,7 @@ protected void checkCRLs( validCrlFound = true; } catch (AnnotatedException e) - { + { e.printStackTrace(); lastException = e; } }