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