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.Serializable; 041import java.util.Comparator; 042 043import com.unboundid.util.NotMutable; 044import com.unboundid.util.NotNull; 045import com.unboundid.util.Nullable; 046import com.unboundid.util.StaticUtils; 047import com.unboundid.util.ThreadSafety; 048import com.unboundid.util.ThreadSafetyLevel; 049 050 051 052/** 053 * This class provides a comparator that may be used to order TLS cipher suites 054 * from most-preferred to least-preferred. Note that its behavior is undefined 055 * for strings that are not valid TLS cipher suite names. 056 * <BR><BR> 057 * This comparator uses the following logic: 058 * <UL> 059 * <LI> 060 * Cipher suite names that end with "_SCSV" will be ordered after those that 061 * do not. These are signalling cipher suite values that indicate special 062 * capabilities and aren't really cipher suites. 063 * </LI> 064 * 065 * <LI> 066 * Cipher suite names that contain "_NULL" will be ordered after those that 067 * do not. 068 * </LI> 069 * 070 * <LI> 071 * Cipher suite names that contain "_ANON" will be ordered after those that 072 * do not. 073 * </LI> 074 * 075 * <LI> 076 * Cipher suite names that contain "_EXPORT" will be ordered after those 077 * that do not. 078 * </LI> 079 * 080 * <LI> 081 * Cipher suites will be ordered according to their prefix, as follows: 082 * <UL> 083 * <LI> 084 * Suite names starting with TLS_AES_ will come first, as they are 085 * TLSv1.3 (or later) suites that use AES for bulk encryption. 086 * </LI> 087 * <LI> 088 * Suite names starting with TLS_CHACHA20_ will come next, as they are 089 * TLSv1.3 (or later) suites that use the ChaCha20 stream cipher, which 090 * is less widely supported than AES. 091 * </LI> 092 * <LI> 093 * Suite names starting with TLS_ECDHE_ will come next, as they use 094 * elliptic curve Diffie-Hellman key exchange with ephemeral keys, 095 * providing support for forward secrecy. 096 * </LI> 097 * <LI> 098 * Suite names starting with TLS_DHE_ will come next, as they use 099 * Diffie-Hellman key exchange with ephemeral keys, also providing 100 * support for forward secrecy, but less efficient than the elliptic 101 * curve variant. 102 * </LI> 103 * <LI> 104 * Suite names starting with TLS_RSA_ will come next, as they use RSA 105 * key exchange, which does not support forward secrecy, but is still 106 * considered secure. 107 * </LI> 108 * <LI> 109 * Suite names starting with TLS_ but that do not match any of the 110 * above values will come next, as they are less desirable than any of 111 * the more specific TLS-based suites. 112 * </LI> 113 * <LI> 114 * Suite names starting with SSL_ will come next, as they are legacy 115 * SSL-based protocols that should be considered weaker than TLS-based 116 * protocol.s 117 * </LI> 118 * <LI> 119 * Suite names that do not start with TLS_ or SSL_ will come last. No 120 * such suites are expected. 121 * </LI> 122 * </UL> 123 * </LI> 124 * 125 * <LI> 126 * Cipher suite names that contain _AES will be ordered before those that 127 * contain _CHACHA20, as AES is a more widely supported bulk cipher than 128 * ChaCha20. Suite names that do not contain either _AES or _CHACHA20 will 129 * be ordered after those that contain _CHACHA20, as they likely use a bulk 130 * cipher that is weaker or not as widely supported. 131 * </LI> 132 * 133 * <LI> 134 * Cipher suites that use AES with a GCM mode will be ordered before those 135 * that use AES with a non-GCM mode. GCM (Galois/Counter Mode) uses 136 * authenticated encryption, which provides better security guarantees than 137 * non-authenticated encryption. 138 * </LI> 139 * 140 * <LI> 141 * Cipher suites that use AES with a 256-bit key will be ordered before 142 * those that use AES with a 128-bit key. 143 * </LI> 144 * 145 * <LI> 146 * Cipher suites will be ordered according to their digest algorithm, as 147 * follows: 148 * <UL> 149 * <LI> 150 * Suites that use a 512-bit SHA-2 digest will come first. At present, 151 * no such suites are defined, but they may be added in the future. 152 * </LI> 153 * <LI> 154 * Suites that use a 384-bit SHA-2 digest will come next. 155 * </LI> 156 * <LI> 157 * Suites that use a 256-bit SHA-2 digest will come next. 158 * </LI> 159 * <LI> 160 * Suites that use a SHA-1 digest will come next. 161 * </LI> 162 * <LI> 163 * Suites that use any other digest algorithm will come last, as they 164 * likely use an algorithm that is weaker or not as widely supported. 165 * </LI> 166 * </UL> 167 * </LI> 168 * 169 * <LI> 170 * If none of the above criteria can be used to differentiate the cipher 171 * suites, then it will fall back to simple lexicographic ordering. 172 * </LI> 173 * </UL> 174 */ 175@NotMutable() 176@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 177public final class TLSCipherSuiteComparator 178 implements Comparator<String>, Serializable 179{ 180 /** 181 * The singleton instance of this comparator. 182 */ 183 @NotNull private static final TLSCipherSuiteComparator INSTANCE = 184 new TLSCipherSuiteComparator(); 185 186 187 188 /** 189 * The serial version UID for this serializable class. 190 */ 191 private static final long serialVersionUID = 7719643162516590858L; 192 193 194 195 /** 196 * Creates a new instance of this comparator. 197 */ 198 private TLSCipherSuiteComparator() 199 { 200 // No implementation is required. 201 } 202 203 204 205 /** 206 * Retrieves the singleton instance of this TLS cipher suite comparator. 207 * 208 * @return The singleton instance of this TLS cipher suite comparator. 209 */ 210 @NotNull() 211 public static TLSCipherSuiteComparator getInstance() 212 { 213 return INSTANCE; 214 } 215 216 217 218 /** 219 * Compares the provided strings to determine the logical order of the TLS 220 * cipher suites that they represent. 221 * 222 * @param s1 The first string to compare. It must not be {@code null}, and 223 * it should represent a valid cipher suite name. 224 * @param s2 The second string to compare. It must not be {@code null}, and 225 * it should represent a valid cipher suite name. 226 * 227 * @return A negative integer value if the first cipher suite name should be 228 * ordered before the second, a positive integer value if the first 229 * cipher suite name should be ordered after the second, or zero if 230 * the names are considered logically equivalent. 231 */ 232 @Override() 233 public int compare(@NotNull final String s1, @NotNull final String s2) 234 { 235 final String cipherSuiteName1 = 236 StaticUtils.toUpperCase(s1).replace('-', '_'); 237 final String cipherSuiteName2 = 238 StaticUtils.toUpperCase(s2).replace('-', '_'); 239 240 final int scsvOrder = getSCSVOrder(cipherSuiteName1, cipherSuiteName2); 241 if (scsvOrder != 0) 242 { 243 return scsvOrder; 244 } 245 246 final int explicitlyWeakOrder = 247 getExplicitlyWeakOrder(cipherSuiteName1, cipherSuiteName2); 248 if (explicitlyWeakOrder != 0) 249 { 250 return explicitlyWeakOrder; 251 } 252 253 final int prefixOrder = getPrefixOrder(cipherSuiteName1, cipherSuiteName2); 254 if (prefixOrder != 0) 255 { 256 return prefixOrder; 257 } 258 259 final int blockCipherOrder = 260 getBlockCipherOrder(cipherSuiteName1, cipherSuiteName2); 261 if (blockCipherOrder != 0) 262 { 263 return blockCipherOrder; 264 } 265 266 final int digestOrder = getDigestOrder(cipherSuiteName1, cipherSuiteName2); 267 if (digestOrder != 0) 268 { 269 return digestOrder; 270 } 271 272 return s1.compareTo(s2); 273 } 274 275 276 277 /** 278 * Attempts to order the provided cipher suite names using signalling cipher 279 * suite values. 280 * 281 * @param cipherSuiteName1 The first cipher suite name to compare. It must 282 * not be {@code null}, and it should represent a 283 * valid cipher suite name. 284 * @param cipherSuiteName2 The second cipher suite name to compare. It must 285 * not be {@code null}, and it should represent a 286 * valid cipher suite name. 287 * 288 * @return A negative integer value if the first cipher suite name should be 289 * ordered before the second, a positive integer value if the first 290 * cipher suite should be ordered after the second, or zero if they 291 * are considered logically equivalent for the purposes of this 292 * method. 293 */ 294 private static int getSCSVOrder(@NotNull final String cipherSuiteName1, 295 @NotNull final String cipherSuiteName2) 296 { 297 if (cipherSuiteName1.endsWith("_SCSV")) 298 { 299 if (cipherSuiteName2.endsWith("_SCSV")) 300 { 301 return 0; 302 } 303 else 304 { 305 return 1; 306 } 307 } 308 else if (cipherSuiteName2.endsWith("_SCSV")) 309 { 310 return -1; 311 } 312 else 313 { 314 return 0; 315 } 316 } 317 318 319 320 /** 321 * Attempts to order the provided cipher suite names by whether the use a 322 * null component. anonymous authentication, or export-grade encryption. 323 * 324 * @param cipherSuiteName1 The first cipher suite name to compare. It must 325 * not be {@code null}, and it should represent a 326 * valid cipher suite name. 327 * @param cipherSuiteName2 The second cipher suite name to compare. It must 328 * not be {@code null}, and it should represent a 329 * valid cipher suite name. 330 * 331 * @return A negative integer value if the first cipher suite name should be 332 * ordered before the second, a positive integer value if the first 333 * cipher suite should be ordered after the second, or zero if they 334 * are considered logically equivalent for the purposes of this 335 * method. 336 */ 337 private static int getExplicitlyWeakOrder( 338 @NotNull final String cipherSuiteName1, 339 @NotNull final String cipherSuiteName2) 340 { 341 if (cipherSuiteName1.contains("_NULL")) 342 { 343 if (! cipherSuiteName2.contains("_NULL")) 344 { 345 return 1; 346 } 347 } 348 else if (cipherSuiteName2.contains("_NULL")) 349 { 350 return -1; 351 } 352 353 if (cipherSuiteName1.contains("_ANON")) 354 { 355 if (! cipherSuiteName2.contains("_ANON")) 356 { 357 return 1; 358 } 359 } 360 else if (cipherSuiteName2.contains("_ANON")) 361 { 362 return -1; 363 } 364 365 if (cipherSuiteName1.contains("_EXPORT")) 366 { 367 if (! cipherSuiteName2.contains("_EXPORT")) 368 { 369 return 1; 370 } 371 } 372 else if (cipherSuiteName2.contains("_EXPORT")) 373 { 374 return -1; 375 } 376 377 return 0; 378 } 379 380 381 382 /** 383 * Attempts to order the provided cipher suite names using the protocol and 384 * key agreement algorithm. 385 * 386 * @param cipherSuiteName1 The first cipher suite name to compare. It must 387 * not be {@code null}, and it should represent a 388 * valid cipher suite name. 389 * @param cipherSuiteName2 The second cipher suite name to compare. It must 390 * not be {@code null}, and it should represent a 391 * valid cipher suite name. 392 * 393 * @return A negative integer value if the first cipher suite name should be 394 * ordered before the second, a positive integer value if the first 395 * cipher suite should be ordered after the second, or zero if they 396 * are considered logically equivalent for the purposes of this 397 * method. 398 */ 399 private static int getPrefixOrder(@NotNull final String cipherSuiteName1, 400 @NotNull final String cipherSuiteName2) 401 { 402 final int prefixValue1 = getPrefixValue(cipherSuiteName1); 403 final int prefixValue2 = getPrefixValue(cipherSuiteName2); 404 return prefixValue1 - prefixValue2; 405 } 406 407 408 409 /** 410 * Retrieves an integer value for the provided cipher suite name based on the 411 * protocol and key agreement algorithm. Lower values are preferred over 412 * higher values. 413 * 414 * @param cipherSuiteName The cipher suite name for which to obtain the 415 * prefix value. It must not be {@code null}, and it 416 * should represent a valid cipher suite name. 417 * 418 * @return An integer value for the provided cipher suite name based on the 419 * protocol and key agreement algorithm. 420 */ 421 private static int getPrefixValue(@NotNull final String cipherSuiteName) 422 { 423 if (cipherSuiteName.startsWith("TLS_AES_")) 424 { 425 return 1; 426 } 427 else if (cipherSuiteName.startsWith("TLS_CHACHA20_")) 428 { 429 return 2; 430 } 431 else if (cipherSuiteName.startsWith("TLS_ECDHE_")) 432 { 433 return 3; 434 } 435 else if (cipherSuiteName.startsWith("TLS_DHE_")) 436 { 437 return 4; 438 } 439 else if (cipherSuiteName.startsWith("TLS_RSA_")) 440 { 441 return 5; 442 } 443 else if (cipherSuiteName.startsWith("TLS_ECDH_")) 444 { 445 return 6; 446 } 447 else if (cipherSuiteName.startsWith("TLS_DH_")) 448 { 449 return 7; 450 } 451 else if (cipherSuiteName.startsWith("TLS_")) 452 { 453 return 8; 454 } 455 if (cipherSuiteName.startsWith("SSL_AES_")) 456 { 457 return 9; 458 } 459 else if (cipherSuiteName.startsWith("SSL_CHACHA20_")) 460 { 461 return 10; 462 } 463 else if (cipherSuiteName.startsWith("SSL_ECDHE_")) 464 { 465 return 11; 466 } 467 else if (cipherSuiteName.startsWith("SSL_DHE_")) 468 { 469 return 12; 470 } 471 else if (cipherSuiteName.startsWith("SSL_RSA_")) 472 { 473 return 13; 474 } 475 else if (cipherSuiteName.startsWith("SSL_ECDH_")) 476 { 477 return 14; 478 } 479 else if (cipherSuiteName.startsWith("SSL_DH_")) 480 { 481 return 15; 482 } 483 else if (cipherSuiteName.startsWith("SSL_")) 484 { 485 return 16; 486 } 487 else 488 { 489 return 17; 490 } 491 } 492 493 494 495 /** 496 * Attempts to order the provided cipher suite names using the block cipher 497 * settings. 498 * 499 * @param cipherSuiteName1 The first cipher suite name to compare. It must 500 * not be {@code null}, and it should represent a 501 * valid cipher suite name. 502 * @param cipherSuiteName2 The second cipher suite name to compare. It must 503 * not be {@code null}, and it should represent a 504 * valid cipher suite name. 505 * 506 * @return A negative integer value if the first cipher suite name should be 507 * ordered before the second, a positive integer value if the first 508 * cipher suite should be ordered after the second, or zero if they 509 * are considered logically equivalent for the purposes of this 510 * method. 511 */ 512 private static int getBlockCipherOrder(@NotNull final String cipherSuiteName1, 513 @NotNull final String cipherSuiteName2) 514 { 515 final int blockCipherValue1 = getBlockCipherValue(cipherSuiteName1); 516 final int blockCipherValue2 = getBlockCipherValue(cipherSuiteName2); 517 return blockCipherValue1 - blockCipherValue2; 518 } 519 520 521 522 /** 523 * Retrieves an integer value for the provided cipher suite name based on the 524 * block cipher settings. Lower values are preferred over higher values. 525 * 526 * @param cipherSuiteName The cipher suite name for which to obtain the 527 * prefix value. It must not be {@code null}, and it 528 * should represent a valid cipher suite name. 529 * 530 * @return An integer value for the provided cipher suite name based on the 531 * block cipher settings. 532 */ 533 private static int getBlockCipherValue(@NotNull final String cipherSuiteName) 534 { 535 if (cipherSuiteName.contains("_AES_256_GCM")) 536 { 537 return 1; 538 } 539 else if (cipherSuiteName.contains("_AES_128_GCM")) 540 { 541 return 2; 542 } 543 else if (cipherSuiteName.contains("_AES") && 544 cipherSuiteName.contains("_GCM")) 545 { 546 return 3; 547 } 548 else if (cipherSuiteName.contains("_AES_256")) 549 { 550 return 4; 551 } 552 else if (cipherSuiteName.contains("_AES_128")) 553 { 554 return 5; 555 } 556 else if (cipherSuiteName.contains("_AES")) 557 { 558 return 6; 559 } 560 else if (cipherSuiteName.contains("_CHACHA20")) 561 { 562 return 7; 563 } 564 else if (cipherSuiteName.contains("_GCM")) 565 { 566 return 8; 567 } 568 else 569 { 570 return 9; 571 } 572 } 573 574 575 576 /** 577 * Attempts to order the provided cipher suite names using the block cipher 578 * settings. 579 * 580 * @param cipherSuiteName1 The first cipher suite name to compare. It must 581 * not be {@code null}, and it should represent a 582 * valid cipher suite name. 583 * @param cipherSuiteName2 The second cipher suite name to compare. It must 584 * not be {@code null}, and it should represent a 585 * valid cipher suite name. 586 * 587 * @return A negative integer value if the first cipher suite name should be 588 * ordered before the second, a positive integer value if the first 589 * cipher suite should be ordered after the second, or zero if they 590 * are considered logically equivalent for the purposes of this 591 * method. 592 */ 593 private static int getDigestOrder(@NotNull final String cipherSuiteName1, 594 @NotNull final String cipherSuiteName2) 595 { 596 final int digestValue1 = getDigestValue(cipherSuiteName1); 597 final int digestValue2 = getDigestValue(cipherSuiteName2); 598 return digestValue1 - digestValue2; 599 } 600 601 602 603 /** 604 * Retrieves an integer value for the provided cipher suite name based on the 605 * block cipher settings. Lower values are preferred over higher values. 606 * 607 * @param cipherSuiteName The cipher suite name for which to obtain the 608 * prefix value. It must not be {@code null}, and it 609 * should represent a valid cipher suite name. 610 * 611 * @return An integer value for the provided cipher suite name based on the 612 * block cipher settings. 613 */ 614 private static int getDigestValue(@NotNull final String cipherSuiteName) 615 { 616 if (cipherSuiteName.endsWith("_SHA512")) 617 { 618 return 1; 619 } 620 else if (cipherSuiteName.endsWith("_SHA384")) 621 { 622 return 2; 623 } 624 else if (cipherSuiteName.endsWith("_SHA256")) 625 { 626 return 3; 627 } 628 else if (cipherSuiteName.endsWith("_SHA")) 629 { 630 return 4; 631 } 632 else 633 { 634 return 5; 635 } 636 } 637 638 639 640 /** 641 * Indicates whether the provided object is logically equivalent to this TLS 642 * cipher suite comparator. 643 * 644 * @param o The object for which to make the determination. 645 * 646 * @return {@code true} if the provided object is logically equivalent to 647 * this TLS cipher suite comparator. 648 */ 649 @Override() 650 public boolean equals(@Nullable final Object o) 651 { 652 return ((o != null) && (o instanceof TLSCipherSuiteComparator)); 653 } 654 655 656 657 /** 658 * Retrieves the hash code for this TLS cipher suite comparator. 659 * 660 * @return The hash code for this TLS cipher suite comparator. 661 */ 662 @Override() 663 public int hashCode() 664 { 665 return 0; 666 } 667}