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