Home | History | Annotate | Download | only in conscrypt
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package org.conscrypt;
     17 
     18 import java.security.PublicKey;
     19 import java.security.cert.X509Certificate;
     20 import java.security.interfaces.ECPublicKey;
     21 import java.security.interfaces.RSAPublicKey;
     22 import java.util.Comparator;
     23 import java.util.Date;
     24 import java.util.HashMap;
     25 import java.util.Locale;
     26 import java.util.Map;
     27 
     28 /**
     29  * {@link Comparator} for prioritizing certificates in path building.
     30  *
     31  * <p>
     32  * The sort order is as follows:
     33  * <ol>
     34  * <li>Self-issued certificates first.</li>
     35  * <li>Strength of certificates descending (EC before RSA, key size descending, signature
     36  * algorithm strength descending).</li>
     37  * <li>notAfter date descending.</li>
     38  * <li>notBefore date descending.</li>
     39  * </ol>
     40  * </p>
     41  */
     42 @Internal
     43 public final class CertificatePriorityComparator implements Comparator<X509Certificate> {
     44 
     45     /**
     46      * Map of signature algorithm OIDs to priorities. OIDs with a lower priority will be sorted
     47      * before those with higher.
     48      */
     49     private static final Map<String, Integer> ALGORITHM_OID_PRIORITY_MAP;
     50 
     51     /*
     52      * Priorities of digest algorithms. Lower is better.
     53      */
     54     private static final Integer PRIORITY_MD5 = 6;
     55     private static final Integer PRIORITY_SHA1 = 5;
     56     private static final Integer PRIORITY_SHA224 = 4;
     57     private static final Integer PRIORITY_SHA256 = 3;
     58     private static final Integer PRIORITY_SHA384 = 2;
     59     private static final Integer PRIORITY_SHA512 = 1;
     60     private static final Integer PRIORITY_UNKNOWN = -1;
     61     static {
     62         ALGORITHM_OID_PRIORITY_MAP = new HashMap<>();
     63         // RSA oids
     64         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.13", PRIORITY_SHA512);
     65         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.12", PRIORITY_SHA384);
     66         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.11", PRIORITY_SHA256);
     67         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.14", PRIORITY_SHA224);
     68         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.5", PRIORITY_SHA1);
     69         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.4", PRIORITY_MD5);
     70         // ECDSA oids
     71         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.4", PRIORITY_SHA512);
     72         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.3", PRIORITY_SHA384);
     73         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.2", PRIORITY_SHA256);
     74         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.1", PRIORITY_SHA224);
     75         ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.1", PRIORITY_SHA1);
     76     }
     77 
     78     @Override
     79     public int compare(X509Certificate lhs, X509Certificate rhs) {
     80         int result;
     81         boolean lhsSelfSigned = lhs.getSubjectDN().equals(lhs.getIssuerDN());
     82         boolean rhsSelfSigned = rhs.getSubjectDN().equals(rhs.getIssuerDN());
     83         // Self-issued before not self-issued to avoid trying bridge certs first.
     84         if (lhsSelfSigned != rhsSelfSigned) {
     85             return rhsSelfSigned ? 1 : -1;
     86         }
     87         // Strength descending.
     88         result = compareStrength(rhs, lhs);
     89         if (result != 0) {
     90             return result;
     91         }
     92         // notAfter descending.
     93         Date lhsNotAfter = lhs.getNotAfter();
     94         Date rhsNotAfter = rhs.getNotAfter();
     95         result = rhsNotAfter.compareTo(lhsNotAfter);
     96         if (result != 0) {
     97             return result;
     98         }
     99         // notBefore descending.
    100         Date lhsNotBefore = lhs.getNotBefore();
    101         Date rhsNotBefore = rhs.getNotBefore();
    102         return rhsNotBefore.compareTo(lhsNotBefore);
    103     }
    104 
    105     private int compareStrength(X509Certificate lhs, X509Certificate rhs) {
    106         int result;
    107         PublicKey lhsPublicKey = lhs.getPublicKey();
    108         PublicKey rhsPublicKey = rhs.getPublicKey();
    109         result = compareKeyAlgorithm(lhsPublicKey, rhsPublicKey);
    110         if (result != 0) {
    111             return result;
    112         }
    113         result = compareKeySize(lhsPublicKey, rhsPublicKey);
    114         if (result != 0) {
    115             return result;
    116         }
    117         return compareSignatureAlgorithm(lhs, rhs);
    118     }
    119 
    120     private int compareKeyAlgorithm(PublicKey lhs, PublicKey rhs) {
    121         String lhsAlgorithm = lhs.getAlgorithm().toUpperCase(Locale.US);
    122         String rhsAlgorithm = rhs.getAlgorithm().toUpperCase(Locale.US);
    123 
    124         if (lhsAlgorithm.equals(rhsAlgorithm)) {
    125             return 0;
    126         }
    127 
    128         // Prefer EC to RSA.
    129         if ("EC".equals(lhsAlgorithm)) {
    130             return 1;
    131         } else {
    132             return -1;
    133         }
    134     }
    135 
    136     private int compareKeySize(PublicKey lhs, PublicKey rhs) {
    137         String lhsAlgorithm = lhs.getAlgorithm().toUpperCase(Locale.US);
    138         String rhsAlgorithm = rhs.getAlgorithm().toUpperCase(Locale.US);
    139         if (!lhsAlgorithm.equals(rhsAlgorithm)) {
    140             throw new IllegalArgumentException("Keys are not of the same type");
    141         }
    142         int lhsSize = getKeySize(lhs);
    143         int rhsSize = getKeySize(rhs);
    144         return lhsSize - rhsSize;
    145     }
    146 
    147     private int getKeySize(PublicKey pkey) {
    148         if (pkey instanceof ECPublicKey) {
    149             return ((ECPublicKey) pkey).getParams().getCurve().getField().getFieldSize();
    150         } else if (pkey instanceof RSAPublicKey) {
    151             return ((RSAPublicKey) pkey).getModulus().bitLength();
    152         } else {
    153             throw new IllegalArgumentException(
    154                     "Unsupported public key type: " + pkey.getClass().getName());
    155         }
    156     }
    157 
    158     private int compareSignatureAlgorithm(X509Certificate lhs, X509Certificate rhs) {
    159         Integer lhsPriority = ALGORITHM_OID_PRIORITY_MAP.get(lhs.getSigAlgOID());
    160         Integer rhsPriority = ALGORITHM_OID_PRIORITY_MAP.get(rhs.getSigAlgOID());
    161         if (lhsPriority == null) {
    162             lhsPriority = PRIORITY_UNKNOWN;
    163         }
    164         if (rhsPriority == null) {
    165             rhsPriority = PRIORITY_UNKNOWN;
    166         }
    167         return rhsPriority - lhsPriority;
    168     }
    169 }
    170