com.unboundid.util.ssl.JVMDefaultTrustManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unboundid-ldapsdk Show documentation
Show all versions of unboundid-ldapsdk Show documentation
The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use
Java API for communicating with LDAP directory servers and performing
related tasks like reading and writing LDIF, encoding and decoding data
using base64 and ASN.1 BER, and performing secure communication. This
package contains the Standard Edition of the LDAP SDK, which is a
complete, general-purpose library for communicating with LDAPv3 directory
servers.
/*
* Copyright 2017-2023 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2017-2023 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2017-2023 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util.ssl;
import java.io.File;
import java.io.FileInputStream;
import java.io.Serializable;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.X509TrustManager;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.util.CryptoHelper;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.ssl.cert.AuthorityKeyIdentifierExtension;
import com.unboundid.util.ssl.cert.SubjectKeyIdentifierExtension;
import com.unboundid.util.ssl.cert.X509CertificateExtension;
import static com.unboundid.util.ssl.SSLMessages.*;
/**
* This class provides an implementation of a trust manager that relies on the
* JVM's default set of trusted issuers.
*
* This implementation will first look for the trust store in the following
* locations within the Java installation, in the following order:
*
* - {@code lib/security/jssecacerts}
* - {@code jre/lib/security/jssecacerts}
* - {@code lib/security/cacerts}
* - {@code jre/lib/security/cacerts}
*
* If none of those files exist (or if they cannot be parsed as a JKS or PKCS
* #12 key store), then we will search for a {@code jssecacerts} or
* {@code cacerts} file below the Java home directory.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class JVMDefaultTrustManager
implements X509TrustManager, Serializable
{
/**
* A reference to the singleton instance of this class.
*/
@NotNull private static final AtomicReference
INSTANCE = new AtomicReference<>();
/**
* The name of the system property that specifies the path to the Java
* installation for the currently-running JVM.
*/
@NotNull private static final String PROPERTY_JAVA_HOME = "java.home";
/**
* A set of alternate file extensions that may be used by Java keystores.
*/
@NotNull static final String[] FILE_EXTENSIONS =
{
".jks",
".p12",
".pkcs12",
".pfx",
};
/**
* A pre-allocated empty certificate array.
*/
@NotNull private static final X509Certificate[] NO_CERTIFICATES =
new X509Certificate[0];
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = -8587938729712485943L;
// A certificate exception that should be thrown for any attempt to use this
// trust store.
@Nullable private final CertificateException certificateException;
// The file from which they keystore was loaded.
@Nullable private final File caCertsFile;
// The keystore instance containing the JVM's default set of trusted issuers.
@Nullable private final KeyStore keystore;
// A map of the certificates in the keystore, indexed by signature.
@NotNull private final Map
trustedCertsBySignature;
// A map of the certificates in the keystore, indexed by key ID.
@NotNull private final Map trustedCertsByKeyID;
/**
* Creates an instance of this trust manager.
*
* @param javaHomePropertyName The name of the system property that should
* specify the path to the Java installation.
*/
JVMDefaultTrustManager(@NotNull final String javaHomePropertyName)
{
// Determine the path to the root of the Java installation.
final String javaHomePath =
StaticUtils.getSystemProperty(javaHomePropertyName);
if (javaHomePath == null)
{
certificateException = new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_NO_JAVA_HOME.get(
javaHomePropertyName));
caCertsFile = null;
keystore = null;
trustedCertsBySignature = Collections.emptyMap();
trustedCertsByKeyID = Collections.emptyMap();
return;
}
final File javaHomeDirectory = new File(javaHomePath);
if ((! javaHomeDirectory.exists()) || (! javaHomeDirectory.isDirectory()))
{
certificateException = new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_INVALID_JAVA_HOME.get(
javaHomePropertyName, javaHomePath));
caCertsFile = null;
keystore = null;
trustedCertsBySignature = Collections.emptyMap();
trustedCertsByKeyID = Collections.emptyMap();
return;
}
// Get a keystore instance that is loaded from the JVM's default set of
// trusted issuers.
final ObjectPair keystorePair;
try
{
keystorePair = getJVMDefaultKeyStore(javaHomeDirectory);
}
catch (final CertificateException ce)
{
Debug.debugException(ce);
certificateException = ce;
caCertsFile = null;
keystore = null;
trustedCertsBySignature = Collections.emptyMap();
trustedCertsByKeyID = Collections.emptyMap();
return;
}
keystore = keystorePair.getFirst();
caCertsFile = keystorePair.getSecond();
// Iterate through the certificates in the keystore and load them into a
// map for faster and more reliable access.
final LinkedHashMap certsBySignature =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(50));
final LinkedHashMap certsByKeyID =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(50));
try
{
final Enumeration aliasEnumeration = keystore.aliases();
while (aliasEnumeration.hasMoreElements())
{
final String alias = aliasEnumeration.nextElement();
try
{
final X509Certificate certificate =
(X509Certificate) keystore.getCertificate(alias);
if (certificate != null)
{
certsBySignature.put(
new ASN1OctetString(certificate.getSignature()),
certificate);
try
{
final com.unboundid.util.ssl.cert.X509Certificate c =
new com.unboundid.util.ssl.cert.X509Certificate(
certificate.getEncoded());
for (final X509CertificateExtension e : c.getExtensions())
{
if (e instanceof SubjectKeyIdentifierExtension)
{
final SubjectKeyIdentifierExtension skie =
(SubjectKeyIdentifierExtension) e;
certsByKeyID.put(
new ASN1OctetString(skie.getKeyIdentifier().getValue()),
c);
}
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
catch (final Exception e)
{
Debug.debugException(e);
certificateException = new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_ERROR_ITERATING_THROUGH_CACERTS.get(
caCertsFile.getAbsolutePath(),
StaticUtils.getExceptionMessage(e)),
e);
trustedCertsBySignature = Collections.emptyMap();
trustedCertsByKeyID = Collections.emptyMap();
return;
}
trustedCertsBySignature = Collections.unmodifiableMap(certsBySignature);
trustedCertsByKeyID = Collections.unmodifiableMap(certsByKeyID);
certificateException = null;
}
/**
* Retrieves the singleton instance of this trust manager.
*
* @return The singleton instance of this trust manager.
*/
@NotNull()
public static JVMDefaultTrustManager getInstance()
{
final JVMDefaultTrustManager existingInstance = INSTANCE.get();
if (existingInstance != null)
{
return existingInstance;
}
final JVMDefaultTrustManager newInstance =
new JVMDefaultTrustManager(PROPERTY_JAVA_HOME);
if (INSTANCE.compareAndSet(null, newInstance))
{
return newInstance;
}
else
{
return INSTANCE.get();
}
}
/**
* Retrieves the keystore that backs this trust manager.
*
* @return The keystore that backs this trust manager.
*
* @throws CertificateException If a problem was encountered while
* initializing this trust manager.
*/
@NotNull()
KeyStore getKeyStore()
throws CertificateException
{
if (certificateException != null)
{
throw certificateException;
}
return keystore;
}
/**
* Retrieves the path to the the file containing the JVM's default set of
* trusted issuers.
*
* @return The path to the file containing the JVM's default set of
* trusted issuers.
*
* @throws CertificateException If a problem was encountered while
* initializing this trust manager.
*/
@NotNull()
public File getCACertsFile()
throws CertificateException
{
if (certificateException != null)
{
throw certificateException;
}
return caCertsFile;
}
/**
* Retrieves the certificates included in this trust manager.
*
* @return The certificates included in this trust manager.
*
* @throws CertificateException If a problem was encountered while
* initializing this trust manager.
*/
@NotNull()
public Collection getTrustedIssuerCertificates()
throws CertificateException
{
if (certificateException != null)
{
throw certificateException;
}
return trustedCertsBySignature.values();
}
/**
* Checks to determine whether the provided client certificate chain should be
* trusted.
*
* @param chain The client certificate chain for which to make the
* determination.
* @param authType The authentication type based on the client certificate.
*
* @throws CertificateException If the provided client certificate chain
* should not be trusted.
*/
@Override()
public void checkClientTrusted(@NotNull final X509Certificate[] chain,
@NotNull final String authType)
throws CertificateException
{
checkTrusted(chain);
}
/**
* Checks to determine whether the provided server certificate chain should be
* trusted.
*
* @param chain The server certificate chain for which to make the
* determination.
* @param authType The key exchange algorithm used.
*
* @throws CertificateException If the provided server certificate chain
* should not be trusted.
*/
@Override()
public void checkServerTrusted(@NotNull final X509Certificate[] chain,
@NotNull final String authType)
throws CertificateException
{
checkTrusted(chain);
}
/**
* Retrieves the accepted issuer certificates for this trust manager.
*
* @return The accepted issuer certificates for this trust manager, or an
* empty set of accepted issuers if a problem was encountered while
* initializing this trust manager.
*/
@Override()
@NotNull()
public X509Certificate[] getAcceptedIssuers()
{
if (certificateException != null)
{
return NO_CERTIFICATES;
}
final X509Certificate[] acceptedIssuers =
new X509Certificate[trustedCertsBySignature.size()];
return trustedCertsBySignature.values().toArray(acceptedIssuers);
}
/**
* Retrieves a {@code KeyStore} that contains the JVM's default set of trusted
* issuers.
*
* @param javaHomeDirectory The path to the JVM installation home directory.
*
* @return An {@code ObjectPair} that includes the keystore and the file from
* which it was loaded.
*
* @throws CertificateException If the keystore could not be found or
* loaded.
*/
@NotNull()
private static ObjectPair getJVMDefaultKeyStore(
@NotNull final File javaHomeDirectory)
throws CertificateException
{
final File libSecurityJSSECACerts = StaticUtils.constructPath(
javaHomeDirectory, "lib", "security", "jssecacerts");
final File jreLibSecurityJSSECACerts = StaticUtils.constructPath(
javaHomeDirectory, "jre", "lib", "security", "jssecacerts");
final File libSecurityCACerts = StaticUtils.constructPath(javaHomeDirectory,
"lib", "security", "cacerts");
final File jreLibSecurityCACerts = StaticUtils.constructPath(
javaHomeDirectory, "jre", "lib", "security", "cacerts");
final ArrayList tryFirstFiles =
new ArrayList<>(4 * FILE_EXTENSIONS.length + 2);
tryFirstFiles.add(libSecurityCACerts);
tryFirstFiles.add(jreLibSecurityCACerts);
for (final String extension : FILE_EXTENSIONS)
{
tryFirstFiles.add(
new File(libSecurityJSSECACerts.getAbsolutePath() + extension));
tryFirstFiles.add(
new File(jreLibSecurityJSSECACerts.getAbsolutePath() + extension));
tryFirstFiles.add(
new File(libSecurityCACerts.getAbsolutePath() + extension));
tryFirstFiles.add(
new File(jreLibSecurityCACerts.getAbsolutePath() + extension));
}
for (final File f : tryFirstFiles)
{
final KeyStore keyStore = loadKeyStore(f);
if (keyStore != null)
{
return new ObjectPair<>(keyStore, f);
}
}
// If we didn't find it with known paths, then try to find it with a
// recursive filesystem search below the Java home directory.
final LinkedHashMap exceptions =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(1));
final ObjectPair keystorePair =
searchForKeyStore(javaHomeDirectory, exceptions);
if (keystorePair != null)
{
return keystorePair;
}
// If we've gotten here, then we couldn't find the keystore. Construct a
// message from the set of exceptions.
if (exceptions.isEmpty())
{
throw new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_CACERTS_NOT_FOUND_NO_EXCEPTION.get());
}
else
{
final StringBuilder buffer = new StringBuilder();
buffer.append(
ERR_JVM_DEFAULT_TRUST_MANAGER_CACERTS_NOT_FOUND_WITH_EXCEPTION.
get());
for (final Map.Entry e : exceptions.entrySet())
{
if (buffer.charAt(buffer.length() - 1) != '.')
{
buffer.append('.');
}
buffer.append(" ");
buffer.append(ERR_JVM_DEFAULT_TRUST_MANAGER_LOAD_ERROR.get(
e.getKey().getAbsolutePath(),
StaticUtils.getExceptionMessage(e.getValue())));
}
throw new CertificateException(buffer.toString());
}
}
/**
* Recursively searches for a valid keystore file below the specified portion
* of the filesystem. Any file named "cacerts", ignoring differences in
* capitalization, and optionally ending with a number of different file
* extensions, will be examined to see if it can be parsed as a Java keystore.
* The first keystore that we find meeting that criteria will be returned.
*
* @param directory The directory in which to search. It must not be
* {@code null}.
* @param exceptions A map that correlates file paths with exceptions
* obtained while interacting with them. If an exception
* is encountered while interacting with this file, then
* it will be added to this map.
*
* @return The first valid keystore found that meets all the necessary
* criteria, or {@code null} if no such keystore could be found.
*/
@Nullable()
private static ObjectPair searchForKeyStore(
@NotNull final File directory,
@NotNull final Map exceptions)
{
filesInDirectoryLoop:
for (final File f : directory.listFiles())
{
if (f.isDirectory())
{
final ObjectPair p = searchForKeyStore(f, exceptions);
if (p != null)
{
return p;
}
}
else
{
final String lowerName = StaticUtils.toLowerCase(f.getName());
if (lowerName.equals("jssecacerts") || lowerName.equals("cacerts"))
{
try
{
final KeyStore keystore = loadKeyStore(f);
return new ObjectPair<>(keystore, f);
}
catch (final CertificateException ce)
{
Debug.debugException(ce);
exceptions.put(f, ce);
}
}
else
{
for (final String extension : FILE_EXTENSIONS)
{
if (lowerName.equals("jssecacerts" + extension) ||
lowerName.equals("cacerts" + extension))
{
try
{
final KeyStore keystore = loadKeyStore(f);
return new ObjectPair<>(keystore, f);
}
catch (final CertificateException ce)
{
Debug.debugException(ce);
exceptions.put(f, ce);
continue filesInDirectoryLoop;
}
}
}
}
}
}
return null;
}
/**
* Attempts to load the contents of the specified file as a Java keystore.
*
* @param f The file from which to load the keystore data.
*
* @return The keystore that was loaded from the specified file.
*
* @throws CertificateException If a problem occurs while trying to load the
*
*/
@Nullable()
private static KeyStore loadKeyStore(@NotNull final File f)
throws CertificateException
{
if ((! f.exists()) || (! f.isFile()))
{
return null;
}
CertificateException firstGetInstanceException = null;
CertificateException firstLoadException = null;
for (final String keyStoreType : new String[] { "JKS", "PKCS12" })
{
final KeyStore keyStore;
try
{
keyStore = CryptoHelper.getKeyStore(keyStoreType, null, true);
}
catch (final Exception e)
{
Debug.debugException(e);
if (firstGetInstanceException == null)
{
firstGetInstanceException = new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_CANNOT_INSTANTIATE_KEYSTORE.get(
keyStoreType, StaticUtils.getExceptionMessage(e)),
e);
}
continue;
}
try (FileInputStream inputStream = new FileInputStream(f))
{
keyStore.load(inputStream, null);
}
catch (final Exception e)
{
Debug.debugException(e);
if (firstLoadException == null)
{
firstLoadException = new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_CANNOT_ERROR_LOADING_KEYSTORE.get(
f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
e);
}
continue;
}
return keyStore;
}
if (firstLoadException != null)
{
throw firstLoadException;
}
throw firstGetInstanceException;
}
/**
* Ensures that the provided certificate chain should be considered trusted.
*
* @param chain The certificate chain to validate. It must not be
* {@code null}).
*
* @throws CertificateException If the provided certificate chain should not
* be considered trusted.
*/
void checkTrusted(@NotNull final X509Certificate[] chain)
throws CertificateException
{
if (certificateException != null)
{
throw certificateException;
}
if ((chain == null) || (chain.length == 0))
{
throw new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_NO_CERTS_IN_CHAIN.get());
}
// It is possible that the chain could rely on cross-signed certificates,
// and that we need to use a different path than the one presented in the
// provided chain. This requires us to potentially compute signatures using
// each certificate in the JVM's default trust store, which can be
// expensive. To avoid that, we'll first only try it if the presented
// chain has any certificates that are outside of their current validity
// window. If we get back a chain that is different from the one provided
// to this method, then we shouldn't need to do any further validation.
final X509Certificate[] chainToValidate = getChainToValidate(chain, true);
if (! Arrays.equals(chainToValidate, chain))
{
return;
}
boolean foundIssuer = false;
final Date currentTime = new Date();
for (final X509Certificate cert : chainToValidate)
{
final ASN1OctetString signature =
new ASN1OctetString(cert.getSignature());
foundIssuer = (trustedCertsBySignature.get(signature) != null);
if (foundIssuer)
{
break;
}
}
if (! foundIssuer)
{
// It's possible that the server sent an incomplete chain. Handle that
// possibility.
foundIssuer = checkIncompleteChain(chain);
}
if (! foundIssuer)
{
// We couldn't validate the presented chain, so see if we can find an
// alternative chain using a cross-signed certificate. In this case,
// we'll perform the expensive check regardless of the validity dates in
// the presented chain. If the attempt to find an alternative chain
// fails, then the getChainToValidate method will throw an exception.
// However, if the alternative chain contains only a single certificate,
// then that suggests the certificate is self-signed and not signed by
// any trusted issuer.
final X509Certificate[] alternativeChain =
getChainToValidate(chain, false);
if (Arrays.equals(alternativeChain, chain))
{
throw new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANGER_NO_TRUSTED_ISSUER_FOUND.get(
chainToString(chain)));
}
}
}
/**
* Retrieves a list containing the certificates in the chain that should
* actually be validated. All certificates in the chain will have been
* confirmed to be in their validity window.
*
* @param chain The chain for which to obtain the path to
* validate. It must not be {@code null} or
* empty.
* @param checkChainValidityWindow Indicates whether to examine the validity
* of certificates in the presented chain
* when determining whether to examine
* certificates by signature. If this is
* {@code true}, then the provided chain
* will be returned as long as all of the
* certificates in it are within their
* validity window. If this is
* {@code false}, then an attempt to find a
* chain based on signatures will be used
* even if all of the certificates in the
* presented chain are considered valid.
*
* @return The chain to be validated. It may be the same as the provided
* chain, or an alternate chain if any certificate in the provided
* chain was outside of its validity window but an alternative trust
* path could be found.
*
* @throws CertificateException If the presented certificate chain included
* a certificate that is outside of its
* current validity window and no alternate
* path could be found.
*/
@NotNull()
private X509Certificate[] getChainToValidate(
@NotNull final X509Certificate[] chain,
final boolean checkChainValidityWindow)
throws CertificateException
{
final Date currentDate = new Date();
// Check to see if any certificate in the provided chain is outside the
// current validity window. If not, then just use the provided chain.
CertificateException firstException = null;
if (checkChainValidityWindow)
{
for (int i=0; i < chain.length; i++)
{
final X509Certificate cert = chain[i];
final Date notBefore = cert.getNotBefore();
if (currentDate.before(notBefore))
{
if (firstException == null)
{
firstException = new CertificateNotYetValidException(
ERR_JVM_DEFAULT_TRUST_MANAGER_CERT_NOT_YET_VALID.get(
chainToString(chain), String.valueOf(cert.getSubjectDN()),
String.valueOf(notBefore)));
}
if (i == 0)
{
// If the peer certificate is not yet valid, then the entire chain
// must be considered invalid.
throw firstException;
}
else
{
break;
}
}
final Date notAfter = cert.getNotAfter();
if (currentDate.after(notAfter))
{
if (firstException == null)
{
firstException = new CertificateExpiredException(
ERR_JVM_DEFAULT_TRUST_MANAGER_CERT_EXPIRED.get(
chainToString(chain),
String.valueOf(cert.getSubjectDN()),
String.valueOf(notAfter)));
}
if (i == 0)
{
// If the peer certificate is expired, then the entire chain must be
// considered invalid.
throw firstException;
}
else
{
break;
}
}
}
// If all the certificates in the chain were within their validity window,
// then just use the provided chain.
if (firstException == null)
{
return chain;
}
}
// If we've gotten here, then we should try to find an alternative chain.
boolean foundAlternative = false;
final List alternativeChain = new ArrayList<>();
chainLoop:
for (final X509Certificate c : chain)
{
alternativeChain.add(c);
try
{
final X509Certificate issuer = findIssuer(c, currentDate);
if (issuer == null)
{
break;
}
else
{
foundAlternative = true;
alternativeChain.add(issuer);
X509Certificate prevIssuer = issuer;
while (true)
{
try
{
final X509Certificate nextIssuer =
findIssuer(prevIssuer, currentDate);
if (nextIssuer == null)
{
break chainLoop;
}
else
{
alternativeChain.add(nextIssuer);
prevIssuer = nextIssuer;
}
}
catch (final CertificateException e)
{
foundAlternative = false;
break chainLoop;
}
}
}
}
catch (final CertificateException e)
{
Debug.debugException(e);
}
}
if (foundAlternative)
{
return alternativeChain.toArray(NO_CERTIFICATES);
}
else
{
if (firstException == null)
{
throw new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANGER_NO_TRUSTED_ISSUER_FOUND.get(
chainToString(chain)));
}
else
{
throw firstException;
}
}
}
/**
* Finds the issuer for the provided certificate, if it is in the JVM-default
* trust store.
*
* @param cert The certificate for which to find the issuer. It must
* have already been retrieved from the JVM-default trust
* store.
* @param currentDate The current date to use when verifying validity.
*
* @return The issuer for the provided certificate, or {@code null} if the
* provided certificate is self-signed.
*
* @throws CertificateException If the provided certificate is not
* self-signed but its issuer could not be
* found, or if the issuer certificate is
* not currently valid.
*/
@Nullable()
private X509Certificate findIssuer(@NotNull final X509Certificate cert,
@NotNull final Date currentDate)
throws CertificateException
{
try
{
// More fully decode the provided certificate so that we can better
// examine it.
final com.unboundid.util.ssl.cert.X509Certificate c =
new com.unboundid.util.ssl.cert.X509Certificate(
cert.getEncoded());
// If the certificate is self-signed, then it doesn't have an issuer.
if (c.isSelfSigned())
{
return null;
}
// See if the certificate has an authority key identifier extension. If
// so, then use it to try to find the issuer.
for (final X509CertificateExtension e : c.getExtensions())
{
if (e instanceof AuthorityKeyIdentifierExtension)
{
final AuthorityKeyIdentifierExtension akie =
(AuthorityKeyIdentifierExtension) e;
final ASN1OctetString authorityKeyID =
new ASN1OctetString(akie.getKeyIdentifier().getValue());
final com.unboundid.util.ssl.cert.X509Certificate issuer =
trustedCertsByKeyID.get(authorityKeyID);
if ((issuer != null) && issuer.isWithinValidityWindow(currentDate))
{
c.verifySignature(issuer);
return (X509Certificate) issuer.toCertificate();
}
}
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
throw new CertificateException(
ERR_JVM_DEFAULT_TRUST_MANAGER_CANNOT_FIND_ISSUER.get(
String.valueOf(cert.getSubjectDN())));
}
/**
* Checks to determine whether the provided certificate chain may be
* incomplete, and if so, whether we can find and trust the issuer of the last
* certificate in the chain.
*
* @param chain The chain to validate.
*
* @return {@code true} if the chain could be validated, or {@code false} if
* not.
*/
private boolean checkIncompleteChain(@NotNull final X509Certificate[] chain)
{
try
{
// Get the last certificate in the chain and decode it as one that we can
// more fully inspect.
final com.unboundid.util.ssl.cert.X509Certificate c =
new com.unboundid.util.ssl.cert.X509Certificate(
chain[chain.length - 1].getEncoded());
// If the certificate is self-signed, then it can't be trusted.
if (c.isSelfSigned())
{
return false;
}
// See if the certificate has an authority key identifier extension. If
// so, then use it to try to find the issuer.
for (final X509CertificateExtension e : c.getExtensions())
{
if (e instanceof AuthorityKeyIdentifierExtension)
{
final AuthorityKeyIdentifierExtension akie =
(AuthorityKeyIdentifierExtension) e;
final ASN1OctetString authorityKeyID =
new ASN1OctetString(akie.getKeyIdentifier().getValue());
final com.unboundid.util.ssl.cert.X509Certificate issuer =
trustedCertsByKeyID.get(authorityKeyID);
if ((issuer != null) && issuer.isWithinValidityWindow())
{
c.verifySignature(issuer);
return true;
}
}
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
return false;
}
/**
* Constructs a string representation of the certificates in the provided
* chain. It will consist of a comma-delimited list of their subject DNs,
* with each subject DN surrounded by single quotes.
*
* @param chain The chain for which to obtain the string representation.
*
* @return A string representation of the provided certificate chain.
*/
@NotNull()
static String chainToString(@NotNull final X509Certificate[] chain)
{
final StringBuilder buffer = new StringBuilder();
switch (chain.length)
{
case 0:
break;
case 1:
buffer.append('\'');
buffer.append(chain[0].getSubjectDN());
buffer.append('\'');
break;
case 2:
buffer.append('\'');
buffer.append(chain[0].getSubjectDN());
buffer.append("' and '");
buffer.append(chain[1].getSubjectDN());
buffer.append('\'');
break;
default:
for (int i=0; i < chain.length; i++)
{
if (i > 0)
{
buffer.append(", ");
}
if (i == (chain.length - 1))
{
buffer.append("and ");
}
buffer.append('\'');
buffer.append(chain[i].getSubjectDN());
buffer.append('\'');
}
}
return buffer.toString();
}
}