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