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