001/*
002 * Copyright 2019-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2019-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2019-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util.ssl;
037
038
039
040import java.io.OutputStream;
041import java.io.PrintStream;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collection;
045import java.util.Collections;
046import java.util.HashMap;
047import java.util.LinkedHashSet;
048import java.util.List;
049import java.util.Map;
050import java.util.Set;
051import java.util.SortedMap;
052import java.util.SortedSet;
053import java.util.TreeMap;
054import java.util.TreeSet;
055import java.util.concurrent.atomic.AtomicBoolean;
056import java.util.concurrent.atomic.AtomicReference;
057import javax.net.ssl.SSLContext;
058import javax.net.ssl.SSLParameters;
059
060import com.unboundid.ldap.sdk.LDAPException;
061import com.unboundid.ldap.sdk.LDAPRuntimeException;
062import com.unboundid.ldap.sdk.ResultCode;
063import com.unboundid.ldap.sdk.Version;
064import com.unboundid.util.CommandLineTool;
065import com.unboundid.util.CryptoHelper;
066import com.unboundid.util.Debug;
067import com.unboundid.util.NotMutable;
068import com.unboundid.util.NotNull;
069import com.unboundid.util.Nullable;
070import com.unboundid.util.ObjectPair;
071import com.unboundid.util.PropertyManager;
072import com.unboundid.util.StaticUtils;
073import com.unboundid.util.ThreadSafety;
074import com.unboundid.util.ThreadSafetyLevel;
075import com.unboundid.util.args.ArgumentException;
076import com.unboundid.util.args.ArgumentParser;
077
078import static com.unboundid.util.ssl.SSLMessages.*;
079
080
081
082/**
083 * This class provides a utility for selecting the cipher suites that should be
084 * supported for TLS communication.  The logic used to select the recommended
085 * TLS cipher suites is as follows:
086 * <UL>
087 *   <LI>
088 *     In most JVMs, only cipher suites that use the TLS protocol will be
089 *     recommended (that is, suites that use a prefix of "TLS_"), and legacy SSL
090 *     suites (those that use a prefix of "SSL_") will not be recommended, nor
091 *     will any suites that use an unrecognized protocol.  Note that this
092 *     restriction will not be enforced for JVMs in which the vendor string
093 *     contains "IBM" (as they tend to use "SSL_" prefixes for most or all
094 *     cipher suites), or if the {@link #PROPERTY_ALLOW_SSL_PREFIX} system
095 *     property is set to {@code true}.
096 *   </LI>
097 *
098 *   <LI>
099 *     Any cipher suite that uses a NULL key exchange, authentication, bulk
100 *     encryption, or digest algorithm will not be recommended.
101 *   </LI>
102 *
103 *   <LI>
104 *     Any cipher suite that uses anonymous authentication will not be
105 *     recommended.
106 *   </LI>
107 *
108 *   <LI>
109 *     Any cipher suite that uses weakened export-grade encryption will not be
110 *     recommended.
111 *   </LI>
112 *
113 *   <LI>
114 *     By default, only cipher suites that use the ECDHE or DHE key exchange
115 *     algorithms will be recommended, as they allow for forward secrecy.
116 *     Suites that use RSA key exchange algorithms (which don't support forward
117 *     secrecy) will only be recommended if the JVM doesn't support either
118 *     TLSv1.3 or TLSv1.2, or if overridden programmatically or by system
119 *     property.  Other key agreement algorithms (like ECDH, DH, and KRB5) will
120 *     not be recommended.  Similarly, cipher suites that use a pre-shared key
121 *     or password will not be recommended.
122 *   </LI>
123 *
124 *   <LI>
125 *     Only cipher suites that use AES or ChaCha20 bulk encryption ciphers will
126 *     be recommended.  Other bulk cipher algorithms (like RC4, DES, 3DES, IDEA,
127 *     Camellia, and ARIA) will not be recommended.
128 *   </LI>
129 *
130 *   <LI>
131 *     By default, only cipher suites that use SHA-2 digests will be
132 *     recommended.  SHA-1 suites will only be recommended if the JVM doesn't
133 *     support either TLSv1.3 or TLSv1.2, or if overridden programmatically or
134 *     by system property.  All other digest algorithms (like MD5) will not be
135 *     recommended.
136 *   </LI>
137 * </UL>
138 * <BR><BR>
139 * Also note that this class can be used as a command-line tool for debugging
140 * purposes.
141 */
142@NotMutable()
143@ThreadSafety(level= ThreadSafetyLevel.COMPLETELY_THREADSAFE)
144public final class TLSCipherSuiteSelector
145       extends CommandLineTool
146{
147  /**
148   * An instance of this TLS cipher suite selector that will be used by the
149   * static methods.
150   */
151  @NotNull private static final AtomicReference<TLSCipherSuiteSelector>
152       STATIC_INSTANCE = new AtomicReference<>();
153
154
155
156  /**
157   * The name of a system property
158   * (com.unboundid.util.ssl.TLSCipherSuiteSelector.allowRSAKeyExchange) that
159   * can be used to indicate whether to recommend cipher suites that use the RSA
160   * key exchange algorithm.  RSA key exchange does not support forward secrecy,
161   * so it will not be recommended by default unless the JVM doesn't support
162   * either TLSv1.3 or TLSv1.2.  This can be overridden via the
163   * {@link #setAllowRSAKeyExchange(boolean)} method.
164   */
165  @NotNull public static final String PROPERTY_ALLOW_RSA_KEY_EXCHANGE =
166       TLSCipherSuiteSelector.class.getName() + ".allowRSAKeyExchange";
167
168
169
170  /**
171   * The name of a system property
172   * (com.unboundid.util.ssl.TLSCipherSuiteSelector.allowSHA1) that can be used
173   * to indicate whether to recommend cipher suites that use the SHA-1 digest
174   * algorithm.  The SHA-1 digest is now considered weak, so it will not be
175   * recommended by default unless the JVM doesn't support either TLSv1.3 or
176   * TLSv1.2.  This can be overridden via the {@link #setAllowSHA1(boolean)}
177   * method.
178   */
179  @NotNull public static final String PROPERTY_ALLOW_SHA_1 =
180       TLSCipherSuiteSelector.class.getName() + ".allowSHA1";
181
182
183
184  /**
185   * The name of a system property
186   * (com.unboundid.util.ssl.TLSCipherSuiteSelector.allowSSLPrefix) that can be
187   * used to indicate whether to recommend cipher suites that use a prefix of
188   * "SSL_" rather than "TLS_".  If this is not specified, then the default
189   * behavior will be to disable all suites with an "SSL_" prefix for all
190   * non-IBM JVMs, but to allow them for JVMs in which the vendor string
191   * contains "IBM", as they are known to use "SSL_" prefixes even for suites
192   * that are only in use in conjunction with TLS protocols.
193   */
194  @NotNull public static final String PROPERTY_ALLOW_SSL_PREFIX =
195       TLSCipherSuiteSelector.class.getName() + ".allowSSLPrefix";
196
197
198
199  /**
200   * A flag that indicates whether to allow the RSA key exchange algorithm.
201   */
202  @NotNull private static final AtomicBoolean ALLOW_RSA_KEY_EXCHANGE =
203       new AtomicBoolean(false);
204
205
206
207  /**
208   * A flag that indicates whether to allow cipher suites that use the SHA-1
209   * digest algorithm.
210   */
211  @NotNull private static final AtomicBoolean ALLOW_SHA_1 =
212       new AtomicBoolean(false);
213
214
215
216  /**
217   * A flag that indicates whether to allow cipher suites that use a prefix of
218   * "SSL_".
219   */
220  @NotNull private static final AtomicBoolean ALLOW_SSL_PREFIX =
221       new AtomicBoolean(false);
222
223
224
225  static
226  {
227    final boolean allowRSA =
228         PropertyManager.getBoolean(PROPERTY_ALLOW_RSA_KEY_EXCHANGE, false);
229    final boolean allowSHA1 =
230         PropertyManager.getBoolean(PROPERTY_ALLOW_SHA_1, false);
231
232    final boolean allowSSLPrefix;
233    final String allowSSLPrefixPropertyValue =
234         PropertyManager.get(PROPERTY_ALLOW_SSL_PREFIX);
235    if (allowSSLPrefixPropertyValue != null)
236    {
237      allowSSLPrefix = allowSSLPrefixPropertyValue.equalsIgnoreCase("true");
238    }
239    else
240    {
241      final String javaVendorString =
242           StaticUtils.getSystemProperty("java.vendor");
243      final String jvmVendorString =
244           StaticUtils.getSystemProperty("java.vm.vendor");
245      if (((javaVendorString != null) &&
246              javaVendorString.toUpperCase().contains("IBM")) ||
247          ((jvmVendorString != null) &&
248              jvmVendorString.toUpperCase().contains("IBM")))
249      {
250        allowSSLPrefix = true;
251      }
252      else
253      {
254        allowSSLPrefix = false;
255      }
256    }
257
258    ALLOW_RSA_KEY_EXCHANGE.set(allowRSA);
259    ALLOW_SHA_1.set(allowSHA1);
260    ALLOW_SSL_PREFIX.set(allowSSLPrefix);
261  }
262
263
264
265  // Indicates whether SSL/TLS debugging is expected to be enabled in the JVM,
266  // based on the value of the javax.net.debug system property.
267  private final boolean jvmSSLDebuggingEnabled;
268
269  // Retrieves a map of the supported cipher suites that are not recommended
270  // for use, mapped to a list of the reasons that the cipher suites are not
271  // recommended.
272  @NotNull private final SortedMap<String,List<String>>
273       nonRecommendedCipherSuites;
274
275  // The set of TLS cipher suites enabled in the JVM by default, sorted in
276  // order of most preferred to least preferred.
277  @NotNull private final SortedSet<String> defaultCipherSuites;
278
279  // The recommended set of TLS cipher suites selected by this class, sorted in
280  // order of most preferred to least preferred.
281  @NotNull private final SortedSet<String> recommendedCipherSuites;
282
283  // The full set of TLS cipher suites supported in the JVM, sorted in order of
284  // most preferred to least preferred.
285  @NotNull private final SortedSet<String> supportedCipherSuites;
286
287  // The recommended set of TLS cipher suites as an array rather than a set.
288  @NotNull private final String[] recommendedCipherSuiteArray;
289
290
291
292  /**
293   * Invokes this command-line program with the provided set of arguments.
294   *
295   * @param  args  The command-line arguments provided to this program.
296   */
297  public static void main(@NotNull final String... args)
298  {
299    final ResultCode resultCode = main(System.out, System.err, args);
300    if (resultCode != ResultCode.SUCCESS)
301    {
302      System.exit(resultCode.intValue());
303    }
304  }
305
306
307
308  /**
309   * Invokes this command-line program with the provided set of arguments.
310   *
311   * @param  out   The output stream to use for standard output.  It may be
312   *               {@code null} if standard output should be suppressed.
313   * @param  err   The output stream to use for standard error.  It may be
314   *               {@code null} if standard error should be suppressed.
315   * @param  args  The command-line arguments provided to this program.
316   *
317   * @return  A result code that indicates whether the processing was
318   *          successful.
319   */
320  @NotNull()
321  public static ResultCode main(@Nullable final OutputStream out,
322                                @Nullable final OutputStream err,
323                                @NotNull final String... args)
324  {
325    final TLSCipherSuiteSelector tool = new TLSCipherSuiteSelector(out, err);
326    return tool.runTool(args);
327  }
328
329
330
331  /**
332   * Creates a new instance of this TLS cipher suite selector that will suppress
333   * all output.
334   *
335   * @param  useJVMDefaults  Indicates whether to use the JVM-default settings.
336   *                         This should only be {@code true} for the initial
337   *                         instance created before the static initializer has
338   *                         run.
339   */
340  private TLSCipherSuiteSelector(final boolean useJVMDefaults)
341  {
342    this(null, null, useJVMDefaults);
343  }
344
345
346
347
348  /**
349   * Creates a new instance of this TLS cipher suite selector that will use the
350   * provided output streams.  Note that this constructor should only be used
351   * when invoking it as a command-line tool.
352   *
353   * @param  out  The output stream to use for standard output.  It may be
354   *              {@code null} if standard output should be suppressed.
355   * @param  err  The output stream to use for standard error.  It may be
356   *              {@code null} if standard error should be suppressed.
357   */
358  public TLSCipherSuiteSelector(@Nullable final OutputStream out,
359                                @Nullable final OutputStream err)
360  {
361    this(out, err, false);
362  }
363
364
365
366
367  /**
368   * Creates a new instance of this TLS cipher suite selector that will use the
369   * provided output streams.  Note that this constructor should only be used
370   * when invoking it as a command-line tool.
371   *
372   * @param  out             The output stream to use for standard output.  It
373   *                         may be {@code null} if standard output should be
374   *                         suppressed.
375   * @param  err             The output stream to use for standard error.  It
376   *                         may be {@code null} if standard error should be
377   *                         suppressed.
378   * @param  useJVMDefaults  Indicates whether to use the JVM-default settings.
379   *                         This should only be {@code true} for the initial
380   *                         instance created before the static initializer has
381   *                         run.
382   */
383  public TLSCipherSuiteSelector(@Nullable final OutputStream out,
384                                @Nullable final OutputStream err,
385                                final boolean useJVMDefaults)
386  {
387    super(out, err);
388
389    try
390    {
391      final SSLContext sslContext;
392      if (useJVMDefaults)
393      {
394        sslContext = SSLContext.getDefault();
395      }
396      else
397      {
398        sslContext = CryptoHelper.getDefaultSSLContext();
399      }
400
401      final SSLParameters supportedParameters =
402           sslContext.getSupportedSSLParameters();
403      final TreeSet<String> supportedSet =
404           new TreeSet<>(TLSCipherSuiteComparator.getInstance());
405      supportedSet.addAll(Arrays.asList(supportedParameters.getCipherSuites()));
406      supportedCipherSuites = Collections.unmodifiableSortedSet(supportedSet);
407
408      final SSLParameters defaultParameters =
409           sslContext.getDefaultSSLParameters();
410      final TreeSet<String> defaultSet =
411           new TreeSet<>(TLSCipherSuiteComparator.getInstance());
412      defaultSet.addAll(Arrays.asList(defaultParameters.getCipherSuites()));
413      defaultCipherSuites = Collections.unmodifiableSortedSet(defaultSet);
414
415      if (useJVMDefaults)
416      {
417        recommendedCipherSuites = defaultCipherSuites;
418        nonRecommendedCipherSuites = Collections.unmodifiableSortedMap(
419             new TreeMap<String,List<String>>());
420      }
421      else
422      {
423        final ObjectPair<SortedSet<String>,SortedMap<String,List<String>>>
424             selectedPair = selectCipherSuites(
425             supportedParameters.getCipherSuites());
426        if (selectedPair.getFirst().isEmpty())
427        {
428          // We couldn't identify any recommended suites.  Just fall back on the
429          // JVM-default suites.
430          recommendedCipherSuites = defaultCipherSuites;
431          nonRecommendedCipherSuites = Collections.unmodifiableSortedMap(
432               new TreeMap<String,List<String>>());
433        }
434        else
435        {
436          recommendedCipherSuites =
437               Collections.unmodifiableSortedSet(selectedPair.getFirst());
438          nonRecommendedCipherSuites =
439               Collections.unmodifiableSortedMap(selectedPair.getSecond());
440        }
441      }
442
443      recommendedCipherSuiteArray =
444           recommendedCipherSuites.toArray(StaticUtils.NO_STRINGS);
445    }
446    catch (final Exception e)
447    {
448      Debug.debugException(e);
449
450      // This should never happen.
451      throw new LDAPRuntimeException(new LDAPException(ResultCode.LOCAL_ERROR,
452           ERR_TLS_CIPHER_SUITE_SELECTOR_INIT_ERROR.get(
453                StaticUtils.getExceptionMessage(e)),
454           e));
455    }
456
457
458    // See if the JVM's TLS debugging support is enabled. If so, then invoke the
459    // tool and send its output to standard error.
460    final String javaxNetDebugPropertyValue =
461         StaticUtils.getSystemProperty("javax.net.debug");
462    if (javaxNetDebugPropertyValue == null)
463    {
464      jvmSSLDebuggingEnabled = false;
465    }
466    else
467    {
468      final String lowerValue =
469           StaticUtils.toLowerCase(javaxNetDebugPropertyValue);
470      jvmSSLDebuggingEnabled =
471           (lowerValue.contains("all") || lowerValue.contains("ssl"));
472      if (jvmSSLDebuggingEnabled)
473      {
474        System.err.println();
475        System.err.println(getClass().getName() + " Results:");
476        generateOutput(System.err);
477        System.err.println();
478      }
479    }
480  }
481
482
483
484  /**
485   * Retrieves the set of all TLS cipher suites supported by the JVM.  The set
486   * will be sorted in order of most preferred to least preferred, as determined
487   * by the {@link TLSCipherSuiteComparator}.
488   *
489   * @return  The set of all TLS cipher suites supported by the JVM.
490   */
491  @NotNull()
492  public static SortedSet<String> getSupportedCipherSuites()
493  {
494    return getStaticInstance().supportedCipherSuites;
495  }
496
497
498
499  /**
500   * Retrieves the set of TLS cipher suites enabled by default in the JVM.  The
501   * set will be sorted in order of most preferred to least preferred, as
502   * determined by the {@link TLSCipherSuiteComparator}.
503   *
504   * @return  The set of TLS cipher suites enabled by default in the JVM.
505   */
506  @NotNull()
507  public static SortedSet<String> getDefaultCipherSuites()
508  {
509    return getStaticInstance().defaultCipherSuites;
510  }
511
512
513
514  /**
515   * Retrieves the recommended set of TLS cipher suites as selected by this
516   * class.  The set will be sorted in order of most preferred to least
517   * preferred, as determined by the {@link TLSCipherSuiteComparator}.
518   *
519   * @return  The recommended set of TLS cipher suites as selected by this
520   *          class.
521   */
522  @NotNull()
523  public static SortedSet<String> getRecommendedCipherSuites()
524  {
525    return getStaticInstance().recommendedCipherSuites;
526  }
527
528
529
530  /**
531   * Retrieves an array containing the recommended set of TLS cipher suites as
532   * selected by this class.  The array will be sorted in order of most
533   * preferred to least preferred, as determined by the
534   * {@link TLSCipherSuiteComparator}.
535   *
536   * @return  An array containing the recommended set of TLS cipher suites as
537   *          selected by this class.
538   */
539  @NotNull()
540  public static String[] getRecommendedCipherSuiteArray()
541  {
542    return getStaticInstance().recommendedCipherSuiteArray.clone();
543  }
544
545
546
547  /**
548   * Retrieves a map containing the TLS cipher suites that are supported by the
549   * JVM but are not recommended for use.  The keys of the map will be the names
550   * of the non-recommended cipher suites, sorted in order of most preferred to
551   * least preferred, as determined by the {@link TLSCipherSuiteComparator}.
552   * Each TLS cipher suite name will be mapped to a list of the reasons it is
553   * not recommended for use.
554   *
555   * @return  A map containing the TLS cipher suites that are supported by the
556   *          JVM but are not recommended for use
557   */
558  @NotNull()
559  public static SortedMap<String,List<String>> getNonRecommendedCipherSuites()
560  {
561    return getStaticInstance().nonRecommendedCipherSuites;
562  }
563
564
565
566  /**
567   * Organizes the provided set of cipher suites into recommended and
568   * non-recommended sets.
569   *
570   * @param  cipherSuiteArray  An array of the cipher suites to be organized.
571   *
572   * @return  An object pair in which the first element is the sorted set of
573   *          recommended cipher suites, and the second element is the sorted
574   *          map of non-recommended cipher suites and the reasons they are not
575   *          recommended for use.
576   */
577  @NotNull()
578  static ObjectPair<SortedSet<String>,SortedMap<String,List<String>>>
579       selectCipherSuites(@NotNull final String[] cipherSuiteArray)
580  {
581    return selectCipherSuites(cipherSuiteArray, ALLOW_SSL_PREFIX.get());
582  }
583
584
585
586  /**
587   * Organizes the provided set of cipher suites into recommended and
588   * non-recommended sets.
589   *
590   * @param  cipherSuiteArray  An array of the cipher suites to be organized.
591   * @param  includeSSLSuites  Indicates whether to allow suites that start
592   *                           with "SSL_".  If this is {@code false} (which
593   *                           should be the case for all calls to this method
594   *                           that don't come directly from this method), then
595   *                           only suites that start with "TLS_" will be
596   *                           included.  If this is {@code true}, then suites
597   *                           that start with "SSL_" may be included.  This is
598   *                           necessary because some JVMs (for example, the IBM
599   *                           JVM) only report suites that start with "SSL_"
600   *                           and none with "TLS_".  In that case, we'll rely
601   *                           only on other logic to determine which suites to
602   *                           recommend and which to exclude.
603   *
604   * @return  An object pair in which the first element is the sorted set of
605   *          recommended cipher suites, and the second element is the sorted
606   *          map of non-recommended cipher suites and the reasons they are not
607   *          recommended for use.
608   */
609  @NotNull()
610  private static ObjectPair<SortedSet<String>,SortedMap<String,List<String>>>
611               selectCipherSuites(@NotNull final String[] cipherSuiteArray,
612                                 final boolean includeSSLSuites)
613  {
614    final SortedSet<String> recommendedSet =
615         new TreeSet<>(TLSCipherSuiteComparator.getInstance());
616    final SortedMap<String,List<String>> nonRecommendedMap =
617         new TreeMap<>(TLSCipherSuiteComparator.getInstance());
618
619    for (final String cipherSuiteName : cipherSuiteArray)
620    {
621      String name =
622           StaticUtils.toUpperCase(cipherSuiteName).replace('-', '_');
623
624      // Signalling cipher suite values (which indicate capabilities of the
625      // implementation and aren't really cipher suites on their own) will
626      // always be accepted.
627      if (name.endsWith("_SCSV"))
628      {
629        recommendedSet.add(cipherSuiteName);
630        continue;
631      }
632
633
634      // Only cipher suites using the TLS protocol will be accepted.
635      final List<String> nonRecommendedReasons = new ArrayList<>(5);
636      if (name.startsWith("SSL_") && (! includeSSLSuites))
637      {
638        nonRecommendedReasons.add(
639             ERR_TLS_CIPHER_SUITE_SELECTOR_LEGACY_SSL_PROTOCOL.get());
640      }
641      else if (name.startsWith("TLS_") || name.startsWith("SSL_"))
642      {
643        if (name.startsWith("SSL_"))
644        {
645          name = "TLS_" + name.substring(4);
646        }
647
648        // Only TLS cipher suites using a recommended key exchange algorithm
649        // will be accepted.
650        if (name.startsWith("TLS_AES_") ||
651             name.startsWith("TLS_CHACHA20_") ||
652             name.startsWith("TLS_ECDHE_") ||
653             name.startsWith("TLS_DHE_"))
654        {
655          // These are recommended key exchange algorithms.
656        }
657        else if (name.startsWith("TLS_RSA_"))
658        {
659          if (ALLOW_RSA_KEY_EXCHANGE.get())
660          {
661            // This will be considered a recommended key exchange algorithm.
662          }
663          else
664          {
665            nonRecommendedReasons.add(
666                 ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get(
667                      "RSA"));
668          }
669        }
670        else if (name.startsWith("TLS_ECDH_"))
671        {
672          nonRecommendedReasons.add(
673               ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get(
674                    "ECDH"));
675        }
676        else if (name.startsWith("TLS_DH_"))
677        {
678          nonRecommendedReasons.add(
679               ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get(
680                    "DH"));
681        }
682        else if (name.startsWith("TLS_KRB5_"))
683        {
684          nonRecommendedReasons.add(
685               ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_KE_ALG.get(
686                    "KRB5"));
687        }
688        else
689        {
690          nonRecommendedReasons.add(
691               ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_UNKNOWN_KE_ALG.
692                    get());
693        }
694      }
695      else
696      {
697        nonRecommendedReasons.add(
698             ERR_TLS_CIPHER_SUITE_SELECTOR_UNRECOGNIZED_PROTOCOL.get());
699      }
700
701
702      // Cipher suites that rely on pre-shared keys will not be accepted.
703      if (name.contains("_PSK"))
704      {
705        nonRecommendedReasons.add(ERR_TLS_CIPHER_SUITE_SELECTOR_PSK.get());
706      }
707
708
709      // Cipher suites that use a null component will not be accepted.
710      if (name.contains("_NULL"))
711      {
712        nonRecommendedReasons.add(
713             ERR_TLS_CIPHER_SUITE_SELECTOR_NULL_COMPONENT.get());
714      }
715
716
717      // Cipher suites that use anonymous authentication will not be accepted.
718      if (name.contains("_ANON"))
719      {
720        nonRecommendedReasons.add(
721             ERR_TLS_CIPHER_SUITE_SELECTOR_ANON_AUTH.get());
722      }
723
724
725      // Cipher suites that use export-grade encryption will not be accepted.
726      if (name.contains("_EXPORT"))
727      {
728        nonRecommendedReasons.add(
729             ERR_TLS_CIPHER_SUITE_SELECTOR_EXPORT_ENCRYPTION.get());
730      }
731
732
733      // Only cipher suites that use AES or ChaCha20 will be accepted.
734      if (name.contains("_AES") || name.contains("_CHACHA20"))
735      {
736        // These are recommended bulk cipher algorithms.
737      }
738      else if (name.contains("_RC4"))
739      {
740        nonRecommendedReasons.add(
741             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get(
742                  "RC4"));
743      }
744      else if (name.contains("_3DES"))
745      {
746        nonRecommendedReasons.add(
747             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get(
748                  "3DES"));
749      }
750      else if (name.contains("_DES"))
751      {
752        nonRecommendedReasons.add(
753             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get(
754                  "DES"));
755      }
756      else if (name.contains("_IDEA"))
757      {
758        nonRecommendedReasons.add(
759             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get(
760                  "IDEA"));
761      }
762      else if (name.contains("_CAMELLIA"))
763      {
764        nonRecommendedReasons.add(
765             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get(
766                  "Camellia"));
767      }
768      else if (name.contains("_ARIA"))
769      {
770        nonRecommendedReasons.add(
771             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_BE_ALG.get(
772                  "ARIA"));
773      }
774      else
775      {
776        nonRecommendedReasons.add(
777             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_UNKNOWN_BE_ALG.
778                  get());
779      }
780
781
782      // Only cipher suites that use a SHA-1 or SHA-2 digest algorithm will be
783      // accepted.
784      if (name.endsWith("_SHA512") ||
785           name.endsWith("_SHA384") ||
786           name.endsWith("_SHA256"))
787      {
788        // These are recommended digest algorithms.
789      }
790      else if (name.endsWith("_SHA"))
791      {
792        if (ALLOW_SHA_1.get())
793        {
794          // This will be considered a recommended digest algorithm.
795        }
796        else
797        {
798          nonRecommendedReasons.add(
799               ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_DIGEST_ALG.
800                    get("SHA-1"));
801        }
802      }
803      else if (name.endsWith("_MD5"))
804      {
805        nonRecommendedReasons.add(
806             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_KNOWN_DIGEST_ALG.get(
807                  "MD5"));
808      }
809      else
810      {
811        nonRecommendedReasons.add(
812             ERR_TLS_CIPHER_SUITE_SELECTOR_NON_RECOMMENDED_UNKNOWN_DIGEST_ALG.
813                  get());
814      }
815
816
817      // Determine whether to recommend the cipher suite based on whether there
818      // are any non-recommended reasons.
819      if (nonRecommendedReasons.isEmpty())
820      {
821        recommendedSet.add(cipherSuiteName);
822      }
823      else
824      {
825        nonRecommendedMap.put(cipherSuiteName,
826             Collections.unmodifiableList(nonRecommendedReasons));
827      }
828    }
829
830    return new ObjectPair<>(recommendedSet, nonRecommendedMap);
831  }
832
833
834
835  /**
836   * {@inheritDoc}
837   */
838  @Override()
839  @NotNull()
840  public String getToolName()
841  {
842    return "tls-cipher-suite-selector";
843  }
844
845
846
847  /**
848   * {@inheritDoc}
849   */
850  @Override()
851  @NotNull()
852  public String getToolDescription()
853  {
854    return INFO_TLS_CIPHER_SUITE_SELECTOR_TOOL_DESC.get();
855  }
856
857
858
859  /**
860   * {@inheritDoc}
861   */
862  @Override()
863  @NotNull()
864  public String getToolVersion()
865  {
866    return Version.NUMERIC_VERSION_STRING;
867  }
868
869
870
871  /**
872   * {@inheritDoc}
873   */
874  @Override()
875  public void addToolArguments(@NotNull final ArgumentParser parser)
876       throws ArgumentException
877  {
878    // This tool does not require any arguments.
879  }
880
881
882
883  /**
884   * {@inheritDoc}
885   */
886  @Override()
887  @NotNull()
888  public ResultCode doToolProcessing()
889  {
890    generateOutput(getOut());
891    return ResultCode.SUCCESS;
892  }
893
894
895
896  /**
897   * Writes the output to the provided print stream.
898   *
899   * @param  s  The print stream to which the output should be written.
900   */
901  private void generateOutput(@NotNull final PrintStream s)
902  {
903    try
904    {
905      final SSLContext sslContext = CryptoHelper.getDefaultSSLContext();
906      s.println("Supported TLS Protocols:");
907      for (final String protocol :
908           sslContext.getSupportedSSLParameters().getProtocols())
909      {
910        s.println("* " + protocol);
911      }
912      s.println();
913
914      s.println("Enabled TLS Protocols:");
915      for (final String protocol : SSLUtil.getEnabledSSLProtocols())
916      {
917        s.println("* " + protocol);
918      }
919      s.println();
920    }
921    catch (final Exception e)
922    {
923      Debug.debugException(e);
924    }
925
926    s.println("Supported TLS Cipher Suites:");
927    for (final String cipherSuite : supportedCipherSuites)
928    {
929      s.println("* " + cipherSuite);
930    }
931
932    s.println();
933    s.println("JVM-Default TLS Cipher Suites:");
934    for (final String cipherSuite : defaultCipherSuites)
935    {
936      s.println("* " + cipherSuite);
937    }
938
939    s.println();
940    s.println("Non-Recommended TLS Cipher Suites:");
941    for (final Map.Entry<String,List<String>> e :
942         nonRecommendedCipherSuites.entrySet())
943    {
944      s.println("* " + e.getKey());
945      for (final String reason : e.getValue())
946      {
947        s.println("  - " + reason);
948      }
949    }
950
951    s.println();
952    s.println("Recommended TLS Cipher Suites:");
953    for (final String cipherSuite : recommendedCipherSuites)
954    {
955      s.println("* " + cipherSuite);
956    }
957  }
958
959
960
961  /**
962   * Filters the provided collection of potential cipher suite names to retrieve
963   * a set of the suites that are supported by the JVM.
964   *
965   * @param  potentialSuiteNames  The collection of cipher suite names to be
966   *                              filtered.
967   *
968   * @return  The set of provided cipher suites that are supported by the JVM,
969   *          or an empty set if none of the potential provided suite names are
970   *          supported by the JVM.
971   */
972  @NotNull()
973  public static Set<String> selectSupportedCipherSuites(
974                     @Nullable final Collection<String> potentialSuiteNames)
975  {
976    if (potentialSuiteNames == null)
977    {
978      return Collections.emptySet();
979    }
980
981    final TLSCipherSuiteSelector instance = getStaticInstance();
982    final int capacity = StaticUtils.computeMapCapacity(
983         instance.supportedCipherSuites.size());
984    final Map<String,String> supportedMap = new HashMap<>(capacity);
985    for (final String supportedSuite : instance.supportedCipherSuites)
986    {
987      supportedMap.put(
988           StaticUtils.toUpperCase(supportedSuite).replace('-', '_'),
989           supportedSuite);
990    }
991
992    final Set<String> selectedSet = new LinkedHashSet<>(capacity);
993    for (final String potentialSuite : potentialSuiteNames)
994    {
995      final String supportedName = supportedMap.get(
996           StaticUtils.toUpperCase(potentialSuite).replace('-', '_'));
997      if (supportedName != null)
998      {
999        selectedSet.add(supportedName);
1000      }
1001    }
1002
1003    return Collections.unmodifiableSet(selectedSet);
1004  }
1005
1006
1007
1008  /**
1009   * Indicates whether cipher suites that use the RSA key exchange algorithm
1010   * should be recommended by default.
1011   *
1012   * @return  {@code true} if cipher suites that use the RSA key exchange
1013   *          algorithm should be recommended by default, or {@code false} if
1014   *          not.
1015   */
1016  public static boolean allowRSAKeyExchange()
1017  {
1018    return ALLOW_RSA_KEY_EXCHANGE.get();
1019  }
1020
1021
1022
1023  /**
1024   * Specifies whether cipher suites that use the RSA key exchange algorithm
1025   * should be recommended by default.
1026   *
1027   * @param  allowRSAKeyExchange  Indicates whether cipher suites that use the
1028   *                              RSA key exchange algorithm should be
1029   *                              recommended by default.
1030   */
1031  public static void setAllowRSAKeyExchange(final boolean allowRSAKeyExchange)
1032  {
1033    ALLOW_RSA_KEY_EXCHANGE.set(allowRSAKeyExchange);
1034    recompute();
1035  }
1036
1037
1038
1039  /**
1040   * Indicates whether cipher suites that use the SHA-1 digest algorithm should
1041   * be recommended by default.
1042   *
1043   * @return  {@code true} if cipher suites that use the SHA-1 digest algorithm
1044   *          should be recommended by default, or {@code false} if not.
1045   */
1046  public static boolean allowSHA1()
1047  {
1048    return ALLOW_SHA_1.get();
1049  }
1050
1051
1052
1053  /**
1054   * Specifies whether cipher suites that use the SHA-1 digest algorithm should
1055   * be recommended by default.
1056   *
1057   * @param  allowSHA1  Indicates whether cipher suites that use the SHA-1
1058   *                    digest algorithm should be recommended by default.
1059   */
1060  public static void setAllowSHA1(final boolean allowSHA1)
1061  {
1062    ALLOW_SHA_1.set(allowSHA1);
1063    recompute();
1064  }
1065
1066
1067
1068  /**
1069   * Indicates whether cipher suites whose names start with "SSL_" should be
1070   * recommended by default.
1071   *
1072   * @return  {@code true} if cipher suites prefixed with either "SSL_" or
1073   *          "TLS_" should be recommended by default, or {@code false} if only
1074   *          suites prefixed with "TLS_" should be recommended by default.
1075   */
1076  public static boolean allowSSLPrefixedSuites()
1077  {
1078    return ALLOW_SSL_PREFIX.get();
1079  }
1080
1081
1082
1083  /**
1084   * Specifies whether cipher suites whose names start with "SSL_" should be
1085   * recommended by default.
1086   *
1087   * @param  allowSSLPrefix  Indicates whether cipher suites whose names start
1088   *                         with "SSL_" should be recommended by default.  If
1089   *                         this is {@code true}, then suites prefixed with
1090   *                         either "TLS_" or "SSL_" may be recommended.  If
1091   *                         this is {@code false}, then only suites prefixed
1092   *                         with "TLS_" may be recommended.
1093   */
1094  public static void setAllowSSLPrefixedSuites(final boolean allowSSLPrefix)
1095  {
1096    ALLOW_SSL_PREFIX.set(allowSSLPrefix);
1097    recompute();
1098  }
1099
1100
1101
1102  /**
1103   * Retrieves the static instance of this TLS cipher suite selector.
1104   *
1105   * @return  The static instance of this TLS cipher suite selector.
1106   */
1107  @NotNull()
1108  private static TLSCipherSuiteSelector getStaticInstance()
1109  {
1110    TLSCipherSuiteSelector instance = STATIC_INSTANCE.get();
1111    if (instance == null)
1112    {
1113      synchronized (TLSCipherSuiteSelector.class)
1114      {
1115        STATIC_INSTANCE.compareAndSet(null,
1116             new TLSCipherSuiteSelector(null, null, false));
1117        instance = STATIC_INSTANCE.get();
1118      }
1119    }
1120
1121    return instance;
1122  }
1123
1124
1125
1126  /**
1127   * Re-computes the default instance of this cipher suite selector.  This may
1128   * be necessary after certain actions that alter the supported set of TLS
1129   * cipher suites (for example, installing or removing cryptographic
1130   * providers).
1131   */
1132  public static void recompute()
1133  {
1134    synchronized (TLSCipherSuiteSelector.class)
1135    {
1136      STATIC_INSTANCE.set(null);
1137    }
1138  }
1139
1140
1141
1142  /**
1143   * Indicates whether SSL/TLS debugging is expected to be enabled in the JVM,
1144   * based on the value of the javax.net.debug system property.
1145   *
1146   * @return  {@code true} if SSL/TLS debugging is expected to be enabled in
1147   *          the JVM, ro {@code false} if not.
1148   */
1149  static boolean jvmSSLDebuggingEnabled()
1150  {
1151    return getStaticInstance().jvmSSLDebuggingEnabled;
1152  }
1153}