1153 lines
46 KiB
Java
1153 lines
46 KiB
Java
/*
|
|
* Copyright (c) 2015, 2023, 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.CompositeName;
|
|
import javax.naming.InvalidNameException;
|
|
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.naming.ldap.LdapContext;
|
|
import javax.naming.ldap.LdapName;
|
|
import javax.security.auth.x500.X500Principal;
|
|
|
|
import com.sun.jndi.ldap.LdapReferralException;
|
|
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.GetBooleanAction;
|
|
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";
|
|
|
|
/*
|
|
* Internal system property, that when set to "true", disables the
|
|
* JNDI application resource files lookup to prevent recursion issues
|
|
* when validating signed JARs with LDAP URLs in certificates.
|
|
*/
|
|
private final static String PROP_DISABLE_APP_RESOURCE_FILES =
|
|
"sun.security.certpath.ldap.disable.app.resource.files";
|
|
|
|
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 {
|
|
|
|
SecurityManager security = System.getSecurityManager();
|
|
if (security != null) {
|
|
security.checkConnect(params.getServerName(), params.getPort());
|
|
}
|
|
|
|
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);
|
|
|
|
// If property is set to true, disable application resource file lookup.
|
|
boolean disableAppResourceFiles = AccessController.doPrivileged(
|
|
new GetBooleanAction(PROP_DISABLE_APP_RESOURCE_FILES));
|
|
if (disableAppResourceFiles) {
|
|
if (debug != null) {
|
|
debug.println("LDAPCertStore disabling app resource files");
|
|
}
|
|
env.put("com.sun.naming.disable.app.resource.files", "true");
|
|
}
|
|
|
|
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, "throw");
|
|
}
|
|
} 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 static String checkName(String name) throws CertStoreException {
|
|
if (name == null) {
|
|
throw new CertStoreException("Name absent");
|
|
}
|
|
try {
|
|
if (new CompositeName(name).size() > 1) {
|
|
throw new CertStoreException("Invalid name: " + name);
|
|
}
|
|
} catch (InvalidNameException ine) {
|
|
throw new CertStoreException("Invalid name: " + name, ine);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* 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 static 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;
|
|
}
|
|
|
|
/**
|
|
* 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 LdapName name;
|
|
private Map<String, byte[][]> valueMap;
|
|
private final List<String> requestedAttributes;
|
|
|
|
LDAPRequest(String name) throws CertStoreException {
|
|
try {
|
|
// Convert DN to an LdapName so that it is not treated as a
|
|
// composite name by JNDI. In JNDI, using a string name is
|
|
// equivalent to calling new CompositeName(stringName).
|
|
this.name = new LdapName(name);
|
|
} catch (InvalidNameException ine) {
|
|
throw new CertStoreException("Invalid name: " + name, ine);
|
|
}
|
|
requestedAttributes = new ArrayList<>(5);
|
|
}
|
|
|
|
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 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 (LdapReferralException lre) {
|
|
// LdapCtx has a hopCount field to avoid infinite loop
|
|
while (true) {
|
|
try {
|
|
String newName = (String) lre.getReferralInfo();
|
|
URI newUri = new URI(newName);
|
|
if (!newUri.getScheme().equalsIgnoreCase("ldap")) {
|
|
throw new IllegalArgumentException("Not LDAP");
|
|
}
|
|
String newDn = newUri.getPath();
|
|
if (newDn != null && newDn.charAt(0) == '/') {
|
|
newDn = newDn.substring(1);
|
|
}
|
|
// In JNDI, it is not possible to use an LdapName for
|
|
// the referral DN, so we must validate the syntax of
|
|
// the string DN.
|
|
checkName(newDn);
|
|
} catch (Exception e) {
|
|
throw new NamingException("Cannot follow referral to "
|
|
+ lre.getReferralInfo());
|
|
}
|
|
LdapContext refCtx =
|
|
(LdapContext)lre.getReferralContext();
|
|
|
|
// repeat the original operation at the new context
|
|
try {
|
|
attrs = refCtx.getAttributes(name, attrIds);
|
|
break;
|
|
} catch (LdapReferralException re) {
|
|
lre = re;
|
|
continue;
|
|
} finally {
|
|
// Make sure we close referral context
|
|
refCtx.close();
|
|
}
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 if (nameObject instanceof String) {
|
|
issuerName = (String)nameObject;
|
|
} else {
|
|
throw new CertStoreException(
|
|
"unrecognized issuerName: must be String or byte[]");
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|