Home | History | Annotate | Download | only in ct
      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