1 /* 2 * Copyright (C) 2015 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 17 package org.conscrypt.ct; 18 19 import java.security.cert.CertificateEncodingException; 20 import java.security.cert.CertificateException; 21 import java.security.cert.X509Certificate; 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.List; 25 import org.conscrypt.NativeCrypto; 26 import org.conscrypt.OpenSSLX509Certificate; 27 28 public class CTVerifier { 29 private final CTLogStore store; 30 31 public CTVerifier(CTLogStore store) { 32 this.store = store; 33 } 34 35 public CTVerificationResult verifySignedCertificateTimestamps(List<X509Certificate> chain, 36 byte[] tlsData, byte[] ocspData) throws CertificateEncodingException { 37 OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[chain.size()]; 38 int i = 0; 39 for(X509Certificate cert : chain) { 40 certs[i++] = OpenSSLX509Certificate.fromCertificate(cert); 41 } 42 return verifySignedCertificateTimestamps(certs, tlsData, ocspData); 43 } 44 45 /** 46 * Verify a certificate chain for transparency. 47 * Signed timestamps are extracted from the leaf certificate, TLS extension, and stapled ocsp 48 * response, and verified against the list of known logs. 49 * @throws IllegalArgumentException if the chain is empty 50 */ 51 public CTVerificationResult verifySignedCertificateTimestamps(OpenSSLX509Certificate[] chain, 52 byte[] tlsData, byte[] ocspData) throws CertificateEncodingException { 53 if (chain.length == 0) { 54 throw new IllegalArgumentException("Chain of certificates mustn't be empty."); 55 } 56 57 OpenSSLX509Certificate leaf = chain[0]; 58 59 CTVerificationResult result = new CTVerificationResult(); 60 List<SignedCertificateTimestamp> tlsScts = getSCTsFromTLSExtension(tlsData); 61 verifyExternalSCTs(tlsScts, leaf, result); 62 63 List<SignedCertificateTimestamp> ocspScts = getSCTsFromOCSPResponse(ocspData, chain); 64 verifyExternalSCTs(ocspScts, leaf, result); 65 66 List<SignedCertificateTimestamp> embeddedScts = getSCTsFromX509Extension(chain[0]); 67 verifyEmbeddedSCTs(embeddedScts, chain, result); 68 return result; 69 } 70 71 /** 72 * Verify a list of SCTs which were embedded from an X509 certificate. 73 * The result of the verification for each sct is added to {@code result}. 74 */ 75 private void verifyEmbeddedSCTs(List<SignedCertificateTimestamp> scts, 76 OpenSSLX509Certificate[] chain, 77 CTVerificationResult result) { 78 // Avoid creating the cert entry if we don't need it 79 if (scts.isEmpty()) { 80 return; 81 } 82 83 CertificateEntry precertEntry = null; 84 if (chain.length >= 2) { 85 OpenSSLX509Certificate leaf = chain[0]; 86 OpenSSLX509Certificate issuer = chain[1]; 87 88 try { 89 precertEntry = CertificateEntry.createForPrecertificate(leaf, issuer); 90 } catch (CertificateException e) { 91 // Leave precertEntry as null, we handle it just below 92 } 93 } 94 95 if (precertEntry == null) { 96 markSCTsAsInvalid(scts, result); 97 return; 98 } 99 100 for (SignedCertificateTimestamp sct: scts) { 101 VerifiedSCT.Status status = verifySingleSCT(sct, precertEntry); 102 result.add(new VerifiedSCT(sct, status)); 103 } 104 } 105 106 /** 107 * Verify a list of SCTs which were not embedded in an X509 certificate, that is received 108 * through the TLS or OCSP extensions. 109 * The result of the verification for each sct is added to {@code result}. 110 */ 111 private void verifyExternalSCTs(List<SignedCertificateTimestamp> scts, 112 OpenSSLX509Certificate leaf, 113 CTVerificationResult result) { 114 // Avoid creating the cert entry if we don't need it 115 if (scts.isEmpty()) { 116 return; 117 } 118 119 CertificateEntry x509Entry; 120 try { 121 x509Entry = CertificateEntry.createForX509Certificate(leaf); 122 } catch (CertificateException e) { 123 markSCTsAsInvalid(scts, result); 124 return; 125 } 126 127 for (SignedCertificateTimestamp sct: scts) { 128 VerifiedSCT.Status status = verifySingleSCT(sct, x509Entry); 129 result.add(new VerifiedSCT(sct, status)); 130 } 131 } 132 133 /** 134 * Verify a single SCT for the given Certificate Entry 135 */ 136 private VerifiedSCT.Status verifySingleSCT(SignedCertificateTimestamp sct, 137 CertificateEntry certEntry) { 138 CTLogInfo log = store.getKnownLog(sct.getLogID()); 139 if (log == null) { 140 return VerifiedSCT.Status.UNKNOWN_LOG; 141 } 142 143 return log.verifySingleSCT(sct, certEntry); 144 } 145 146 /** 147 * Add every SCT in {@code scts} to {@code result} with INVALID_SCT as status 148 */ 149 private void markSCTsAsInvalid(List<SignedCertificateTimestamp> scts, 150 CTVerificationResult result) { 151 for (SignedCertificateTimestamp sct: scts) { 152 result.add(new VerifiedSCT(sct, VerifiedSCT.Status.INVALID_SCT)); 153 } 154 } 155 156 /** 157 * Parse an encoded SignedCertificateTimestampList into a list of SignedCertificateTimestamp 158 * instances, as described by RFC6962. 159 * Individual SCTs which fail to be parsed are skipped. If the data is null, or the encompassing 160 * list fails to be parsed, an empty list is returned. 161 * @param origin used to create the SignedCertificateTimestamp instances. 162 */ 163 private List<SignedCertificateTimestamp> getSCTsFromSCTList(byte[] data, 164 SignedCertificateTimestamp.Origin origin) { 165 if (data == null) { 166 return Collections.emptyList(); 167 } 168 169 byte[][] sctList; 170 try { 171 sctList = Serialization.readList(data, CTConstants.SCT_LIST_LENGTH_BYTES, 172 CTConstants.SERIALIZED_SCT_LENGTH_BYTES); 173 } catch (SerializationException e) { 174 return Collections.emptyList(); 175 } 176 177 List<SignedCertificateTimestamp> scts = new ArrayList<>(); 178 for (byte[] encodedSCT: sctList) { 179 try { 180 SignedCertificateTimestamp sct = SignedCertificateTimestamp.decode(encodedSCT, origin); 181 scts.add(sct); 182 } catch (SerializationException e) { 183 // Ignore errors 184 } 185 } 186 187 return scts; 188 } 189 190 /** 191 * Extract a list of SignedCertificateTimestamp from a TLS "signed_certificate_timestamp" 192 * extension as described by RFC6962. 193 * Individual SCTs which fail to be parsed are skipped. If the data is null, or the encompassing 194 * list fails to be parsed, an empty list is returned. 195 * @param data contents of the TLS extension to be decoded 196 */ 197 private List<SignedCertificateTimestamp> getSCTsFromTLSExtension(byte[] data) { 198 return getSCTsFromSCTList(data, SignedCertificateTimestamp.Origin.TLS_EXTENSION); 199 } 200 201 /** 202 * Extract a list of SignedCertificateTimestamp contained in an OCSP response. 203 * If the data is null, or parsing the OCSP response fails, an empty list is returned. 204 * Individual SCTs which fail to be parsed are skipped. 205 * @param data contents of the OCSP response 206 * @param chain certificate chain for which to get SCTs. Must contain at least the leaf and it's 207 * issuer in order to identify the relevant SingleResponse from the OCSP response, 208 * or an empty list is returned 209 */ 210 private List<SignedCertificateTimestamp> getSCTsFromOCSPResponse(byte[] data, 211 OpenSSLX509Certificate[] chain) { 212 if (data == null || chain.length < 2) { 213 return Collections.emptyList(); 214 } 215 216 byte[] extData = NativeCrypto.get_ocsp_single_extension(data, CTConstants.OCSP_SCT_LIST_OID, 217 chain[0].getContext(), 218 chain[1].getContext()); 219 if (extData == null) { 220 return Collections.emptyList(); 221 } 222 223 try { 224 return getSCTsFromSCTList( 225 Serialization.readDEROctetString( 226 Serialization.readDEROctetString(extData)), 227 SignedCertificateTimestamp.Origin.OCSP_RESPONSE); 228 } catch (SerializationException e) { 229 return Collections.emptyList(); 230 } 231 } 232 233 /** 234 * Extract a list of SignedCertificateTimestamp embedded in an X509 certificate. 235 * 236 * If the certificate does not contain any SCT extension, or the encompassing encoded list fails 237 * to be parsed, an empty list is returned. Individual SCTs which fail to be parsed are ignored. 238 */ 239 private List<SignedCertificateTimestamp> getSCTsFromX509Extension(OpenSSLX509Certificate leaf) { 240 byte[] extData = leaf.getExtensionValue(CTConstants.X509_SCT_LIST_OID); 241 if (extData == null) { 242 return Collections.emptyList(); 243 } 244 245 try { 246 return getSCTsFromSCTList( 247 Serialization.readDEROctetString( 248 Serialization.readDEROctetString(extData)), 249 SignedCertificateTimestamp.Origin.EMBEDDED); 250 } catch (SerializationException e) { 251 return Collections.emptyList(); 252 } 253 } 254 } 255 256