All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.ssl.PEMFileTrustManager Maven / Gradle / Ivy

Go to download

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.

The newest version!
/*
 * Copyright 2021-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2021-2024 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-2024 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 - 2024 Weber Informatics LLC | Privacy Policy