001/* 002 * Copyright 2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util.ssl; 022 023 024 025import java.io.Serializable; 026import java.util.Comparator; 027 028import com.unboundid.util.NotMutable; 029import com.unboundid.util.StaticUtils; 030import com.unboundid.util.ThreadSafety; 031import com.unboundid.util.ThreadSafetyLevel; 032 033 034 035/** 036 * This class provides a comparator that may be used to order TLS cipher suites 037 * from most-preferred to least-preferred. Note that its behavior is undefined 038 * for strings that are not valid TLS cipher suite names. 039 * <BR><BR> 040 * This comparator uses the following logic: 041 * <UL> 042 * <LI> 043 * Cipher suite names that end with "_SCSV" will be ordered after those that 044 * do not. These are signalling cipher suite values that indicate special 045 * capabilities and aren't really cipher suites. 046 * </LI> 047 * 048 * <LI> 049 * Cipher suites will be ordered according to their prefix, as follows: 050 * <UL> 051 * <LI> 052 * Suite names starting with TLS_AES_ will come first, as they are 053 * TLSv1.3 (or later) suites that use AES for bulk encryption. 054 * </LI> 055 * <LI> 056 * Suite names starting with TLS_CHACHA20_ will come next, as they are 057 * TLSv1.3 (or later) suites that use the ChaCha20 stream cipher, which 058 * is less widely supported than AES. 059 * </LI> 060 * <LI> 061 * Suite names starting with TLS_ECDHE_ will come next, as they use 062 * elliptic curve Diffie-Hellman key exchange with ephemeral keys, 063 * providing support for forward secrecy. 064 * </LI> 065 * <LI> 066 * Suite names starting with TLS_DHE_ will come next, as they use 067 * Diffie-Hellman key exchange with ephemeral keys, also providing 068 * support for forward secrecy, but less efficient than the elliptic 069 * curve variant. 070 * </LI> 071 * <LI> 072 * Suite names starting with TLS_RSA_ will come next, as they use RSA 073 * key exchange, which does not support forward secrecy, but is still 074 * considered secure. 075 * </LI> 076 * <LI> 077 * Suite names starting with TLS_ but that do not match any of the 078 * above values will come next, as they are less desirable than any of 079 * the more specific TLS-based suites. 080 * </LI> 081 * <LI> 082 * Suite names starting with SSL_ will come next, as they are legacy 083 * SSL-based protocols that should be considered weaker than TLS-based 084 * protocol.s 085 * </LI> 086 * <LI> 087 * Suite names that do not start with TLS_ or SSL_ will come last. No 088 * such suites are expected. 089 * </LI> 090 * </UL> 091 * </LI> 092 * 093 * <LI> 094 * Cipher suite names that contain _AES will be ordered before those that 095 * contain _CHACHA20, as AES is a more widely supported bulk cipher than 096 * ChaCha20. Suite names that do not contain either _AES or _CHACHA20 will 097 * be ordered after those that contain _CHACHA20, as they likely use a bulk 098 * cipher that is weaker or not as widely supported. 099 * </LI> 100 * 101 * <LI> 102 * Cipher suites that use AES with a GCM mode will be ordered before those 103 * that use AES with a non-GCM mode. GCM (Galois/Counter Mode) uses 104 * authenticated encryption, which provides better security guarantees than 105 * non-authenticated encryption. 106 * </LI> 107 * 108 * <LI> 109 * Cipher suites that use AES with a 256-bit key will be ordered before 110 * those that use AES with a 128-bit key. 111 * </LI> 112 * 113 * <LI> 114 * Cipher suites will be ordered according to their digest algorithm, as 115 * follows: 116 * <UL> 117 * <LI> 118 * Suites that use a 512-bit SHA-2 digest will come first. At present, 119 * no such suites are defined, but they may be added in the future. 120 * </LI> 121 * <LI> 122 * Suites that use a 384-bit SHA-2 digest will come next. 123 * </LI> 124 * <LI> 125 * Suites that use a 256-bit SHA-2 digest will come next. 126 * </LI> 127 * <LI> 128 * Suites that use a SHA-1 digest will come next. 129 * </LI> 130 * <LI> 131 * Suites that use any other digest algorithm will come last, as they 132 * likely use an algorithm that is weaker or not as widely supported. 133 * </LI> 134 * </UL> 135 * </LI> 136 * 137 * <LI> 138 * If none of the above criteria can be used to differentiate the cipher 139 * suites, then it will fall back to simple lexicographic ordering. 140 * </LI> 141 * </UL> 142 */ 143@NotMutable() 144@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 145public final class TLSCipherSuiteComparator 146 implements Comparator<String>, Serializable 147{ 148 /** 149 * The singleton instance of this comparator. 150 */ 151 private static final TLSCipherSuiteComparator INSTANCE = 152 new TLSCipherSuiteComparator(); 153 154 155 156 /** 157 * The serial version UID for this serializable class. 158 */ 159 private static final long serialVersionUID = 7719643162516590858L; 160 161 162 163 /** 164 * Creates a new instance of this comparator. 165 */ 166 private TLSCipherSuiteComparator() 167 { 168 // No implementation is required. 169 } 170 171 172 173 /** 174 * Retrieves the singleton instance of this TLS cipher suite comparator. 175 * 176 * @return The singleton instance of this TLS cipher suite comparator. 177 */ 178 public static TLSCipherSuiteComparator getInstance() 179 { 180 return INSTANCE; 181 } 182 183 184 185 /** 186 * Compares the provided strings to determine the logical order of the TLS 187 * cipher suites that they represent. 188 * 189 * @param s1 The first string to compare. It must not be {@code null}, and 190 * it should represent a valid cipher suite name. 191 * @param s2 The second string to compare. It must not be {@code null}, and 192 * it should represent a valid cipher suite name. 193 * 194 * @return A negative integer value if the first cipher suite name should be 195 * ordered before the second, a positive integer value if the first 196 * cipher suite name should be ordered after the second, or zero if 197 * the names are considered logically equivalent. 198 */ 199 @Override() 200 public int compare(final String s1, final String s2) 201 { 202 final String cipherSuiteName1 = 203 StaticUtils.toUpperCase(s1).replace('-', '_'); 204 final String cipherSuiteName2 = 205 StaticUtils.toUpperCase(s2).replace('-', '_'); 206 207 final int scsvOrder = getSCSVOrder(cipherSuiteName1, cipherSuiteName2); 208 if (scsvOrder != 0) 209 { 210 return scsvOrder; 211 } 212 213 final int prefixOrder = getPrefixOrder(cipherSuiteName1, cipherSuiteName2); 214 if (prefixOrder != 0) 215 { 216 return prefixOrder; 217 } 218 219 final int blockCipherOrder = 220 getBlockCipherOrder(cipherSuiteName1, cipherSuiteName2); 221 if (blockCipherOrder != 0) 222 { 223 return blockCipherOrder; 224 } 225 226 final int digestOrder = getDigestOrder(cipherSuiteName1, cipherSuiteName2); 227 if (digestOrder != 0) 228 { 229 return digestOrder; 230 } 231 232 return s1.compareTo(s2); 233 } 234 235 236 237 /** 238 * Attempts to order the provided cipher suite names using signalling cipher 239 * suite values. 240 * 241 * @param cipherSuiteName1 The first cipher suite name to compare. It must 242 * not be {@code null}, and it should represent a 243 * valid cipher suite name. 244 * @param cipherSuiteName2 The second cipher suite name to compare. It must 245 * not be {@code null}, and it should represent a 246 * valid cipher suite name. 247 * 248 * @return A negative integer value if the first cipher suite name should be 249 * ordered before the second, a positive integer value if the first 250 * cipher suite should be ordered after the second, or zero if they 251 * are considered logically equivalent for the purposes of this 252 * method. 253 */ 254 private static int getSCSVOrder(final String cipherSuiteName1, 255 final String cipherSuiteName2) 256 { 257 if (cipherSuiteName1.endsWith("_SCSV")) 258 { 259 if (cipherSuiteName2.endsWith("_SCSV")) 260 { 261 return 0; 262 } 263 else 264 { 265 return 1; 266 } 267 } 268 else if (cipherSuiteName2.endsWith("_SCSV")) 269 { 270 return -1; 271 } 272 else 273 { 274 return 0; 275 } 276 } 277 278 279 280 /** 281 * Attempts to order the provided cipher suite names using the protocol and 282 * key agreement algorithm. 283 * 284 * @param cipherSuiteName1 The first cipher suite name to compare. It must 285 * not be {@code null}, and it should represent a 286 * valid cipher suite name. 287 * @param cipherSuiteName2 The second cipher suite name to compare. It must 288 * not be {@code null}, and it should represent a 289 * valid cipher suite name. 290 * 291 * @return A negative integer value if the first cipher suite name should be 292 * ordered before the second, a positive integer value if the first 293 * cipher suite should be ordered after the second, or zero if they 294 * are considered logically equivalent for the purposes of this 295 * method. 296 */ 297 private static int getPrefixOrder(final String cipherSuiteName1, 298 final String cipherSuiteName2) 299 { 300 final int prefixValue1 = getPrefixValue(cipherSuiteName1); 301 final int prefixValue2 = getPrefixValue(cipherSuiteName2); 302 return prefixValue1 - prefixValue2; 303 } 304 305 306 307 /** 308 * Retrieves an integer value for the provided cipher suite name based on the 309 * protocol and key agreement algorithm. Lower values are preferred over 310 * higher values. 311 * 312 * @param cipherSuiteName The cipher suite name for which to obtain the 313 * prefix value. It must not be {@code null}, and it 314 * should represent a valid cipher suite name. 315 * 316 * @return An integer value for the provided cipher suite name based on the 317 * protocol and key agreement algorithm. 318 */ 319 private static int getPrefixValue(final String cipherSuiteName) 320 { 321 if (cipherSuiteName.startsWith("TLS_AES_")) 322 { 323 return 1; 324 } 325 else if (cipherSuiteName.startsWith("TLS_CHACHA20_")) 326 { 327 return 2; 328 } 329 else if (cipherSuiteName.startsWith("TLS_ECDHE_")) 330 { 331 return 3; 332 } 333 else if (cipherSuiteName.startsWith("TLS_DHE_")) 334 { 335 return 4; 336 } 337 else if (cipherSuiteName.startsWith("TLS_RSA_")) 338 { 339 return 5; 340 } 341 else if (cipherSuiteName.startsWith("TLS_")) 342 { 343 return 6; 344 } 345 else if (cipherSuiteName.startsWith("SSL_")) 346 { 347 return 7; 348 } 349 else 350 { 351 return 8; 352 } 353 } 354 355 356 357 /** 358 * Attempts to order the provided cipher suite names using the block cipher 359 * settings. 360 * 361 * @param cipherSuiteName1 The first cipher suite name to compare. It must 362 * not be {@code null}, and it should represent a 363 * valid cipher suite name. 364 * @param cipherSuiteName2 The second cipher suite name to compare. It must 365 * not be {@code null}, and it should represent a 366 * valid cipher suite name. 367 * 368 * @return A negative integer value if the first cipher suite name should be 369 * ordered before the second, a positive integer value if the first 370 * cipher suite should be ordered after the second, or zero if they 371 * are considered logically equivalent for the purposes of this 372 * method. 373 */ 374 private static int getBlockCipherOrder(final String cipherSuiteName1, 375 final String cipherSuiteName2) 376 { 377 final int blockCipherValue1 = getBlockCipherValue(cipherSuiteName1); 378 final int blockCipherValue2 = getBlockCipherValue(cipherSuiteName2); 379 return blockCipherValue1 - blockCipherValue2; 380 } 381 382 383 384 /** 385 * Retrieves an integer value for the provided cipher suite name based on the 386 * block cipher settings. Lower values are preferred over higher values. 387 * 388 * @param cipherSuiteName The cipher suite name for which to obtain the 389 * prefix value. It must not be {@code null}, and it 390 * should represent a valid cipher suite name. 391 * 392 * @return An integer value for the provided cipher suite name based on the 393 * block cipher settings. 394 */ 395 private static int getBlockCipherValue(final String cipherSuiteName) 396 { 397 if (cipherSuiteName.contains("_AES_256_GCM")) 398 { 399 return 1; 400 } 401 else if (cipherSuiteName.contains("_AES_128_GCM")) 402 { 403 return 2; 404 } 405 else if (cipherSuiteName.contains("_AES") && 406 cipherSuiteName.contains("_GCM")) 407 { 408 return 3; 409 } 410 else if (cipherSuiteName.contains("_AES_256")) 411 { 412 return 4; 413 } 414 else if (cipherSuiteName.contains("_AES_128")) 415 { 416 return 5; 417 } 418 else if (cipherSuiteName.contains("_AES")) 419 { 420 return 6; 421 } 422 else if (cipherSuiteName.contains("_CHACHA20")) 423 { 424 return 7; 425 } 426 else if (cipherSuiteName.contains("_GCM")) 427 { 428 return 8; 429 } 430 else 431 { 432 return 9; 433 } 434 } 435 436 437 438 /** 439 * Attempts to order the provided cipher suite names using the block cipher 440 * settings. 441 * 442 * @param cipherSuiteName1 The first cipher suite name to compare. It must 443 * not be {@code null}, and it should represent a 444 * valid cipher suite name. 445 * @param cipherSuiteName2 The second cipher suite name to compare. It must 446 * not be {@code null}, and it should represent a 447 * valid cipher suite name. 448 * 449 * @return A negative integer value if the first cipher suite name should be 450 * ordered before the second, a positive integer value if the first 451 * cipher suite should be ordered after the second, or zero if they 452 * are considered logically equivalent for the purposes of this 453 * method. 454 */ 455 private static int getDigestOrder(final String cipherSuiteName1, 456 final String cipherSuiteName2) 457 { 458 final int digestValue1 = getDigestValue(cipherSuiteName1); 459 final int digestValue2 = getDigestValue(cipherSuiteName2); 460 return digestValue1 - digestValue2; 461 } 462 463 464 465 /** 466 * Retrieves an integer value for the provided cipher suite name based on the 467 * block cipher settings. Lower values are preferred over higher values. 468 * 469 * @param cipherSuiteName The cipher suite name for which to obtain the 470 * prefix value. It must not be {@code null}, and it 471 * should represent a valid cipher suite name. 472 * 473 * @return An integer value for the provided cipher suite name based on the 474 * block cipher settings. 475 */ 476 private static int getDigestValue(final String cipherSuiteName) 477 { 478 if (cipherSuiteName.endsWith("_SHA512")) 479 { 480 return 1; 481 } 482 else if (cipherSuiteName.endsWith("_SHA384")) 483 { 484 return 2; 485 } 486 else if (cipherSuiteName.endsWith("_SHA256")) 487 { 488 return 3; 489 } 490 else if (cipherSuiteName.endsWith("_SHA")) 491 { 492 return 4; 493 } 494 else 495 { 496 return 5; 497 } 498 } 499 500 501 502 /** 503 * Indicates whether the provided object is logically equivalent to this TLS 504 * cipher suite comparator. 505 * 506 * @param o The object for which to make the determination. 507 * 508 * @return {@code true} if the provided object is logically equivalent to 509 * this TLS cipher suite comparator. 510 */ 511 @Override() 512 public boolean equals(final Object o) 513 { 514 return ((o != null) && (o instanceof TLSCipherSuiteComparator)); 515 } 516 517 518 519 /** 520 * Retrieves the hash code for this TLS cipher suite comparator. 521 * 522 * @return The hash code for this TLS cipher suite comparator. 523 */ 524 @Override() 525 public int hashCode() 526 { 527 return 0; 528 } 529}