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

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

/*
 * Copyright 2019-2020 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2019-2020 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) 2019-2020 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.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;

import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPRuntimeException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.util.CommandLineTool;
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.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;

import static com.unboundid.util.ssl.SSLMessages.*;



/**
 * This class provides a utility for selecting the cipher suites that should be
 * supported for TLS communication.  The logic used to select the recommended
 * TLS cipher suites is as follows:
 * 
    *
  • * Only cipher suites that use the TLS protocol will be recommended. Legacy * SSL suites will not be recommended, nor will any suites that use an * unrecognized protocol. *
  • * *
  • * Any cipher suite that uses a NULL key exchange, authentication, bulk * encryption, or digest algorithm will not be recommended. *
  • * *
  • * Any cipher suite that uses anonymous authentication will not be * recommended. *
  • * *
  • * Any cipher suite that uses weakened export-grade encryption will not be * recommended. *
  • * *
  • * Only cipher suites that use ECDHE, DHE, or RSA key exchange algorithms * will be recommended. Other key agreement algorithms, including ECDH, * DH, and KRB5, will not be recommended. Cipher suites that use a * pre-shared key or password will not be recommended. *
  • * *
  • * Only cipher suites that use AES or ChaCha20 bulk encryption ciphers will * be recommended. Other bulk cipher algorithms, including RC4, DES, 3DES, * IDEA, Camellia, and ARIA, will not be recommended. *
  • * *
  • * Only cipher suites that use SHA-1 or SHA-2 digests will be recommended * (although SHA-1 digests are de-prioritized). Other digest algorithms, * like MD5, will not be recommended. *
  • *
*

* Also note that this class can be used as a command-line tool for debugging * purposes. */ @NotMutable() @ThreadSafety(level= ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class TLSCipherSuiteSelector extends CommandLineTool { /** * The singleton instance of this TLS cipher suite selector. */ @NotNull private static final TLSCipherSuiteSelector INSTANCE = new TLSCipherSuiteSelector(); // Retrieves a map of the supported cipher suites that are not recommended // for use, mapped to a list of the reasons that the cipher suites are not // recommended. @NotNull private final SortedMap> nonRecommendedCipherSuites; // The set of TLS cipher suites enabled in the JVM by default, sorted in // order of most preferred to least preferred. @NotNull private final SortedSet defaultCipherSuites; // The recommended set of TLS cipher suites selected by this class, sorted in // order of most preferred to least preferred. @NotNull private final SortedSet recommendedCipherSuites; // The full set of TLS cipher suites supported in the JVM, sorted in order of // most preferred to least preferred. @NotNull private final SortedSet supportedCipherSuites; // The recommended set of TLS cipher suites as an array rather than a set. @NotNull private final String[] recommendedCipherSuiteArray; /** * Invokes this command-line program with the provided set of arguments. * * @param args The command-line arguments provided to this program. */ public static void main(@NotNull final String... args) { final ResultCode resultCode = main(System.out, System.err, args); if (resultCode != ResultCode.SUCCESS) { System.exit(resultCode.intValue()); } } /** * Invokes this command-line program with the provided set of arguments. * * @param out The output stream to use for standard output. It may be * {@code null} if standard output should be suppressed. * @param err The output stream to use for standard error. It may be * {@code null} if standard error should be suppressed. * @param args The command-line arguments provided to this program. * * @return A result code that indicates whether the processing was * successful. */ @NotNull() public static ResultCode main(@Nullable final OutputStream out, @Nullable final OutputStream err, @NotNull final String... args) { final TLSCipherSuiteSelector tool = new TLSCipherSuiteSelector(out, err); return tool.runTool(args); } /** * Creates a new instance of this TLS cipher suite selector that will suppress * all output. */ private TLSCipherSuiteSelector() { this(null, null); } /** * Creates a new instance of this TLS cipher suite selector that will use the * provided output streams. Note that this constructor should only be used * when invoking it as a command-line tool. * * @param out The output stream to use for standard output. It may be * {@code null} if standard output should be suppressed. * @param err The output stream to use for standard error. It may be * {@code null} if standard error should be suppressed. */ public TLSCipherSuiteSelector(@Nullable final OutputStream out, @Nullable final OutputStream err) { super(out, err); try { final SSLContext sslContext = SSLContext.getDefault(); final SSLParameters supportedParameters = sslContext.getSupportedSSLParameters(); final TreeSet supportedSet = new TreeSet<>(TLSCipherSuiteComparator.getInstance()); supportedSet.addAll(Arrays.asList(supportedParameters.getCipherSuites())); supportedCipherSuites = Collections.unmodifiableSortedSet(supportedSet); final SSLParameters defaultParameters = sslContext.getDefaultSSLParameters(); final TreeSet defaultSet = new TreeSet<>(TLSCipherSuiteComparator.getInstance()); defaultSet.addAll(Arrays.asList(defaultParameters.getCipherSuites())); defaultCipherSuites = Collections.unmodifiableSortedSet(supportedSet); final ObjectPair,SortedMap>> selectedPair = selectCipherSuites( supportedParameters.getCipherSuites()); recommendedCipherSuites = Collections.unmodifiableSortedSet(selectedPair.getFirst()); nonRecommendedCipherSuites = Collections.unmodifiableSortedMap(selectedPair.getSecond()); recommendedCipherSuiteArray = recommendedCipherSuites.toArray(StaticUtils.NO_STRINGS); } catch (final Exception e) { Debug.debugException(e); // This should never happen. throw new LDAPRuntimeException(new LDAPException(ResultCode.LOCAL_ERROR, ERR_TLS_CIPHER_SUITE_SELECTOR_INIT_ERROR.get( StaticUtils.getExceptionMessage(e)), e)); } // If the JVM's TLS debugging support is enabled, then invoke the tool // and send its output to standard error. final String debugProperty = StaticUtils.getSystemProperty("javax.net.debug"); if ((debugProperty != null) && debugProperty.equals("all")) { System.err.println(); System.err.println(getClass().getName() + " Results:"); generateOutput(System.err); System.err.println(); } } /** * Retrieves the set of all TLS cipher suites supported by the JVM. The set * will be sorted in order of most preferred to least preferred, as determined * by the {@link TLSCipherSuiteComparator}. * * @return The set of all TLS cipher suites supported by the JVM. */ @NotNull() public static SortedSet getSupportedCipherSuites() { return INSTANCE.supportedCipherSuites; } /** * Retrieves the set of TLS cipher suites enabled by default in the JVM. The * set will be sorted in order of most preferred to least preferred, as * determined by the {@link TLSCipherSuiteComparator}. * * @return The set of TLS cipher suites enabled by default in the JVM. */ @NotNull() public static SortedSet getDefaultCipherSuites() { return INSTANCE.defaultCipherSuites; } /** * Retrieves the recommended set of TLS cipher suites as selected by this * class. The set will be sorted in order of most preferred to least * preferred, as determined by the {@link TLSCipherSuiteComparator}. * * @return The recommended set of TLS cipher suites as selected by this * class. */ @NotNull() public static SortedSet getRecommendedCipherSuites() { return INSTANCE.recommendedCipherSuites; } /** * Retrieves an array containing the recommended set of TLS cipher suites as * selected by this class. The array will be sorted in order of most * preferred to least preferred, as determined by the * {@link TLSCipherSuiteComparator}. * * @return An array containing the recommended set of TLS cipher suites as * selected by this class. */ @NotNull() public static String[] getRecommendedCipherSuiteArray() { return INSTANCE.recommendedCipherSuiteArray.clone(); } /** * Retrieves a map containing the TLS cipher suites that are supported by the * JVM but are not recommended for use. The keys of the map will be the names * of the non-recommended cipher suites, sorted in order of most preferred to * least preferred, as determined by the {@link TLSCipherSuiteComparator}. * Each TLS cipher suite name will be mapped to a list of the reasons it is * not recommended for use. * * @return A map containing the TLS cipher suites that are supported by the * JVM but are not recommended for use */ @NotNull() public static SortedMap> getNonRecommendedCipherSuites() { return INSTANCE.nonRecommendedCipherSuites; } /** * Organizes the provided set of cipher suites into recommended and * non-recommended sets. * * @param cipherSuiteArray An array of the cipher suites to be organized. * * @return An object pair in which the first element is the sorted set of * recommended cipher suites, and the second element is the sorted * map of non-recommended cipher suites and the reasons they are not * recommended for use. */ @NotNull() static ObjectPair,SortedMap>> selectCipherSuites(@NotNull final String[] cipherSuiteArray) { final SortedSet recommendedSet = new TreeSet<>(TLSCipherSuiteComparator.getInstance()); final SortedMap> nonRecommendedMap = new TreeMap<>(TLSCipherSuiteComparator.getInstance()); for (final String cipherSuiteName : cipherSuiteArray) { final String name = StaticUtils.toUpperCase(cipherSuiteName).replace('-', '_'); // Signalling cipher suite values (which indicate capabilities of the // implementation and aren't really cipher suites on their own) will // always be accepted. if (name.endsWith("_SCSV")) { recommendedSet.add(cipherSuiteName); continue; } // Only cipher suites using the TLS protocol will be accepted. final List nonRecommendedReasons = new ArrayList<>(5); if (name.startsWith("SSL_")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_LEGACY_SSL_PROTOCOL.get()); } else if (name.startsWith("TLS_")) { // Only TLS cipher suites using a recommended key exchange algorithm // will be accepted. if (name.startsWith("TLS_AES_") || name.startsWith("TLS_CHACHA20_") || name.startsWith("TLS_ECDHE_") || name.startsWith("TLS_DHE_") || name.startsWith("TLS_RSA_")) { // These are recommended key exchange algorithms. } else if (name.startsWith("TLS_ECDH_")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get( "ECDH")); } else if (name.startsWith("TLS_DH_")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get( "DH")); } else if (name.startsWith("TLS_KRB5_")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get( "KRB5")); } else { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_UNKNOWN_KE_ALG. get()); } } else { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_UNRECOGNIZED_PROTOCOL.get()); } // Cipher suites that rely on pre-shared keys will not be accepted. if (name.contains("_PSK")) { nonRecommendedReasons.add(ERR_TLS_CIPHER_SUITE_SELECTOR_PSK.get()); } // Cipher suites that use a null component will not be accepted. if (name.contains("_NULL")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NULL_COMPONENT.get()); } // Cipher suites that use anonymous authentication will not be accepted. if (name.contains("_ANON")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_ANON_AUTH.get()); } // Cipher suites that use export-grade encryption will not be accepted. if (name.contains("_EXPORT")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_EXPORT_ENCRYPTION.get()); } // Only cipher suites that use AES or ChaCha20 will be accepted. if (name.contains("_AES") || name.contains("_CHACHA20")) { // These are recommended bulk cipher algorithms. } else if (name.contains("_RC4")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get( "RC4")); } else if (name.contains("_3DES")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get( "3DES")); } else if (name.contains("_DES")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get( "DES")); } else if (name.contains("_IDEA")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get( "IDEA")); } else if (name.contains("_CAMELLIA")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get( "Camellia")); } else if (name.contains("_ARIA")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get( "ARIA")); } else { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_UNKNOWN_BE_ALG. get()); } // Only cipher suites that use a SHA-1 or SHA-2 digest algorithm will be // accepted. if (name.endsWith("_SHA512") || name.endsWith("_SHA384") || name.endsWith("_SHA256") || name.endsWith("_SHA")) { // These are recommended digest algorithms. } else if (name.endsWith("_MD5")) { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_DIGEST_ALG.get( "MD5")); } else { nonRecommendedReasons.add( ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_UNKNOWN_DIGEST_ALG. get()); } // Determine whether to recommend the cipher suite based on whether there // are any non-recommended reasons. if (nonRecommendedReasons.isEmpty()) { recommendedSet.add(cipherSuiteName); } else { nonRecommendedMap.put(cipherSuiteName, Collections.unmodifiableList(nonRecommendedReasons)); } } return new ObjectPair<>(recommendedSet, nonRecommendedMap); } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolName() { return "tls-cipher-suite-selector"; } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolDescription() { return INFO_TLS_CIPHER_SUITE_SELECTOR_TOOL_DESC.get(); } /** * {@inheritDoc} */ @Override() @NotNull() public String getToolVersion() { return Version.NUMERIC_VERSION_STRING; } /** * {@inheritDoc} */ @Override() public void addToolArguments(@NotNull final ArgumentParser parser) throws ArgumentException { // This tool does not require any arguments. } /** * {@inheritDoc} */ @Override() @NotNull() public ResultCode doToolProcessing() { generateOutput(getOut()); return ResultCode.SUCCESS; } /** * Writes the output to the provided print stream. * * @param s The print stream to which the output should be written. */ private void generateOutput(@NotNull final PrintStream s) { s.println("Supported TLS Cipher Suites:"); for (final String cipherSuite : supportedCipherSuites) { s.println("* " + cipherSuite); } s.println(); s.println("JVM-Default TLS Cipher Suites:"); for (final String cipherSuite : defaultCipherSuites) { s.println("* " + cipherSuite); } s.println(); s.println("Non-Recommended TLS Cipher Suites:"); for (final Map.Entry> e : nonRecommendedCipherSuites.entrySet()) { s.println("* " + e.getKey()); for (final String reason : e.getValue()) { s.println(" - " + reason); } } s.println(); s.println("Recommended TLS Cipher Suites:"); for (final String cipherSuite : recommendedCipherSuites) { s.println("* " + cipherSuite); } } /** * Filters the provided collection of potential cipher suite names to retrieve * a set of the suites that are supported by the JVM. * * @param potentialSuiteNames The collection of cipher suite names to be * filtered. * * @return The set of provided cipher suites that are supported by the JVM, * or an empty set if none of the potential provided suite names are * supported by the JVM. */ @NotNull() public static Set selectSupportedCipherSuites( @Nullable final Collection potentialSuiteNames) { if (potentialSuiteNames == null) { return Collections.emptySet(); } final int capacity = StaticUtils.computeMapCapacity(INSTANCE.supportedCipherSuites.size()); final Map supportedMap = new HashMap<>(capacity); for (final String supportedSuite : INSTANCE.supportedCipherSuites) { supportedMap.put( StaticUtils.toUpperCase(supportedSuite).replace('-', '_'), supportedSuite); } final Set selectedSet = new LinkedHashSet<>(capacity); for (final String potentialSuite : potentialSuiteNames) { final String supportedName = supportedMap.get( StaticUtils.toUpperCase(potentialSuite).replace('-', '_')); if (supportedName != null) { selectedSet.add(supportedName); } } return Collections.unmodifiableSet(selectedSet); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy