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}