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}