tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

internal.rs (11940B)


      1 /* -*- Mode: rust; rust-indent-offset: 4 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 use pkcs11_bindings::*;
      7 
      8 use smallvec::SmallVec;
      9 
     10 use crate::certdata::*;
     11 
     12 // The token stores 2N+1 objects: one NSS root list object, N certificate objects, and N trust
     13 // objects.
     14 //
     15 // Internally, the token identifies each object by its ObjectClass (RootList, Certificate,
     16 // or Trust) and its index in the list of objects of the same class.
     17 //
     18 // The PKCS#11 interface, on the other hand, identifies each object with a unique, non-zero,
     19 // unsigned long. This ulong is referred to as the object's CK_OBJECT_HANDLE.
     20 //
     21 // We're free to choose the mapping between ObjectHandles and CK_OBJECT_HANDLEs. Currently we
     22 // encode the ObjectClass in the low 2 bits of the CK_OBJECT_HANDLE and the index in the higher
     23 // bits. We use the values 1, 2, and 3 for ObjectClass to avoid using 0 as a CK_OBJECT_HANDLE.
     24 //
     25 #[derive(Clone, Copy)]
     26 pub enum ObjectClass {
     27    RootList = 1,
     28    Certificate = 2,
     29    Trust = 3,
     30 }
     31 
     32 #[derive(Clone, Copy)]
     33 pub struct ObjectHandle {
     34    class: ObjectClass,
     35    index: usize,
     36 }
     37 
     38 impl TryFrom<CK_OBJECT_HANDLE> for ObjectHandle {
     39    type Error = ();
     40    fn try_from(handle: CK_OBJECT_HANDLE) -> Result<Self, Self::Error> {
     41        if let Ok(handle) = usize::try_from(handle) {
     42            let index = handle >> 2;
     43            let class = match handle & 3 {
     44                1 if index == 0 => ObjectClass::RootList,
     45                2 if index < BUILTINS.len() => ObjectClass::Certificate,
     46                3 if index < BUILTINS.len() => ObjectClass::Trust,
     47                _ => return Err(()),
     48            };
     49            Ok(ObjectHandle { class, index })
     50        } else {
     51            Err(())
     52        }
     53    }
     54 }
     55 
     56 impl From<ObjectHandle> for CK_OBJECT_HANDLE {
     57    fn from(object_handle: ObjectHandle) -> CK_OBJECT_HANDLE {
     58        match CK_OBJECT_HANDLE::try_from(object_handle.index) {
     59            Ok(index) => (index << 2) | (object_handle.class as CK_OBJECT_HANDLE),
     60            Err(_) => 0,
     61        }
     62    }
     63 }
     64 
     65 pub fn get_attribute(attribute: CK_ATTRIBUTE_TYPE, object: &ObjectHandle) -> Option<&'static [u8]> {
     66    match object.class {
     67        ObjectClass::RootList => get_root_list_attribute(attribute),
     68        ObjectClass::Certificate => get_cert_attribute(attribute, &BUILTINS[object.index]),
     69        ObjectClass::Trust => get_trust_attribute(attribute, &BUILTINS[object.index]),
     70    }
     71 }
     72 
     73 // Every attribute that appears in certdata.txt must have a corresponding match arm in one of the
     74 // get_*_attribute functions.
     75 //
     76 fn get_root_list_attribute(attribute: CK_ATTRIBUTE_TYPE) -> Option<&'static [u8]> {
     77    match attribute {
     78        CKA_CLASS => Some(CKO_NSS_BUILTIN_ROOT_LIST_BYTES),
     79        CKA_TOKEN => Some(CK_TRUE_BYTES),
     80        CKA_PRIVATE => Some(CK_FALSE_BYTES),
     81        CKA_MODIFIABLE => Some(CK_FALSE_BYTES),
     82        CKA_LABEL => Some(&ROOT_LIST_LABEL[..]),
     83        _ => None,
     84    }
     85 }
     86 
     87 fn get_cert_attribute(attribute: CK_ATTRIBUTE_TYPE, cert: &Root) -> Option<&[u8]> {
     88    match attribute {
     89        CKA_CLASS => Some(CKO_CERTIFICATE_BYTES),
     90        CKA_TOKEN => Some(CK_TRUE_BYTES),
     91        CKA_PRIVATE => Some(CK_FALSE_BYTES),
     92        CKA_MODIFIABLE => Some(CK_FALSE_BYTES),
     93        CKA_LABEL => Some(cert.label.as_bytes()),
     94        CKA_CERTIFICATE_TYPE => Some(CKC_X_509_BYTES),
     95        CKA_SUBJECT => Some(cert.der_name()),
     96        CKA_ID => Some(b"0\0"), // null terminated to match C implementation
     97        CKA_ISSUER => Some(cert.der_name()),
     98        CKA_SERIAL_NUMBER => Some(cert.der_serial()),
     99        CKA_VALUE => Some(cert.der_cert),
    100        nss::CKA_NSS_MOZILLA_CA_POLICY => cert.mozilla_ca_policy,
    101        nss::CKA_NSS_SERVER_DISTRUST_AFTER => cert.server_distrust_after,
    102        nss::CKA_NSS_EMAIL_DISTRUST_AFTER => cert.email_distrust_after,
    103        _ => None,
    104    }
    105 }
    106 
    107 fn get_trust_attribute(attribute: CK_ATTRIBUTE_TYPE, cert: &Root) -> Option<&[u8]> {
    108    match attribute {
    109        CKA_CLASS => Some(CKO_TRUST_BYTES),
    110        CKA_TOKEN => Some(CK_TRUE_BYTES),
    111        CKA_PRIVATE => Some(CK_FALSE_BYTES),
    112        CKA_MODIFIABLE => Some(CK_FALSE_BYTES),
    113        CKA_LABEL => Some(cert.label.as_bytes()),
    114        CKA_NAME_HASH_ALGORITHM => Some(CKM_SHA256_BYTES),
    115        CKA_HASH_OF_CERTIFICATE => Some(&cert.sha256[..]),
    116        CKA_ISSUER => Some(cert.der_name()),
    117        CKA_SERIAL_NUMBER => Some(cert.der_serial()),
    118        nss::CKA_PKCS_TRUST_SERVER_AUTH => Some(cert.trust_server),
    119        nss::CKA_PKCS_TRUST_CLIENT_AUTH => Some(CKT_TRUST_MUST_VERIFY_TRUST_BYTES),
    120        nss::CKA_PKCS_TRUST_CODE_SIGNING => Some(CKT_TRUST_MUST_VERIFY_TRUST_BYTES),
    121        nss::CKA_PKCS_TRUST_EMAIL_PROTECTION => Some(cert.trust_email),
    122        _ => None,
    123    }
    124 }
    125 
    126 // A query matches an object if each term matches some attribute of the object. A search result is
    127 // a list of object handles. Typical queries yield zero or one results, so we optimize for this
    128 // case.
    129 //
    130 pub type Query<'a> = [(CK_ATTRIBUTE_TYPE, &'a [u8])];
    131 pub type SearchResult = SmallVec<[ObjectHandle; 1]>;
    132 
    133 pub fn search(query: &Query) -> SearchResult {
    134    // The BUILTINS list is sorted by name. So if the query includes a CKA_SUBJECT or CKA_ISSUER
    135    // field we can binary search.
    136    for &(attr, value) in query {
    137        if attr == CKA_SUBJECT || attr == CKA_ISSUER {
    138            return search_by_name(value, query);
    139        }
    140    }
    141 
    142    let mut results: SearchResult = SearchResult::default();
    143 
    144    // A query with no name term might match the root list object
    145    if match_root_list(query) {
    146        results.push(ObjectHandle {
    147            class: ObjectClass::RootList,
    148            index: 0,
    149        });
    150    }
    151 
    152    // A query with a CKA_CLASS term matches exactly one type of object, and we should avoid
    153    // iterating over BUILTINS when CKO_CLASS is neither CKO_CERTIFICATE_BYTES nor
    154    // CKO_NSS_TRUST_BYTES.
    155    let mut maybe_cert = true;
    156    let mut maybe_trust = true;
    157    for &(attr, value) in query {
    158        if attr == CKA_CLASS {
    159            maybe_cert = value.eq(CKO_CERTIFICATE_BYTES);
    160            maybe_trust = value.eq(CKO_TRUST_BYTES);
    161            break;
    162        }
    163    }
    164 
    165    if !(maybe_cert || maybe_trust) {
    166        return results; // The root list or nothing.
    167    }
    168 
    169    for (index, builtin) in BUILTINS.iter().enumerate() {
    170        if maybe_cert && match_cert(query, builtin) {
    171            results.push(ObjectHandle {
    172                class: ObjectClass::Certificate,
    173                index,
    174            });
    175        }
    176        if maybe_trust && match_trust(query, builtin) {
    177            results.push(ObjectHandle {
    178                class: ObjectClass::Trust,
    179                index,
    180            });
    181        }
    182    }
    183    results
    184 }
    185 
    186 fn search_by_name(name: &[u8], query: &Query) -> SearchResult {
    187    let mut results: SearchResult = SearchResult::default();
    188 
    189    let index = match BUILTINS.binary_search_by_key(&name, |r| r.der_name()) {
    190        Ok(index) => index,
    191        _ => return results,
    192    };
    193 
    194    // binary search returned a matching index, but maybe not the smallest
    195    let mut min = index;
    196    while min > 0 && name.eq(BUILTINS[min - 1].der_name()) {
    197        min -= 1;
    198    }
    199 
    200    // ... and maybe not the largest.
    201    let mut max = index;
    202    while max < BUILTINS.len() - 1 && name.eq(BUILTINS[max + 1].der_name()) {
    203        max += 1;
    204    }
    205 
    206    for (index, builtin) in BUILTINS.iter().enumerate().take(max + 1).skip(min) {
    207        if match_cert(query, builtin) {
    208            results.push(ObjectHandle {
    209                class: ObjectClass::Certificate,
    210                index,
    211            });
    212        }
    213        if match_trust(query, builtin) {
    214            results.push(ObjectHandle {
    215                class: ObjectClass::Trust,
    216                index,
    217            });
    218        }
    219    }
    220 
    221    results
    222 }
    223 
    224 fn match_root_list(query: &Query) -> bool {
    225    for &(typ, x) in query {
    226        match get_root_list_attribute(typ) {
    227            Some(y) if x.eq(y) => (),
    228            _ => return false,
    229        }
    230    }
    231    true
    232 }
    233 
    234 fn match_cert(query: &Query, cert: &Root) -> bool {
    235    for &(typ, x) in query {
    236        match get_cert_attribute(typ, cert) {
    237            Some(y) if x.eq(y) => (),
    238            _ => return false,
    239        }
    240    }
    241    true
    242 }
    243 
    244 fn match_trust(query: &Query, cert: &Root) -> bool {
    245    for &(typ, x) in query {
    246        match get_trust_attribute(typ, cert) {
    247            Some(y) if x.eq(y) => (),
    248            _ => return false,
    249        }
    250    }
    251    true
    252 }
    253 
    254 #[cfg(test)]
    255 mod internal_tests {
    256    use crate::certdata::BUILTINS;
    257    use crate::internal::*;
    258    use pkcs11_bindings::*;
    259 
    260    // commented out to avoid vendoring x509_parser
    261    // fn is_valid_utctime(utctime: &[u8]) -> bool {
    262    //     /* TODO: actual validation */
    263    //     utctime.len() == 13
    264    // }
    265    // #[test]
    266    // fn test_certdata() {
    267    //     for root in BUILTINS {
    268    //         // the der_cert field is valid DER
    269    //         let parsed_cert = X509Certificate::from_der(root.der_cert);
    270    //         assert!(parsed_cert.is_ok());
    271 
    272    //         // the der_cert field has no trailing data
    273    //         let (trailing, parsed_cert) = parsed_cert.unwrap();
    274    //         assert!(trailing.is_empty());
    275 
    276    //         // the der_serial field matches the encoded serial
    277    //         assert!(root.der_serial.len() > 2);
    278    //         assert!(root.der_serial[0] == 0x02); // der integer
    279    //         assert!(root.der_serial[1] <= 20); // no more than 20 bytes long
    280    //         assert!(root.der_serial[1] as usize == root.der_serial.len() - 2);
    281    //         assert!(parsed_cert.raw_serial().eq(&root.der_serial[2..]));
    282 
    283    //         // the der_name field matches the encoded subject
    284    //         assert!(parsed_cert.subject.as_raw().eq(root.der_name));
    285 
    286    //         // the der_name field matches the encoded issuer
    287    //         assert!(parsed_cert.issuer.as_raw().eq(root.der_name));
    288 
    289    //         // The server_distrust_after field is None or a valid UTC time
    290    //         if let Some(utctime) = root.server_distrust_after {
    291    //             assert!(is_valid_utctime(&utctime));
    292    //         }
    293 
    294    //         // The email_distrust_after field is None or a valid UTC time
    295    //         if let Some(utctime) = root.email_distrust_after {
    296    //             assert!(is_valid_utctime(&utctime));
    297    //         }
    298 
    299    //         assert!(
    300    //             root.trust_server == CKT_NSS_MUST_VERIFY_TRUST_BYTES
    301    //                 || root.trust_server == CKT_NSS_TRUSTED_DELEGATOR_BYTES
    302    //                 || root.trust_server == CKT_NSS_NOT_TRUSTED_BYTES
    303    //         );
    304    //         assert!(
    305    //             root.trust_email == CKT_NSS_MUST_VERIFY_TRUST_BYTES
    306    //                 || root.trust_email == CKT_NSS_TRUSTED_DELEGATOR_BYTES
    307    //                 || root.trust_email == CKT_NSS_NOT_TRUSTED_BYTES
    308    //         );
    309    //     }
    310    // }
    311 
    312    #[test]
    313    fn test_builtins_sorted() {
    314        for i in 0..(BUILTINS.len() - 1) {
    315            assert!(BUILTINS[i].der_name.le(BUILTINS[i + 1].der_name));
    316        }
    317    }
    318 
    319    #[test]
    320    fn test_search() {
    321        // search for an element that will not be found
    322        let result = search(&[(CKA_TOKEN, &[CK_FALSE])]);
    323        assert_eq!(result.len(), 0);
    324 
    325        // search for root list
    326        let result = search(&[(CKA_CLASS, CKO_NSS_BUILTIN_ROOT_LIST_BYTES)]);
    327        assert!(result.len() == 1);
    328 
    329        // search by name
    330        let result = search(&[
    331            (CKA_CLASS, CKO_CERTIFICATE_BYTES),
    332            (CKA_SUBJECT, BUILTINS[0].der_name),
    333        ]);
    334        assert!(result.len() >= 1);
    335 
    336        // search by issuer and serial
    337        let result = search(&[
    338            (CKA_ISSUER, BUILTINS[0].der_name),
    339            (CKA_SERIAL_NUMBER, BUILTINS[0].der_serial),
    340        ]);
    341        assert!(result.len() >= 1);
    342    }
    343 }