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}