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}