| /* |
| * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.security.provider.certpath.ldap; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import java.net.URI; |
| import java.util.*; |
| import javax.naming.Context; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.NamingException; |
| import javax.naming.NameNotFoundException; |
| import javax.naming.directory.Attribute; |
| import javax.naming.directory.Attributes; |
| import javax.naming.directory.BasicAttributes; |
| import javax.naming.directory.DirContext; |
| import javax.naming.directory.InitialDirContext; |
| |
| import java.security.*; |
| import java.security.cert.Certificate; |
| import java.security.cert.*; |
| import javax.security.auth.x500.X500Principal; |
| |
| import sun.misc.HexDumpEncoder; |
| import sun.security.provider.certpath.X509CertificatePair; |
| import sun.security.util.Cache; |
| import sun.security.util.Debug; |
| import sun.security.x509.X500Name; |
| import sun.security.action.GetPropertyAction; |
| |
| /** |
| * A <code>CertStore</code> that retrieves <code>Certificates</code> and |
| * <code>CRL</code>s from an LDAP directory, using the PKIX LDAP V2 Schema |
| * (RFC 2587): |
| * <a href="http://www.ietf.org/rfc/rfc2587.txt"> |
| * http://www.ietf.org/rfc/rfc2587.txt</a>. |
| * <p> |
| * Before calling the {@link #engineGetCertificates engineGetCertificates} or |
| * {@link #engineGetCRLs engineGetCRLs} methods, the |
| * {@link #LDAPCertStore(CertStoreParameters) |
| * LDAPCertStore(CertStoreParameters)} constructor is called to create the |
| * <code>CertStore</code> and establish the DNS name and port of the LDAP |
| * server from which <code>Certificate</code>s and <code>CRL</code>s will be |
| * retrieved. |
| * <p> |
| * <b>Concurrent Access</b> |
| * <p> |
| * As described in the javadoc for <code>CertStoreSpi</code>, the |
| * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods |
| * must be thread-safe. That is, multiple threads may concurrently |
| * invoke these methods on a single <code>LDAPCertStore</code> object |
| * (or more than one) with no ill effects. This allows a |
| * <code>CertPathBuilder</code> to search for a CRL while simultaneously |
| * searching for further certificates, for instance. |
| * <p> |
| * This is achieved by adding the <code>synchronized</code> keyword to the |
| * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods. |
| * <p> |
| * This classes uses caching and requests multiple attributes at once to |
| * minimize LDAP round trips. The cache is associated with the CertStore |
| * instance. It uses soft references to hold the values to minimize impact |
| * on footprint and currently has a maximum size of 750 attributes and a |
| * 30 second default lifetime. |
| * <p> |
| * We always request CA certificates, cross certificate pairs, and ARLs in |
| * a single LDAP request when any one of them is needed. The reason is that |
| * we typically need all of them anyway and requesting them in one go can |
| * reduce the number of requests to a third. Even if we don't need them, |
| * these attributes are typically small enough not to cause a noticeable |
| * overhead. In addition, when the prefetchCRLs flag is true, we also request |
| * the full CRLs. It is currently false initially but set to true once any |
| * request for an ARL to the server returns an null value. The reason is |
| * that CRLs could be rather large but are rarely used. This implementation |
| * should improve performance in most cases. |
| * |
| * @see java.security.cert.CertStore |
| * |
| * @since 1.4 |
| * @author Steve Hanna |
| * @author Andreas Sterbenz |
| */ |
| public final class LDAPCertStore extends CertStoreSpi { |
| |
| private static final Debug debug = Debug.getInstance("certpath"); |
| |
| private final static boolean DEBUG = false; |
| |
| /** |
| * LDAP attribute identifiers. |
| */ |
| private static final String USER_CERT = "userCertificate;binary"; |
| private static final String CA_CERT = "cACertificate;binary"; |
| private static final String CROSS_CERT = "crossCertificatePair;binary"; |
| private static final String CRL = "certificateRevocationList;binary"; |
| private static final String ARL = "authorityRevocationList;binary"; |
| private static final String DELTA_CRL = "deltaRevocationList;binary"; |
| |
| // Constants for various empty values |
| private final static String[] STRING0 = new String[0]; |
| |
| private final static byte[][] BB0 = new byte[0][]; |
| |
| private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes(); |
| |
| // cache related constants |
| private final static int DEFAULT_CACHE_SIZE = 750; |
| private final static int DEFAULT_CACHE_LIFETIME = 30; |
| |
| private final static int LIFETIME; |
| |
| private final static String PROP_LIFETIME = |
| "sun.security.certpath.ldap.cache.lifetime"; |
| |
| static { |
| String s = AccessController.doPrivileged( |
| new GetPropertyAction(PROP_LIFETIME)); |
| if (s != null) { |
| LIFETIME = Integer.parseInt(s); // throws NumberFormatException |
| } else { |
| LIFETIME = DEFAULT_CACHE_LIFETIME; |
| } |
| } |
| |
| /** |
| * The CertificateFactory used to decode certificates from |
| * their binary stored form. |
| */ |
| private CertificateFactory cf; |
| /** |
| * The JNDI directory context. |
| */ |
| private DirContext ctx; |
| |
| /** |
| * Flag indicating whether we should prefetch CRLs. |
| */ |
| private boolean prefetchCRLs = false; |
| |
| private final Cache<String, byte[][]> valueCache; |
| |
| private int cacheHits = 0; |
| private int cacheMisses = 0; |
| private int requests = 0; |
| |
| /** |
| * Creates a <code>CertStore</code> with the specified parameters. |
| * For this class, the parameters object must be an instance of |
| * <code>LDAPCertStoreParameters</code>. |
| * |
| * @param params the algorithm parameters |
| * @exception InvalidAlgorithmParameterException if params is not an |
| * instance of <code>LDAPCertStoreParameters</code> |
| */ |
| public LDAPCertStore(CertStoreParameters params) |
| throws InvalidAlgorithmParameterException { |
| super(params); |
| if (!(params instanceof LDAPCertStoreParameters)) |
| throw new InvalidAlgorithmParameterException( |
| "parameters must be LDAPCertStoreParameters"); |
| |
| LDAPCertStoreParameters lparams = (LDAPCertStoreParameters) params; |
| |
| // Create InitialDirContext needed to communicate with the server |
| createInitialDirContext(lparams.getServerName(), lparams.getPort()); |
| |
| // Create CertificateFactory for use later on |
| try { |
| cf = CertificateFactory.getInstance("X.509"); |
| } catch (CertificateException e) { |
| throw new InvalidAlgorithmParameterException( |
| "unable to create CertificateFactory for X.509"); |
| } |
| if (LIFETIME == 0) { |
| valueCache = Cache.newNullCache(); |
| } else if (LIFETIME < 0) { |
| valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE); |
| } else { |
| valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME); |
| } |
| } |
| |
| /** |
| * Returns an LDAP CertStore. This method consults a cache of |
| * CertStores (shared per JVM) using the LDAP server/port as a key. |
| */ |
| private static final Cache<LDAPCertStoreParameters, CertStore> |
| certStoreCache = Cache.newSoftMemoryCache(185); |
| static synchronized CertStore getInstance(LDAPCertStoreParameters params) |
| throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { |
| CertStore lcs = certStoreCache.get(params); |
| if (lcs == null) { |
| lcs = CertStore.getInstance("LDAP", params); |
| certStoreCache.put(params, lcs); |
| } else { |
| if (debug != null) { |
| debug.println("LDAPCertStore.getInstance: cache hit"); |
| } |
| } |
| return lcs; |
| } |
| |
| /** |
| * Create InitialDirContext. |
| * |
| * @param server Server DNS name hosting LDAP service |
| * @param port Port at which server listens for requests |
| * @throws InvalidAlgorithmParameterException if creation fails |
| */ |
| private void createInitialDirContext(String server, int port) |
| throws InvalidAlgorithmParameterException { |
| String url = "ldap://" + server + ":" + port; |
| Hashtable<String,Object> env = new Hashtable<>(); |
| env.put(Context.INITIAL_CONTEXT_FACTORY, |
| "com.sun.jndi.ldap.LdapCtxFactory"); |
| env.put(Context.PROVIDER_URL, url); |
| try { |
| ctx = new InitialDirContext(env); |
| /* |
| * By default, follow referrals unless application has |
| * overridden property in an application resource file. |
| */ |
| Hashtable<?,?> currentEnv = ctx.getEnvironment(); |
| if (currentEnv.get(Context.REFERRAL) == null) { |
| ctx.addToEnvironment(Context.REFERRAL, "follow"); |
| } |
| } catch (NamingException e) { |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineInit about to throw " |
| + "InvalidAlgorithmParameterException"); |
| e.printStackTrace(); |
| } |
| Exception ee = new InvalidAlgorithmParameterException |
| ("unable to create InitialDirContext using supplied parameters"); |
| ee.initCause(e); |
| throw (InvalidAlgorithmParameterException)ee; |
| } |
| } |
| |
| /** |
| * Private class encapsulating the actual LDAP operations and cache |
| * handling. Use: |
| * |
| * LDAPRequest request = new LDAPRequest(dn); |
| * request.addRequestedAttribute(CROSS_CERT); |
| * request.addRequestedAttribute(CA_CERT); |
| * byte[][] crossValues = request.getValues(CROSS_CERT); |
| * byte[][] caValues = request.getValues(CA_CERT); |
| * |
| * At most one LDAP request is sent for each instance created. If all |
| * getValues() calls can be satisfied from the cache, no request |
| * is sent at all. If a request is sent, all requested attributes |
| * are always added to the cache irrespective of whether the getValues() |
| * method is called. |
| */ |
| private class LDAPRequest { |
| |
| private final String name; |
| private Map<String, byte[][]> valueMap; |
| private final List<String> requestedAttributes; |
| |
| LDAPRequest(String name) { |
| this.name = name; |
| requestedAttributes = new ArrayList<>(5); |
| } |
| |
| String getName() { |
| return name; |
| } |
| |
| void addRequestedAttribute(String attrId) { |
| if (valueMap != null) { |
| throw new IllegalStateException("Request already sent"); |
| } |
| requestedAttributes.add(attrId); |
| } |
| |
| /** |
| * Gets one or more binary values from an attribute. |
| * |
| * @param name the location holding the attribute |
| * @param attrId the attribute identifier |
| * @return an array of binary values (byte arrays) |
| * @throws NamingException if a naming exception occurs |
| */ |
| byte[][] getValues(String attrId) throws NamingException { |
| if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) { |
| System.out.println("Cache hits: " + cacheHits + "; misses: " |
| + cacheMisses); |
| } |
| String cacheKey = name + "|" + attrId; |
| byte[][] values = valueCache.get(cacheKey); |
| if (values != null) { |
| cacheHits++; |
| return values; |
| } |
| cacheMisses++; |
| Map<String, byte[][]> attrs = getValueMap(); |
| values = attrs.get(attrId); |
| return values; |
| } |
| |
| /** |
| * Get a map containing the values for this request. The first time |
| * this method is called on an object, the LDAP request is sent, |
| * the results parsed and added to a private map and also to the |
| * cache of this LDAPCertStore. Subsequent calls return the private |
| * map immediately. |
| * |
| * The map contains an entry for each requested attribute. The |
| * attribute name is the key, values are byte[][]. If there are no |
| * values for that attribute, values are byte[0][]. |
| * |
| * @return the value Map |
| * @throws NamingException if a naming exception occurs |
| */ |
| private Map<String, byte[][]> getValueMap() throws NamingException { |
| if (valueMap != null) { |
| return valueMap; |
| } |
| if (DEBUG) { |
| System.out.println("Request: " + name + ":" + requestedAttributes); |
| requests++; |
| if (requests % 5 == 0) { |
| System.out.println("LDAP requests: " + requests); |
| } |
| } |
| valueMap = new HashMap<>(8); |
| String[] attrIds = requestedAttributes.toArray(STRING0); |
| Attributes attrs; |
| try { |
| attrs = ctx.getAttributes(name, attrIds); |
| } catch (NameNotFoundException e) { |
| // name does not exist on this LDAP server |
| // treat same as not attributes found |
| attrs = EMPTY_ATTRIBUTES; |
| } |
| for (String attrId : requestedAttributes) { |
| Attribute attr = attrs.get(attrId); |
| byte[][] values = getAttributeValues(attr); |
| cacheAttribute(attrId, values); |
| valueMap.put(attrId, values); |
| } |
| return valueMap; |
| } |
| |
| /** |
| * Add the values to the cache. |
| */ |
| private void cacheAttribute(String attrId, byte[][] values) { |
| String cacheKey = name + "|" + attrId; |
| valueCache.put(cacheKey, values); |
| } |
| |
| /** |
| * Get the values for the given attribute. If the attribute is null |
| * or does not contain any values, a zero length byte array is |
| * returned. NOTE that it is assumed that all values are byte arrays. |
| */ |
| private byte[][] getAttributeValues(Attribute attr) |
| throws NamingException { |
| byte[][] values; |
| if (attr == null) { |
| values = BB0; |
| } else { |
| values = new byte[attr.size()][]; |
| int i = 0; |
| NamingEnumeration<?> enum_ = attr.getAll(); |
| while (enum_.hasMore()) { |
| Object obj = enum_.next(); |
| if (debug != null) { |
| if (obj instanceof String) { |
| debug.println("LDAPCertStore.getAttrValues() " |
| + "enum.next is a string!: " + obj); |
| } |
| } |
| byte[] value = (byte[])obj; |
| values[i++] = value; |
| } |
| } |
| return values; |
| } |
| |
| } |
| |
| /* |
| * Gets certificates from an attribute id and location in the LDAP |
| * directory. Returns a Collection containing only the Certificates that |
| * match the specified CertSelector. |
| * |
| * @param name the location holding the attribute |
| * @param id the attribute identifier |
| * @param sel a CertSelector that the Certificates must match |
| * @return a Collection of Certificates found |
| * @throws CertStoreException if an exception occurs |
| */ |
| private Collection<X509Certificate> getCertificates(LDAPRequest request, |
| String id, X509CertSelector sel) throws CertStoreException { |
| |
| /* fetch encoded certs from storage */ |
| byte[][] encodedCert; |
| try { |
| encodedCert = request.getValues(id); |
| } catch (NamingException namingEx) { |
| throw new CertStoreException(namingEx); |
| } |
| |
| int n = encodedCert.length; |
| if (n == 0) { |
| return Collections.emptySet(); |
| } |
| |
| List<X509Certificate> certs = new ArrayList<>(n); |
| /* decode certs and check if they satisfy selector */ |
| for (int i = 0; i < n; i++) { |
| ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]); |
| try { |
| Certificate cert = cf.generateCertificate(bais); |
| if (sel.match(cert)) { |
| certs.add((X509Certificate)cert); |
| } |
| } catch (CertificateException e) { |
| if (debug != null) { |
| debug.println("LDAPCertStore.getCertificates() encountered " |
| + "exception while parsing cert, skipping the bad data: "); |
| HexDumpEncoder encoder = new HexDumpEncoder(); |
| debug.println( |
| "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]"); |
| } |
| } |
| } |
| |
| return certs; |
| } |
| |
| /* |
| * Gets certificate pairs from an attribute id and location in the LDAP |
| * directory. |
| * |
| * @param name the location holding the attribute |
| * @param id the attribute identifier |
| * @return a Collection of X509CertificatePairs found |
| * @throws CertStoreException if an exception occurs |
| */ |
| private Collection<X509CertificatePair> getCertPairs( |
| LDAPRequest request, String id) throws CertStoreException { |
| |
| /* fetch the encoded cert pairs from storage */ |
| byte[][] encodedCertPair; |
| try { |
| encodedCertPair = request.getValues(id); |
| } catch (NamingException namingEx) { |
| throw new CertStoreException(namingEx); |
| } |
| |
| int n = encodedCertPair.length; |
| if (n == 0) { |
| return Collections.emptySet(); |
| } |
| |
| List<X509CertificatePair> certPairs = new ArrayList<>(n); |
| /* decode each cert pair and add it to the Collection */ |
| for (int i = 0; i < n; i++) { |
| try { |
| X509CertificatePair certPair = |
| X509CertificatePair.generateCertificatePair(encodedCertPair[i]); |
| certPairs.add(certPair); |
| } catch (CertificateException e) { |
| if (debug != null) { |
| debug.println( |
| "LDAPCertStore.getCertPairs() encountered exception " |
| + "while parsing cert, skipping the bad data: "); |
| HexDumpEncoder encoder = new HexDumpEncoder(); |
| debug.println( |
| "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]"); |
| } |
| } |
| } |
| |
| return certPairs; |
| } |
| |
| /* |
| * Looks at certificate pairs stored in the crossCertificatePair attribute |
| * at the specified location in the LDAP directory. Returns a Collection |
| * containing all Certificates stored in the forward component that match |
| * the forward CertSelector and all Certificates stored in the reverse |
| * component that match the reverse CertSelector. |
| * <p> |
| * If either forward or reverse is null, all certificates from the |
| * corresponding component will be rejected. |
| * |
| * @param name the location to look in |
| * @param forward the forward CertSelector (or null) |
| * @param reverse the reverse CertSelector (or null) |
| * @return a Collection of Certificates found |
| * @throws CertStoreException if an exception occurs |
| */ |
| private Collection<X509Certificate> getMatchingCrossCerts( |
| LDAPRequest request, X509CertSelector forward, |
| X509CertSelector reverse) |
| throws CertStoreException { |
| // Get the cert pairs |
| Collection<X509CertificatePair> certPairs = |
| getCertPairs(request, CROSS_CERT); |
| |
| // Find Certificates that match and put them in a list |
| ArrayList<X509Certificate> matchingCerts = new ArrayList<>(); |
| for (X509CertificatePair certPair : certPairs) { |
| X509Certificate cert; |
| if (forward != null) { |
| cert = certPair.getForward(); |
| if ((cert != null) && forward.match(cert)) { |
| matchingCerts.add(cert); |
| } |
| } |
| if (reverse != null) { |
| cert = certPair.getReverse(); |
| if ((cert != null) && reverse.match(cert)) { |
| matchingCerts.add(cert); |
| } |
| } |
| } |
| return matchingCerts; |
| } |
| |
| /** |
| * Returns a <code>Collection</code> of <code>Certificate</code>s that |
| * match the specified selector. If no <code>Certificate</code>s |
| * match the selector, an empty <code>Collection</code> will be returned. |
| * <p> |
| * It is not practical to search every entry in the LDAP database for |
| * matching <code>Certificate</code>s. Instead, the <code>CertSelector</code> |
| * is examined in order to determine where matching <code>Certificate</code>s |
| * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). |
| * If the subject is specified, its directory entry is searched. If the |
| * issuer is specified, its directory entry is searched. If neither the |
| * subject nor the issuer are specified (or the selector is not an |
| * <code>X509CertSelector</code>), a <code>CertStoreException</code> is |
| * thrown. |
| * |
| * @param selector a <code>CertSelector</code> used to select which |
| * <code>Certificate</code>s should be returned. |
| * @return a <code>Collection</code> of <code>Certificate</code>s that |
| * match the specified selector |
| * @throws CertStoreException if an exception occurs |
| */ |
| public synchronized Collection<X509Certificate> engineGetCertificates |
| (CertSelector selector) throws CertStoreException { |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() selector: " |
| + String.valueOf(selector)); |
| } |
| |
| if (selector == null) { |
| selector = new X509CertSelector(); |
| } |
| if (!(selector instanceof X509CertSelector)) { |
| throw new CertStoreException("LDAPCertStore needs an X509CertSelector " + |
| "to find certs"); |
| } |
| X509CertSelector xsel = (X509CertSelector) selector; |
| int basicConstraints = xsel.getBasicConstraints(); |
| String subject = xsel.getSubjectAsString(); |
| String issuer = xsel.getIssuerAsString(); |
| HashSet<X509Certificate> certs = new HashSet<>(); |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() basicConstraints: " |
| + basicConstraints); |
| } |
| |
| // basicConstraints: |
| // -2: only EE certs accepted |
| // -1: no check is done |
| // 0: any CA certificate accepted |
| // >1: certificate's basicConstraints extension pathlen must match |
| if (subject != null) { |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() " |
| + "subject is not null"); |
| } |
| LDAPRequest request = new LDAPRequest(subject); |
| if (basicConstraints > -2) { |
| request.addRequestedAttribute(CROSS_CERT); |
| request.addRequestedAttribute(CA_CERT); |
| request.addRequestedAttribute(ARL); |
| if (prefetchCRLs) { |
| request.addRequestedAttribute(CRL); |
| } |
| } |
| if (basicConstraints < 0) { |
| request.addRequestedAttribute(USER_CERT); |
| } |
| |
| if (basicConstraints > -2) { |
| certs.addAll(getMatchingCrossCerts(request, xsel, null)); |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() after " |
| + "getMatchingCrossCerts(subject,xsel,null),certs.size(): " |
| + certs.size()); |
| } |
| certs.addAll(getCertificates(request, CA_CERT, xsel)); |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() after " |
| + "getCertificates(subject,CA_CERT,xsel),certs.size(): " |
| + certs.size()); |
| } |
| } |
| if (basicConstraints < 0) { |
| certs.addAll(getCertificates(request, USER_CERT, xsel)); |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() after " |
| + "getCertificates(subject,USER_CERT, xsel),certs.size(): " |
| + certs.size()); |
| } |
| } |
| } else { |
| if (debug != null) { |
| debug.println |
| ("LDAPCertStore.engineGetCertificates() subject is null"); |
| } |
| if (basicConstraints == -2) { |
| throw new CertStoreException("need subject to find EE certs"); |
| } |
| if (issuer == null) { |
| throw new CertStoreException("need subject or issuer to find certs"); |
| } |
| } |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() about to " |
| + "getMatchingCrossCerts..."); |
| } |
| if ((issuer != null) && (basicConstraints > -2)) { |
| LDAPRequest request = new LDAPRequest(issuer); |
| request.addRequestedAttribute(CROSS_CERT); |
| request.addRequestedAttribute(CA_CERT); |
| request.addRequestedAttribute(ARL); |
| if (prefetchCRLs) { |
| request.addRequestedAttribute(CRL); |
| } |
| |
| certs.addAll(getMatchingCrossCerts(request, null, xsel)); |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() after " |
| + "getMatchingCrossCerts(issuer,null,xsel),certs.size(): " |
| + certs.size()); |
| } |
| certs.addAll(getCertificates(request, CA_CERT, xsel)); |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() after " |
| + "getCertificates(issuer,CA_CERT,xsel),certs.size(): " |
| + certs.size()); |
| } |
| } |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCertificates() returning certs"); |
| } |
| return certs; |
| } |
| |
| /* |
| * Gets CRLs from an attribute id and location in the LDAP directory. |
| * Returns a Collection containing only the CRLs that match the |
| * specified CRLSelector. |
| * |
| * @param name the location holding the attribute |
| * @param id the attribute identifier |
| * @param sel a CRLSelector that the CRLs must match |
| * @return a Collection of CRLs found |
| * @throws CertStoreException if an exception occurs |
| */ |
| private Collection<X509CRL> getCRLs(LDAPRequest request, String id, |
| X509CRLSelector sel) throws CertStoreException { |
| |
| /* fetch the encoded crls from storage */ |
| byte[][] encodedCRL; |
| try { |
| encodedCRL = request.getValues(id); |
| } catch (NamingException namingEx) { |
| throw new CertStoreException(namingEx); |
| } |
| |
| int n = encodedCRL.length; |
| if (n == 0) { |
| return Collections.emptySet(); |
| } |
| |
| List<X509CRL> crls = new ArrayList<>(n); |
| /* decode each crl and check if it matches selector */ |
| for (int i = 0; i < n; i++) { |
| try { |
| CRL crl = cf.generateCRL(new ByteArrayInputStream(encodedCRL[i])); |
| if (sel.match(crl)) { |
| crls.add((X509CRL)crl); |
| } |
| } catch (CRLException e) { |
| if (debug != null) { |
| debug.println("LDAPCertStore.getCRLs() encountered exception" |
| + " while parsing CRL, skipping the bad data: "); |
| HexDumpEncoder encoder = new HexDumpEncoder(); |
| debug.println("[ " + encoder.encodeBuffer(encodedCRL[i]) + " ]"); |
| } |
| } |
| } |
| |
| return crls; |
| } |
| |
| /** |
| * Returns a <code>Collection</code> of <code>CRL</code>s that |
| * match the specified selector. If no <code>CRL</code>s |
| * match the selector, an empty <code>Collection</code> will be returned. |
| * <p> |
| * It is not practical to search every entry in the LDAP database for |
| * matching <code>CRL</code>s. Instead, the <code>CRLSelector</code> |
| * is examined in order to determine where matching <code>CRL</code>s |
| * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). |
| * If issuerNames or certChecking are specified, the issuer's directory |
| * entry is searched. If neither issuerNames or certChecking are specified |
| * (or the selector is not an <code>X509CRLSelector</code>), a |
| * <code>CertStoreException</code> is thrown. |
| * |
| * @param selector A <code>CRLSelector</code> used to select which |
| * <code>CRL</code>s should be returned. Specify <code>null</code> |
| * to return all <code>CRL</code>s. |
| * @return A <code>Collection</code> of <code>CRL</code>s that |
| * match the specified selector |
| * @throws CertStoreException if an exception occurs |
| */ |
| public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector) |
| throws CertStoreException { |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCRLs() selector: " |
| + selector); |
| } |
| // Set up selector and collection to hold CRLs |
| if (selector == null) { |
| selector = new X509CRLSelector(); |
| } |
| if (!(selector instanceof X509CRLSelector)) { |
| throw new CertStoreException("need X509CRLSelector to find CRLs"); |
| } |
| X509CRLSelector xsel = (X509CRLSelector) selector; |
| HashSet<X509CRL> crls = new HashSet<>(); |
| |
| // Look in directory entry for issuer of cert we're checking. |
| Collection<Object> issuerNames; |
| X509Certificate certChecking = xsel.getCertificateChecking(); |
| if (certChecking != null) { |
| issuerNames = new HashSet<>(); |
| X500Principal issuer = certChecking.getIssuerX500Principal(); |
| issuerNames.add(issuer.getName(X500Principal.RFC2253)); |
| } else { |
| // But if we don't know which cert we're checking, try the directory |
| // entries of all acceptable CRL issuers |
| issuerNames = xsel.getIssuerNames(); |
| if (issuerNames == null) { |
| throw new CertStoreException("need issuerNames or certChecking to " |
| + "find CRLs"); |
| } |
| } |
| for (Object nameObject : issuerNames) { |
| String issuerName; |
| if (nameObject instanceof byte[]) { |
| try { |
| X500Principal issuer = new X500Principal((byte[])nameObject); |
| issuerName = issuer.getName(X500Principal.RFC2253); |
| } catch (IllegalArgumentException e) { |
| continue; |
| } |
| } else { |
| issuerName = (String)nameObject; |
| } |
| // If all we want is CA certs, try to get the (probably shorter) ARL |
| Collection<X509CRL> entryCRLs = Collections.emptySet(); |
| if (certChecking == null || certChecking.getBasicConstraints() != -1) { |
| LDAPRequest request = new LDAPRequest(issuerName); |
| request.addRequestedAttribute(CROSS_CERT); |
| request.addRequestedAttribute(CA_CERT); |
| request.addRequestedAttribute(ARL); |
| if (prefetchCRLs) { |
| request.addRequestedAttribute(CRL); |
| } |
| try { |
| entryCRLs = getCRLs(request, ARL, xsel); |
| if (entryCRLs.isEmpty()) { |
| // no ARLs found. We assume that means that there are |
| // no ARLs on this server at all and prefetch the CRLs. |
| prefetchCRLs = true; |
| } else { |
| crls.addAll(entryCRLs); |
| } |
| } catch (CertStoreException e) { |
| if (debug != null) { |
| debug.println("LDAPCertStore.engineGetCRLs non-fatal error " |
| + "retrieving ARLs:" + e); |
| e.printStackTrace(); |
| } |
| } |
| } |
| // Otherwise, get the CRL |
| // if certChecking is null, we don't know if we should look in ARL or CRL |
| // attribute, so check both for matching CRLs. |
| if (entryCRLs.isEmpty() || certChecking == null) { |
| LDAPRequest request = new LDAPRequest(issuerName); |
| request.addRequestedAttribute(CRL); |
| entryCRLs = getCRLs(request, CRL, xsel); |
| crls.addAll(entryCRLs); |
| } |
| } |
| return crls; |
| } |
| |
| // converts an LDAP URI into LDAPCertStoreParameters |
| static LDAPCertStoreParameters getParameters(URI uri) { |
| String host = uri.getHost(); |
| if (host == null) { |
| return new SunLDAPCertStoreParameters(); |
| } else { |
| int port = uri.getPort(); |
| return (port == -1 |
| ? new SunLDAPCertStoreParameters(host) |
| : new SunLDAPCertStoreParameters(host, port)); |
| } |
| } |
| |
| /* |
| * Subclass of LDAPCertStoreParameters with overridden equals/hashCode |
| * methods. This is necessary because the parameters are used as |
| * keys in the LDAPCertStore cache. |
| */ |
| private static class SunLDAPCertStoreParameters |
| extends LDAPCertStoreParameters { |
| |
| private volatile int hashCode = 0; |
| |
| SunLDAPCertStoreParameters(String serverName, int port) { |
| super(serverName, port); |
| } |
| SunLDAPCertStoreParameters(String serverName) { |
| super(serverName); |
| } |
| SunLDAPCertStoreParameters() { |
| super(); |
| } |
| public boolean equals(Object obj) { |
| if (!(obj instanceof LDAPCertStoreParameters)) { |
| return false; |
| } |
| LDAPCertStoreParameters params = (LDAPCertStoreParameters) obj; |
| return (getPort() == params.getPort() && |
| getServerName().equalsIgnoreCase(params.getServerName())); |
| } |
| public int hashCode() { |
| if (hashCode == 0) { |
| int result = 17; |
| result = 37*result + getPort(); |
| result = 37*result + |
| getServerName().toLowerCase(Locale.ENGLISH).hashCode(); |
| hashCode = result; |
| } |
| return hashCode; |
| } |
| } |
| |
| /* |
| * This inner class wraps an existing X509CertSelector and adds |
| * additional criteria to match on when the certificate's subject is |
| * different than the LDAP Distinguished Name entry. The LDAPCertStore |
| * implementation uses the subject DN as the directory entry for |
| * looking up certificates. This can be problematic if the certificates |
| * that you want to fetch have a different subject DN than the entry |
| * where they are stored. You could set the selector's subject to the |
| * LDAP DN entry, but then the resulting match would fail to find the |
| * desired certificates because the subject DNs would not match. This |
| * class avoids that problem by introducing a certSubject which should |
| * be set to the certificate's subject DN when it is different than |
| * the LDAP DN. |
| */ |
| static class LDAPCertSelector extends X509CertSelector { |
| |
| private X500Principal certSubject; |
| private X509CertSelector selector; |
| private X500Principal subject; |
| |
| /** |
| * Creates an LDAPCertSelector. |
| * |
| * @param selector the X509CertSelector to wrap |
| * @param certSubject the subject DN of the certificate that you want |
| * to retrieve via LDAP |
| * @param ldapDN the LDAP DN where the certificate is stored |
| */ |
| LDAPCertSelector(X509CertSelector selector, X500Principal certSubject, |
| String ldapDN) throws IOException { |
| this.selector = selector == null ? new X509CertSelector() : selector; |
| this.certSubject = certSubject; |
| this.subject = new X500Name(ldapDN).asX500Principal(); |
| } |
| |
| // we only override the get (accessor methods) since the set methods |
| // will not be invoked by the code that uses this LDAPCertSelector. |
| public X509Certificate getCertificate() { |
| return selector.getCertificate(); |
| } |
| public BigInteger getSerialNumber() { |
| return selector.getSerialNumber(); |
| } |
| public X500Principal getIssuer() { |
| return selector.getIssuer(); |
| } |
| public String getIssuerAsString() { |
| return selector.getIssuerAsString(); |
| } |
| public byte[] getIssuerAsBytes() throws IOException { |
| return selector.getIssuerAsBytes(); |
| } |
| public X500Principal getSubject() { |
| // return the ldap DN |
| return subject; |
| } |
| public String getSubjectAsString() { |
| // return the ldap DN |
| return subject.getName(); |
| } |
| public byte[] getSubjectAsBytes() throws IOException { |
| // return the encoded ldap DN |
| return subject.getEncoded(); |
| } |
| public byte[] getSubjectKeyIdentifier() { |
| return selector.getSubjectKeyIdentifier(); |
| } |
| public byte[] getAuthorityKeyIdentifier() { |
| return selector.getAuthorityKeyIdentifier(); |
| } |
| public Date getCertificateValid() { |
| return selector.getCertificateValid(); |
| } |
| public Date getPrivateKeyValid() { |
| return selector.getPrivateKeyValid(); |
| } |
| public String getSubjectPublicKeyAlgID() { |
| return selector.getSubjectPublicKeyAlgID(); |
| } |
| public PublicKey getSubjectPublicKey() { |
| return selector.getSubjectPublicKey(); |
| } |
| public boolean[] getKeyUsage() { |
| return selector.getKeyUsage(); |
| } |
| public Set<String> getExtendedKeyUsage() { |
| return selector.getExtendedKeyUsage(); |
| } |
| public boolean getMatchAllSubjectAltNames() { |
| return selector.getMatchAllSubjectAltNames(); |
| } |
| public Collection<List<?>> getSubjectAlternativeNames() { |
| return selector.getSubjectAlternativeNames(); |
| } |
| public byte[] getNameConstraints() { |
| return selector.getNameConstraints(); |
| } |
| public int getBasicConstraints() { |
| return selector.getBasicConstraints(); |
| } |
| public Set<String> getPolicy() { |
| return selector.getPolicy(); |
| } |
| public Collection<List<?>> getPathToNames() { |
| return selector.getPathToNames(); |
| } |
| |
| public boolean match(Certificate cert) { |
| // temporarily set the subject criterion to the certSubject |
| // so that match will not reject the desired certificates |
| selector.setSubject(certSubject); |
| boolean match = selector.match(cert); |
| selector.setSubject(subject); |
| return match; |
| } |
| } |
| |
| /** |
| * This class has the same purpose as LDAPCertSelector except it is for |
| * X.509 CRLs. |
| */ |
| static class LDAPCRLSelector extends X509CRLSelector { |
| |
| private X509CRLSelector selector; |
| private Collection<X500Principal> certIssuers; |
| private Collection<X500Principal> issuers; |
| private HashSet<Object> issuerNames; |
| |
| /** |
| * Creates an LDAPCRLSelector. |
| * |
| * @param selector the X509CRLSelector to wrap |
| * @param certIssuers the issuer DNs of the CRLs that you want |
| * to retrieve via LDAP |
| * @param ldapDN the LDAP DN where the CRL is stored |
| */ |
| LDAPCRLSelector(X509CRLSelector selector, |
| Collection<X500Principal> certIssuers, String ldapDN) |
| throws IOException { |
| this.selector = selector == null ? new X509CRLSelector() : selector; |
| this.certIssuers = certIssuers; |
| issuerNames = new HashSet<>(); |
| issuerNames.add(ldapDN); |
| issuers = new HashSet<>(); |
| issuers.add(new X500Name(ldapDN).asX500Principal()); |
| } |
| // we only override the get (accessor methods) since the set methods |
| // will not be invoked by the code that uses this LDAPCRLSelector. |
| public Collection<X500Principal> getIssuers() { |
| // return the ldap DN |
| return Collections.unmodifiableCollection(issuers); |
| } |
| public Collection<Object> getIssuerNames() { |
| // return the ldap DN |
| return Collections.unmodifiableCollection(issuerNames); |
| } |
| public BigInteger getMinCRL() { |
| return selector.getMinCRL(); |
| } |
| public BigInteger getMaxCRL() { |
| return selector.getMaxCRL(); |
| } |
| public Date getDateAndTime() { |
| return selector.getDateAndTime(); |
| } |
| public X509Certificate getCertificateChecking() { |
| return selector.getCertificateChecking(); |
| } |
| public boolean match(CRL crl) { |
| // temporarily set the issuer criterion to the certIssuers |
| // so that match will not reject the desired CRL |
| selector.setIssuers(certIssuers); |
| boolean match = selector.match(crl); |
| selector.setIssuers(issuers); |
| return match; |
| } |
| } |
| } |