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