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}