
com.unboundid.util.ssl.PEMFileTrustManager Maven / Gradle / Ivy
/*
* Copyright 2021-2022 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2021-2022 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) 2021-2022 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.IOException;
import java.io.Serializable;
import java.security.KeyStoreException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import com.unboundid.util.ssl.cert.CertException;
import com.unboundid.util.ssl.cert.X509PEMFileReader;
import static com.unboundid.util.ssl.SSLMessages.*;
/**
* This class provides an implementation of an X.509 trust manager that can
* obtain information about trusted issuers from one or more PEM files.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class PEMFileTrustManager
implements X509TrustManager, Serializable
{
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 1973401278035832777L;
// The map of trusted certificates read from the PEM files.
@NotNull private final Map trustedCertificates;
/**
* Creates a new PEM file trust manager that will read trusted certificate
* information from the specified PEM files.
*
* @param pemFiles The PEM files from which to read the trusted certificate
* information. It must not be {@code null} or empty, and
* all files must exist. Each element may be a file (which
* may contain one or more PEM-formatted certificates) or a
* directory (in which case all of the files in that
* directory, including subdirectories will be recursively
* processed).
*
* @throws KeyStoreException If a problem occurs while trying to read or
* decode any of the certificates.
*/
public PEMFileTrustManager(@NotNull final File... pemFiles)
throws KeyStoreException
{
this(StaticUtils.toList(pemFiles));
}
/**
* Creates a new PEM file trust manager that will read trusted certificate
* information from the specified PEM files.
*
* @param pemFiles The PEM files from which to read the trusted certificate
* information. It must not be {@code null} or empty, and
* all files must exist. Each element may be a file (which
* may contain one or more PEM-formatted certificates) or a
* directory (in which case all of the files in that
* directory, including subdirectories will be recursively
* processed).
*
* @throws KeyStoreException If a problem occurs while trying to read or
* decode any of the certificates.
*/
public PEMFileTrustManager(@NotNull final List pemFiles)
throws KeyStoreException
{
Validator.ensureNotNullWithMessage(pemFiles,
"PEMFileTrustManager.pemFiles must not be null.");
Validator.ensureFalse(pemFiles.isEmpty(),
"PEMFileTrustManager.pemFiles must not be empty.");
final Map
certMap = new HashMap<>();
for (final File f : pemFiles)
{
readTrustedCertificates(f, certMap);
}
trustedCertificates = Collections.unmodifiableMap(certMap);
}
/**
* Reads trusted certificate information from the specified PEM file.
*
* @param f The PEM file to examine. It must not be {@code null}, and it
* must reference a file that exists. If it is a directory, then
* all files contained in it (including subdirectories) will be
* recursively processed.
* @param m The map to be updated wth the certificates read from the PEM
* files. It must not be {@code null} and must be updatable.
*
* @throws KeyStoreException If a problem is encountered while reading
* trusted certificate information from the
* specified file.
*/
private static void readTrustedCertificates(@NotNull final File f,
@NotNull final Map m)
throws KeyStoreException
{
if (! f.exists())
{
throw new KeyStoreException(
ERR_PEM_FILE_TRUST_MANAGER_NO_SUCH_FILE.get(f.getAbsolutePath()));
}
try
{
if (f.isDirectory())
{
for (final File fileInDir : f.listFiles())
{
readTrustedCertificates(fileInDir, m);
}
}
else
{
try (X509PEMFileReader r = new X509PEMFileReader(f))
{
boolean readCert = false;
while (true)
{
final com.unboundid.util.ssl.cert.X509Certificate cert =
r.readCertificate();
if (cert == null)
{
if (! readCert)
{
throw new KeyStoreException(
ERR_PEM_FILE_TRUST_MANAGER_EMPTY_FILE.get(
f.getAbsolutePath()));
}
break;
}
readCert = true;
final X509Certificate c = (X509Certificate) cert.toCertificate();
m.put(cert, c);
}
}
}
}
catch (final KeyStoreException e)
{
Debug.debugException(e);
throw e;
}
catch (final IOException e)
{
Debug.debugException(e);
throw new KeyStoreException(
ERR_PEM_FILE_TRUST_MANAGER_ERROR_READING_FILE.get(
f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
e);
}
catch (final CertException e)
{
Debug.debugException(e);
throw new KeyStoreException(
ERR_PEM_FILE_TRUST_MANAGER_ERROR_PARSING_CERT.get(
f.getAbsolutePath(), e.getMessage()),
e);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new KeyStoreException(
ERR_PEM_FILE_TRUST_MANAGER_ERROR_PROCESSING_FILE.get(
f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Determines whether the provided client certificate chain should be
* considered trusted based on the trusted certificate information read from
* PEM files.
*
* @param chain The client certificate chain for which to make the
* determination. It must not be {@code null} or empty.
* @param authType The type of authentication to use based on the client
* certificate. It must not be {@code null}.
*
* @throws CertificateException If the provided certificate chain should not
* be considered trusted.
*/
@Override()
public void checkClientTrusted(@NotNull final X509Certificate[] chain,
@NotNull final String authType)
throws CertificateException
{
try
{
checkTrusted(chain);
}
catch (final CertificateException e)
{
Debug.debugException(e);
throw new CertificateException(
ERR_PEM_FILE_TRUST_MANAGER_CLIENT_NOT_TRUSTED.get(e.getMessage()),
e);
}
}
/**
* Determines whether the provided server certificate chain should be
* considered trusted based on the trusted certificate information read from
* PEM files.
*
* @param chain The server certificate chain for which to make the
* determination. It must not be {@code null} or empty.
* @param authType The type of authentication to use based on the server
* certificate. It must not be {@code null}.
*
* @throws CertificateException If the provided certificate chain should not
* be considered trusted.
*/
@Override()
public void checkServerTrusted(@NotNull final X509Certificate[] chain,
@NotNull final String authType)
throws CertificateException
{
try
{
checkTrusted(chain);
}
catch (final CertificateException e)
{
Debug.debugException(e);
throw new CertificateException(
ERR_PEM_FILE_TRUST_MANAGER_SERVER_NOT_TRUSTED.get(e.getMessage()),
e);
}
}
/**
* Determines whether the provided certificate chain should be considered
* trusted based on the trusted certificate information read from PEM files.
* Note that this method assumes that the trusted certificate information read
* from PEM files should be authoritative, and therefore doesn't perform some
* types of validation (like ensuring that all issuer certificates are trusted
* rather than validating that at least one is trusted, or checking extensions
* like basic constraints).
*
* @param chain The certificate chain for which to make the determination.
* It must not be {@code null} or empty.
*
* @throws CertificateException If the provided certificate chain should not
* be considered trusted.
*/
private void checkTrusted(@NotNull final X509Certificate[] chain)
throws CertificateException
{
// If the chain is null or empty, then it cannot be trusted.
if ((chain == null) || (chain.length == 0))
{
throw new CertificateException(
ERR_PEM_FILE_TRUST_MANAGER_EMPTY_CHAIN.get());
}
// Iterate through all the certificates in the chain, parsing them using the
// LDAP SDK's X.509 certificate representation, and performing all of the
// following validation:
//
// - Make sure that the certificate is within the validity window.
//
// - Make sure that each subsequent certificate in the chain is the issuer
// for the previous certificate.
//
// - Check to see whether at least one of the certificates in the chain
// matches one read from the set of PEM files.
boolean foundCertificate = false;
com.unboundid.util.ssl.cert.X509Certificate firstCertificate = null;
com.unboundid.util.ssl.cert.X509Certificate previousCertificate = null;
for (final X509Certificate c : chain)
{
final com.unboundid.util.ssl.cert.X509Certificate parsedCertificate;
try
{
parsedCertificate = new com.unboundid.util.ssl.cert.X509Certificate(
c.getEncoded());
}
catch (final CertException e)
{
Debug.debugException(e);
throw new CertificateException(
ERR_PEM_FILE_TRUST_MANAGER_CANNOT_PARSE_CERT_FROM_CHAIN.get(
c.getSubjectX500Principal().getName(X500Principal.RFC2253),
StaticUtils.getExceptionMessage(e)),
e);
}
if (firstCertificate == null)
{
firstCertificate = parsedCertificate;
}
if (! parsedCertificate.isWithinValidityWindow())
{
throw new CertificateException(
ERR_PEM_FILE_TRUST_MANAGER_CERT_NOT_VALID.get(
String.valueOf(parsedCertificate.getSubjectDN()),
StaticUtils.encodeRFC3339Time(
parsedCertificate.getNotBeforeDate()),
StaticUtils.encodeRFC3339Time(
parsedCertificate.getNotAfterDate())));
}
if ((previousCertificate != null) &&
(! parsedCertificate.isIssuerFor(previousCertificate)))
{
throw new CertificateException(
ERR_PEM_FILE_TRUST_MANAGER_CERT_NOT_ISSUER.get(
String.valueOf(parsedCertificate.getSubjectDN()),
String.valueOf(previousCertificate.getSubjectDN())));
}
foundCertificate |= trustedCertificates.containsKey(parsedCertificate);
previousCertificate = parsedCertificate;
}
// If we didn't find any of the presented certificates in the trust store,
// then it may be that an incomplete chain was presented. If the last
// certificate in the chain is not self-signed, then check to see if any of
// the certificates in the trust store were an issuer for that certificate.
if ((! foundCertificate) && (! previousCertificate.isSelfSigned()))
{
for (final com.unboundid.util.ssl.cert.X509Certificate c :
trustedCertificates.keySet())
{
if (c.isIssuerFor(previousCertificate))
{
foundCertificate = true;
break;
}
}
}
if (! foundCertificate)
{
throw new CertificateException(ERR_PEM_FILE_TRUST_MANAGER_NOT_TRUSTED.get(
String.valueOf(firstCertificate.getSubjectDN())));
}
}
/**
* Retrieves an array of the issuer certificates that will be considered
* trusted.
*
* @return An array of the issuer certificates that will be considered
* trusted, or an empty array if no issuers will be trusted.
*/
@Override()
@NotNull()
public X509Certificate[] getAcceptedIssuers()
{
// Include all certificates that are currently within their validity window.
final long currentTime = System.currentTimeMillis();
final List certList =
new ArrayList<>(trustedCertificates.size());
for (final Map.Entry e : trustedCertificates.entrySet())
{
if (e.getKey().isWithinValidityWindow(currentTime))
{
certList.add(e.getValue());
}
}
final X509Certificate[] certArray = new X509Certificate[certList.size()];
return certList.toArray(certArray);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy