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

com.unboundid.util.json.SecurityOptions Maven / Gradle / Ivy

/*
 * Copyright 2015-2021 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2015-2021 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) 2015-2021 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.json;



import java.util.Arrays;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;

import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
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.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.ssl.AggregateTrustManager;
import com.unboundid.util.ssl.JVMDefaultTrustManager;
import com.unboundid.util.ssl.KeyStoreKeyManager;
import com.unboundid.util.ssl.PKCS11KeyManager;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;
import com.unboundid.util.ssl.TrustStoreTrustManager;

import static com.unboundid.util.json.JSONMessages.*;



/**
 * This class provides a data structure and set of logic for interacting with
 * the set of security options in a JSON object provided to the
 * {@link LDAPConnectionDetailsJSONSpecification}.
 */
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
final class SecurityOptions
{
  /**
   * The name of the field that specifies the alias of the client certificate to
   * use.  If this field is present, then it must be a string that matches the
   * nickname of a client certificate in the configured key store.  If it is
   * absent, then a client certificate will be automatically selected if
   * necessary.
   */
  @NotNull private static final String FIELD_CLIENT_CERT_ALIAS =
       "client-certificate-alias";



  /**
   * The name of the field that specifies the path to the key store file.  If
   * this field is present, then it must be a string that represents a valid
   * path to a key store file in a supported format.  If it is absent, then no
   * key store file will be used.
   */
  @NotNull private static final String FIELD_KEY_STORE_FILE = "key-store-file";



  /**
   * The name of the field that specifies the PIN to use when accessing the key
   * store.  If it is present, then it must be a string containing the PIN.  If
   * it is absent, then the PIN may be read from the file specified in the
   * key-store-pin-file field, or no PIN will be used if that field is not
   * present.  The key-store-pin and key-store-pin-file fields must not both be
   * present, and neither field may be present unless the key-store-file field
   * is present or the key-store-type field is present with a value of "PKCS11".
   */
  @NotNull private static final String FIELD_KEY_STORE_PIN = "key-store-pin";



  /**
   * The name of the field that specifies the path to a file containing the PIN
   * to use when accessing the key store.  If it is present, then it must be a
   * string that represents the path to a file containing the PIN.  If it is
   * absent, then the PIN may obtained from the key-store-pin field, or no PIN
   * will be used if that field is not present.  The key-store-pin and
   * key-store-pin-file fields must not both be present, and neither field may
   * be present unless the key-store-file field is present or the key-store-type
   * field is present with a value of "PKCS11".
   */
  @NotNull private static final String FIELD_KEY_STORE_PIN_FILE =
       "key-store-pin-file";



  /**
   * The name of the field that specifies the type of key store to use.  If it
   * is present, then the value must be a string with a value of "JKS",
   * "PKCS12", or "PKCS11".  If the value is "JKS" or "PKCS12", then the
   * key-store-file field must have been provided.  If the value is "PKCS11",
   * then the key-store-field field must not have been provided.  If it is
   * absent, then a default key store type of "JKS" will be assumed if a
   * key-store-file field is present.
   */
  @NotNull private static final String FIELD_KEY_STORE_TYPE = "key-store-type";



  /**
   * The name of the field that specifies the type of security to use.  If
   * present, the value must be a string whose value is "none" (for no
   * security), either "SSL" or "TLS" (for entirely-encrypted communication via
   * the Transport-Layer Security protocol), or "StartTLS" (to use the StartTLS
   * extended operation to encrypt communication over an initially-unencrypted
   * connection.  If this is not present, a default value of "none" will be
   * used.  If the value of "none" is used (or assumed as the default value),
   * then none of the other fields may be present.
   */
  @NotNull private static final String FIELD_SECURITY_TYPE = "security-type";



  /**
   * The name of the field that indicates whether to blindly trust all
   * certificates that servers may present.  This is convenient for testing
   * purposes, but is not recommended for production use because it does not
   * provide any protection against man-in-the-middle attacks.  If present, the
   * value should be a boolean, and if the value is {@code true} then the
   * trust-jvm-default-issuers, trust-store-file, trust-store-pin,
   * trust-store-pin-file, and trust-store-type fields must not be provided.  If
   * it is absent, then a default of {@code false} will be used.
   */
  @NotNull private static final String FIELD_TRUST_ALL_CERTS =
       "trust-all-certificates";



  /**
   * The name of the field that indicates whether to trust certificates that are
   * outside their validity period, whether the current time is earlier than the
   * "notBefore" time or later than the "notAfter" time.  If present, the value
   * should be a boolean, and if the value is {@code true} then certificate
   * validity time violations will be overlooked if the certificate would have
   * otherwise been accepted.  If it is absent, then a default of {@code false}
   * will be used.  Note that unless this field is present with a value of
   * {@code true}, then certificates will not be trusted outside of their
   * validity window even if the trust-all-certificates field is present with a
   * value of true.
   */
  @NotNull private static final String FIELD_TRUST_EXPIRED_CERTS =
       "trust-expired-certificates";



  /**
   * The name of the field that indicates whether to trust any certificate
   * signed by one of the JVM's default trusted issuers.  If present, the value
   * should be a boolean.
   */
  @NotNull private static final String FIELD_USE_JVM_DEFAULT_TRUST_STORE =
       "use-jvm-default-trust-store";



  /**
   * The name of the field that specifies the path to the trust store file.  If
   * provided, this should be a string that represents a path to a valid trust
   * store file in a recognized format.  If this is absent, then no trust store
   * will be accessed and the JVM's default trust mechanism will be used.
   */
  @NotNull private static final String FIELD_TRUST_STORE_FILE =
       "trust-store-file";



  /**
   * The name of the field that specifies the PIN to use when accessing the
   * trust store.  If it is present, then it must be a string containing the
   * PIN.  If it is absent, then the PIN may be read from the file specified in
   * the trust-store-pin-file field, or no PIN will be used if that field is not
   * present.  The trust-store-pin and trust-store-pin-file fields must not both
   * be present, and neither field may be present unless the trust-store-file
   * field is present.
   */
  @NotNull private static final String FIELD_TRUST_STORE_PIN =
       "trust-store-pin";



  /**
   * The name of the field that specifies a file containing the PIN to use when
   * accessing the trust store.  If it is present, then it must be a string
   * containing the path to a file containing the PIN.  If it is absent, then
   * the PIN may be specified in the trust-store-pin field, or no PIN will be
   * used if that field is not present.  The trust-store-pin and
   * trust-store-pin-file fields must not both be present, and neither field may
   * be present unless the trust-store-file field is present.
   */
  @NotNull private static final String FIELD_TRUST_STORE_PIN_FILE =
       "trust-store-pin-file";



  /**
   * The name of the field that specifies the type of trust store to use.  If it
   * is present, then it must be a string with a value of either "JKS" or
   * "PKCS12".  If it is absent, then a default trust store type of "JKS" will
   * be used if the trust-store-file field is present.
   */
  @NotNull private static final String FIELD_TRUST_STORE_TYPE =
       "trust-store-type";



  /**
   * The name of the field that indicates whether to verify the address
   * contained in the certificate.  If present, the value of this field must be
   * a boolean, and a value of {@code true} indicates that either the address
   * used to connect to the server must match the value of the CN attribute
   * in the certificate subject, or address used to connect to the server must
   * be present in a dNSName, uniformResourceIdentifier, or iPAddress
   * subjectAltName extension in the certificate.  If it is not present, a
   * default value of {@code false} will be used.
   */
  @NotNull private static final String FIELD_VERIFY_ADDRESS =
       "verify-address-in-certificate";



  // Indicates whether to verify certificate addresses.
  private final boolean verifyAddressInCertificate;

  // The socket factory to use when creating connections.
  @NotNull private final SocketFactory socketFactory;

  // The post-connect processor to use if StartTLS-protected connections are to
  // be used in a connection pool.
  @Nullable private final StartTLSPostConnectProcessor postConnectProcessor;



