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