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}