  /**
   * Creates a new set of security options from the information contained in
   * the provided JSON object.
   *
   * @param  connectionDetailsObject  The JSON object containing the LDAP
   *                                  connection details specification.
   *
   * @throws  LDAPException  If there is a problem with the security options
   *                         data in the provided JSON object.
   */
  SecurityOptions(@NotNull final JSONObject connectionDetailsObject)
       throws LDAPException
  {
    boolean useSSL = false;
    boolean useStartTLS = false;
    boolean trustAll = false;
    boolean trustExpired = false;
    boolean useJVMDefaultTrustStore = false;
    boolean verifyAddress = false;
    String  certAlias = null;
    String  keyStoreFile = null;
    String  keyStorePIN = null;
    String  keyStoreType = null;
    String  trustStoreFile = null;
    String  trustStorePIN = null;
    String  trustStoreType = null;

    final JSONObject o = LDAPConnectionDetailsJSONSpecification.getObject(
         connectionDetailsObject,
         LDAPConnectionDetailsJSONSpecification.FIELD_COMMUNICATION_SECURITY);
    if (o != null)
    {
      LDAPConnectionDetailsJSONSpecification.validateAllowedFields(o,
           LDAPConnectionDetailsJSONSpecification.FIELD_COMMUNICATION_SECURITY,
           FIELD_CLIENT_CERT_ALIAS,
           FIELD_KEY_STORE_FILE,
           FIELD_KEY_STORE_PIN,
           FIELD_KEY_STORE_PIN_FILE,
           FIELD_KEY_STORE_TYPE,
           FIELD_SECURITY_TYPE,
           FIELD_TRUST_ALL_CERTS,
           FIELD_TRUST_EXPIRED_CERTS,
           FIELD_TRUST_STORE_FILE,
           FIELD_TRUST_STORE_PIN,
           FIELD_TRUST_STORE_PIN_FILE,
           FIELD_TRUST_STORE_TYPE,
           FIELD_USE_JVM_DEFAULT_TRUST_STORE,
           FIELD_VERIFY_ADDRESS);

      final String type = StaticUtils.toLowerCase(
           LDAPConnectionDetailsJSONSpecification.getString(o,
                FIELD_SECURITY_TYPE, null));
      if (type == null)
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SECURITY_OPTIONS_MISSING_SECURITY_TYPE.get(
                  FIELD_SECURITY_TYPE));
      }
      else if (type.equals("none"))
      {
        if (o.getFields().size() > 1)
        {
          throw new LDAPException(ResultCode.PARAM_ERROR,
               ERR_SECURITY_OPTIONS_INVALID_FIELD_WITH_NONE.get(
                    FIELD_SECURITY_TYPE));
        }
      }
      else if (type.equals("ssl") || type.equals("tls"))
      {
        useSSL = true;
      }
      else if (type.equals("starttls") || type.equals("start-tls"))
      {
        useStartTLS = true;
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SECURITY_OPTIONS_INVALID_TYPE.get(FIELD_SECURITY_TYPE));
      }

      trustExpired = LDAPConnectionDetailsJSONSpecification.getBoolean(o,
           FIELD_TRUST_EXPIRED_CERTS, false);

      trustAll = LDAPConnectionDetailsJSONSpecification.getBoolean(o,
           FIELD_TRUST_ALL_CERTS, false);
      if (trustAll)
      {
        LDAPConnectionDetailsJSONSpecification.rejectConflictingFields(o,
             FIELD_TRUST_ALL_CERTS,
             FIELD_TRUST_STORE_FILE,
             FIELD_TRUST_STORE_PIN,
             FIELD_TRUST_STORE_PIN_FILE,
             FIELD_TRUST_STORE_TYPE,
             FIELD_USE_JVM_DEFAULT_TRUST_STORE);
      }
      else
      {
        trustStoreFile = LDAPConnectionDetailsJSONSpecification.getString(o,
             FIELD_TRUST_STORE_FILE, null);
        if (trustStoreFile == null)
        {
          LDAPConnectionDetailsJSONSpecification.rejectUnresolvedDependency(o,
               FIELD_TRUST_STORE_FILE,
               FIELD_TRUST_STORE_PIN,
               FIELD_TRUST_STORE_PIN_FILE,
               FIELD_TRUST_STORE_TYPE);
        }
        else
        {
          trustStoreType = LDAPConnectionDetailsJSONSpecification.getString(o,
               FIELD_TRUST_STORE_TYPE,
               CryptoHelper.KEY_STORE_TYPE_JKS).toUpperCase();
          if (! (trustStoreType.equals(CryptoHelper.KEY_STORE_TYPE_JKS) ||
                 trustStoreType.equals(CryptoHelper.KEY_STORE_TYPE_PKCS_12)))
          {
            throw new LDAPException(ResultCode.PARAM_ERROR,
                 ERR_SECURITY_OPTIONS_INVALID_TS_TYPE.get(
                      FIELD_TRUST_STORE_TYPE, trustStoreType));
          }

          trustStorePIN = LDAPConnectionDetailsJSONSpecification.getString(o,
               FIELD_TRUST_STORE_PIN, null);
          if (trustStorePIN == null)
          {
            final String trustStorePINFile =
                 LDAPConnectionDetailsJSONSpecification.getString(o,
                      FIELD_TRUST_STORE_PIN_FILE, null);
            if (trustStorePINFile != null)
            {
              trustStorePIN =
                   LDAPConnectionDetailsJSONSpecification.getStringFromFile(
                        trustStorePINFile, FIELD_TRUST_STORE_PIN_FILE);
            }
          }
          else
          {
            LDAPConnectionDetailsJSONSpecification.rejectConflictingFields(o,
                 FIELD_TRUST_STORE_PIN,
                 FIELD_TRUST_STORE_PIN_FILE);
          }
        }

        useJVMDefaultTrustStore =
             LDAPConnectionDetailsJSONSpecification.getBoolean(o,
                  FIELD_USE_JVM_DEFAULT_TRUST_STORE, false);
      }

      verifyAddress = LDAPConnectionDetailsJSONSpecification.getBoolean(o,
           FIELD_VERIFY_ADDRESS, verifyAddress);

      boolean useKeyStore = false;
      keyStoreFile = LDAPConnectionDetailsJSONSpecification.getString(o,
           FIELD_KEY_STORE_FILE, keyStoreFile);
      if (keyStoreFile != null)
      {
        useKeyStore = true;
        keyStoreType = LDAPConnectionDetailsJSONSpecification.getString(o,
             FIELD_KEY_STORE_TYPE,
             CryptoHelper.KEY_STORE_TYPE_JKS).toUpperCase();
        if (! (keyStoreType.equals(CryptoHelper.KEY_STORE_TYPE_JKS) ||
             keyStoreType.equals(CryptoHelper.KEY_STORE_TYPE_PKCS_12)))
        {
          throw new LDAPException(ResultCode.PARAM_ERROR,
               ERR_SECURITY_OPTIONS_INVALID_KS_TYPE_WITH_FILE.get(
                    FIELD_KEY_STORE_TYPE, keyStoreType));

        }
      }
      else
      {
        keyStoreType = LDAPConnectionDetailsJSONSpecification.getString(o,
             FIELD_KEY_STORE_TYPE, null);
        if (keyStoreType != null)
        {
          useKeyStore = true;
          keyStoreType = keyStoreType.toUpperCase();
          if (! keyStoreType.equals("PKCS11"))
          {
            throw new LDAPException(ResultCode.PARAM_ERROR,
                 ERR_SECURITY_OPTIONS_INVALID_KS_TYPE_WITHOUT_FILE.get(
                      FIELD_KEY_STORE_TYPE, keyStoreType,
                      FIELD_KEY_STORE_FILE));
          }
        }
      }

      if (useKeyStore)
      {
        certAlias = LDAPConnectionDetailsJSONSpecification.getString(o,
             FIELD_CLIENT_CERT_ALIAS, null);

        keyStorePIN = LDAPConnectionDetailsJSONSpecification.getString(o,
             FIELD_KEY_STORE_PIN, null);
        if (keyStorePIN == null)
        {
          final String keyStorePINFile =
               LDAPConnectionDetailsJSONSpecification.getString(o,
                    FIELD_KEY_STORE_PIN_FILE, null);
          if (keyStorePINFile != null)
          {
            keyStorePIN =
                 LDAPConnectionDetailsJSONSpecification.getStringFromFile(
                      keyStorePINFile, FIELD_KEY_STORE_PIN_FILE);
          }
        }
        else
        {
          LDAPConnectionDetailsJSONSpecification.rejectConflictingFields(o,
               FIELD_KEY_STORE_PIN, FIELD_KEY_STORE_PIN_FILE);
        }
      }
      else
      {
        for (final String fieldName :
             Arrays.asList(FIELD_KEY_STORE_PIN, FIELD_KEY_STORE_PIN_FILE,
                  FIELD_CLIENT_CERT_ALIAS))
        {
          if (o.getField(fieldName) != null)
          {
            throw new LDAPException(ResultCode.PARAM_ERROR,
                 ERR_SECURITY_OPTIONS_INVALID_FIELD_WITHOUT_KS.get(fieldName));
          }
        }
      }
    }

    verifyAddressInCertificate = verifyAddress;

    if (useSSL || useStartTLS)
    {
      final TrustManager trustManager;
      try
      {
        if (trustAll)
        {
          trustManager = new TrustAllTrustManager(! trustExpired);
        }
        else
        {
          if (trustStoreFile == null)
          {
            if (useJVMDefaultTrustStore)
            {
              trustManager = JVMDefaultTrustManager.getInstance();
            }
            else
            {
              trustManager = null;
            }
          }
          else
          {
            final char[] trustStorePINArray;
            if (trustStorePIN == null)
            {
              trustStorePINArray = null;
            }
            else
            {
              trustStorePINArray = trustStorePIN.toCharArray();
            }

            final TrustStoreTrustManager trustStoreTrustManager =
                 new TrustStoreTrustManager(trustStoreFile, trustStorePINArray,
                      trustStoreType, ! trustExpired);
            if (useJVMDefaultTrustStore)
            {
              trustManager = new AggregateTrustManager(false,
                   trustStoreTrustManager,
                   JVMDefaultTrustManager.getInstance());
            }
            else
            {
              trustManager = trustStoreTrustManager;
            }
          }
        }
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
        throw new LDAPException(ResultCode.LOCAL_ERROR,
             ERR_SECURITY_OPTIONS_CANNOT_CREATE_TRUST_MANAGER.get(
                  StaticUtils.getExceptionMessage(e)),
             e);
      }

      final KeyManager keyManager;
      try
      {
        final char[] keyStorePINArray;
        if (keyStorePIN == null)
        {
          keyStorePINArray = null;
        }
        else
        {
          keyStorePINArray = keyStorePIN.toCharArray();
        }

        if (keyStoreFile != null)
        {
          keyManager = new KeyStoreKeyManager(keyStoreFile, keyStorePINArray,
               keyStoreType, certAlias);
        }
        else if ((keyStoreType != null) && keyStoreType.equals("PKCS11"))
        {
          keyManager = new PKCS11KeyManager(keyStorePINArray, certAlias);
        }
        else
        {
          keyManager = null;
        }
      }
      catch (final Exception e)
      {
        Debug.debugException(e);
        throw new LDAPException(ResultCode.LOCAL_ERROR,
             ERR_SECURITY_OPTIONS_CANNOT_CREATE_KEY_MANAGER.get(
                  StaticUtils.getExceptionMessage(e)),
             e);
      }

      final SSLUtil sslUtil = new SSLUtil(keyManager, trustManager);
      if (useSSL)
      {
        try
        {
          socketFactory = sslUtil.createSSLSocketFactory();
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
          throw new LDAPException(ResultCode.LOCAL_ERROR,
               ERR_SECURITY_OPTIONS_CANNOT_CREATE_SOCKET_FACTORY.get(
                    StaticUtils.getExceptionMessage(e)),
               e);
        }

        postConnectProcessor = null;
      }
      else
      {
        socketFactory = SocketFactory.getDefault();

        try
        {
          postConnectProcessor = new StartTLSPostConnectProcessor(
               sslUtil.createSSLSocketFactory());
        }
        catch (final Exception e)
        {
          Debug.debugException(e);
          throw new LDAPException(ResultCode.LOCAL_ERROR,
               ERR_SECURITY_OPTIONS_CANNOT_CREATE_POST_CONNECT_PROCESSOR.get(
                    StaticUtils.getExceptionMessage(e)),
               e);
        }
      }
    }
    else
    {
      socketFactory = SocketFactory.getDefault();
      postConnectProcessor = null;
    }
  }



  /**
   * Indicates whether to verify server addresses against certificates.
   *
   * @return  Whether to verify server addresses against certificates.
   */
  boolean verifyAddressInCertificate()
  {
    return verifyAddressInCertificate;
  }



  /**
   * Retrieves the socket factory to use when establishing connections.
   *
   * @return  The socket factory to use when establishing connections.
   */
  @NotNull()
  SocketFactory getSocketFactory()
  {
    return socketFactory;
  }



  /**
   * Retrieves the StartTLS post-connect processor to use with a connection
   * pool.
   *
   * @return  The StartTLS post-connect processor to use with a connection pool.
   */
  @Nullable()
  StartTLSPostConnectProcessor getPostConnectProcessor()
  {
    return postConnectProcessor;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy