diff options
Diffstat (limited to '')
-rw-r--r-- | security/manager/ssl/builtins/src/internal.rs | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/security/manager/ssl/builtins/src/internal.rs b/security/manager/ssl/builtins/src/internal.rs new file mode 100644 index 0000000000..09de4e5b79 --- /dev/null +++ b/security/manager/ssl/builtins/src/internal.rs @@ -0,0 +1,344 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use pkcs11_bindings::nss::*; +use pkcs11_bindings::*; + +use smallvec::SmallVec; + +use crate::certdata::*; + +// The token stores 2N+1 objects: one NSS root list object, N certificate objects, and N trust +// objects. +// +// Internally, the token identifies each object by its ObjectClass (RootList, Certificate, +// or Trust) and its index in the list of objects of the same class. +// +// The PKCS#11 interface, on the other hand, identifies each object with a unique, non-zero, +// unsigned long. This ulong is referred to as the object's CK_OBJECT_HANDLE. +// +// We're free to choose the mapping between ObjectHandles and CK_OBJECT_HANDLEs. Currently we +// encode the ObjectClass in the low 2 bits of the CK_OBJECT_HANDLE and the index in the higher +// bits. We use the values 1, 2, and 3 for ObjectClass to avoid using 0 as a CK_OBJECT_HANDLE. +// +#[derive(Clone, Copy)] +pub enum ObjectClass { + RootList = 1, + Certificate = 2, + Trust = 3, +} + +#[derive(Clone, Copy)] +pub struct ObjectHandle { + class: ObjectClass, + index: usize, +} + +impl TryFrom<CK_OBJECT_HANDLE> for ObjectHandle { + type Error = (); + fn try_from(handle: CK_OBJECT_HANDLE) -> Result<Self, Self::Error> { + if let Ok(handle) = usize::try_from(handle) { + let index = handle >> 2; + let class = match handle & 3 { + 1 if index == 0 => ObjectClass::RootList, + 2 if index < BUILTINS.len() => ObjectClass::Certificate, + 3 if index < BUILTINS.len() => ObjectClass::Trust, + _ => return Err(()), + }; + Ok(ObjectHandle { class, index }) + } else { + Err(()) + } + } +} + +impl From<ObjectHandle> for CK_OBJECT_HANDLE { + fn from(object_handle: ObjectHandle) -> CK_OBJECT_HANDLE { + match CK_OBJECT_HANDLE::try_from(object_handle.index) { + Ok(index) => (index << 2) | (object_handle.class as CK_OBJECT_HANDLE), + Err(_) => 0, + } + } +} + +pub fn get_attribute(attribute: CK_ATTRIBUTE_TYPE, object: &ObjectHandle) -> Option<&'static [u8]> { + match object.class { + ObjectClass::RootList => get_root_list_attribute(attribute), + ObjectClass::Certificate => get_cert_attribute(attribute, &BUILTINS[object.index]), + ObjectClass::Trust => get_trust_attribute(attribute, &BUILTINS[object.index]), + } +} + +// Every attribute that appears in certdata.txt must have a corresponding match arm in one of the +// get_*_attribute functions. +// +fn get_root_list_attribute(attribute: CK_ATTRIBUTE_TYPE) -> Option<&'static [u8]> { + match attribute { + CKA_CLASS => Some(CKO_NSS_BUILTIN_ROOT_LIST_BYTES), + CKA_TOKEN => Some(CK_TRUE_BYTES), + CKA_PRIVATE => Some(CK_FALSE_BYTES), + CKA_MODIFIABLE => Some(CK_FALSE_BYTES), + CKA_LABEL => Some(ROOT_LIST_LABEL), + _ => None, + } +} + +fn get_cert_attribute(attribute: CK_ATTRIBUTE_TYPE, cert: &Root) -> Option<&'static [u8]> { + match attribute { + CKA_CLASS => Some(CKO_CERTIFICATE_BYTES), + CKA_TOKEN => Some(CK_TRUE_BYTES), + CKA_PRIVATE => Some(CK_FALSE_BYTES), + CKA_MODIFIABLE => Some(CK_FALSE_BYTES), + CKA_LABEL => Some(cert.label.as_bytes()), + CKA_CERTIFICATE_TYPE => Some(CKC_X_509_BYTES), + CKA_SUBJECT => Some(cert.der_name), + CKA_ID => Some(b"0\0"), // null terminated to match C implementation + CKA_ISSUER => Some(cert.der_name), + CKA_SERIAL_NUMBER => Some(cert.der_serial), + CKA_VALUE => Some(cert.der_cert), + CKA_NSS_MOZILLA_CA_POLICY => cert.mozilla_ca_policy, + CKA_NSS_SERVER_DISTRUST_AFTER => cert.server_distrust_after, + CKA_NSS_EMAIL_DISTRUST_AFTER => cert.email_distrust_after, + _ => None, + } +} + +fn get_trust_attribute(attribute: CK_ATTRIBUTE_TYPE, cert: &Root) -> Option<&'static [u8]> { + match attribute { + CKA_CLASS => Some(CKO_NSS_TRUST_BYTES), + CKA_TOKEN => Some(CK_TRUE_BYTES), + CKA_PRIVATE => Some(CK_FALSE_BYTES), + CKA_MODIFIABLE => Some(CK_FALSE_BYTES), + CKA_LABEL => Some(cert.label.as_bytes()), + CKA_CERT_SHA1_HASH => Some(cert.sha1), + CKA_CERT_MD5_HASH => Some(cert.md5), + CKA_ISSUER => Some(cert.der_name), + CKA_SERIAL_NUMBER => Some(cert.der_serial), + CKA_TRUST_STEP_UP_APPROVED => Some(CK_FALSE_BYTES), + CKA_TRUST_SERVER_AUTH => Some(cert.trust_server), + CKA_TRUST_EMAIL_PROTECTION => Some(cert.trust_email), + CKA_TRUST_CODE_SIGNING => Some(CKT_NSS_MUST_VERIFY_TRUST_BYTES), + _ => None, + } +} + +// A query matches an object if each term matches some attribute of the object. A search result is +// a list of object handles. Typical queries yield zero or one results, so we optimize for this +// case. +// +pub type Query<'a> = [(CK_ATTRIBUTE_TYPE, &'a [u8])]; +pub type SearchResult = SmallVec<[ObjectHandle; 1]>; + +pub fn search(query: &Query) -> SearchResult { + // The BUILTINS list is sorted by name. So if the query includes a CKA_SUBJECT or CKA_ISSUER + // field we can binary search. + for &(attr, value) in query { + if attr == CKA_SUBJECT || attr == CKA_ISSUER { + return search_by_name(value, query); + } + } + + let mut results: SearchResult = SearchResult::default(); + + // A query with no name term might match the root list object + if match_root_list(query) { + results.push(ObjectHandle { + class: ObjectClass::RootList, + index: 0, + }); + } + + // A query with a CKA_CLASS term matches exactly one type of object, and we should avoid + // iterating over BUILTINS when CKO_CLASS is neither CKO_CERTIFICATE_BYTES nor + // CKO_NSS_TRUST_BYTES. + let mut maybe_cert = true; + let mut maybe_trust = true; + for &(attr, value) in query { + if attr == CKA_CLASS { + maybe_cert = value.eq(CKO_CERTIFICATE_BYTES); + maybe_trust = value.eq(CKO_NSS_TRUST_BYTES); + break; + } + } + + if !(maybe_cert || maybe_trust) { + return results; // The root list or nothing. + } + + for (index, builtin) in BUILTINS.iter().enumerate() { + if maybe_cert && match_cert(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Certificate, + index, + }); + } + if maybe_trust && match_trust(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Trust, + index, + }); + } + } + results +} + +fn search_by_name(name: &[u8], query: &Query) -> SearchResult { + let mut results: SearchResult = SearchResult::default(); + + let index = match BUILTINS.binary_search_by_key(&name, |r| r.der_name) { + Ok(index) => index, + _ => return results, + }; + + // binary search returned a matching index, but maybe not the smallest + let mut min = index; + while min > 0 && name.eq(BUILTINS[min - 1].der_name) { + min -= 1; + } + + // ... and maybe not the largest. + let mut max = index; + while max < BUILTINS.len() - 1 && name.eq(BUILTINS[max + 1].der_name) { + max += 1; + } + + for (index, builtin) in BUILTINS.iter().enumerate().take(max + 1).skip(min) { + if match_cert(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Certificate, + index, + }); + } + if match_trust(query, builtin) { + results.push(ObjectHandle { + class: ObjectClass::Trust, + index, + }); + } + } + + results +} + +fn match_root_list(query: &Query) -> bool { + for &(typ, x) in query { + match get_root_list_attribute(typ) { + Some(y) if x.eq(y) => (), + _ => return false, + } + } + true +} + +fn match_cert(query: &Query, cert: &Root) -> bool { + for &(typ, x) in query { + match get_cert_attribute(typ, cert) { + Some(y) if x.eq(y) => (), + _ => return false, + } + } + true +} + +fn match_trust(query: &Query, cert: &Root) -> bool { + for &(typ, x) in query { + match get_trust_attribute(typ, cert) { + Some(y) if x.eq(y) => (), + _ => return false, + } + } + true +} + +#[cfg(test)] +mod internal_tests { + use crate::certdata::BUILTINS; + use crate::internal::*; + use pkcs11_bindings::*; + + // commented out to avoid vendoring x509_parser + // fn is_valid_utctime(utctime: &[u8]) -> bool { + // /* TODO: actual validation */ + // utctime.len() == 13 + // } + // #[test] + // fn test_certdata() { + // for root in BUILTINS { + // // the der_cert field is valid DER + // let parsed_cert = X509Certificate::from_der(root.der_cert); + // assert!(parsed_cert.is_ok()); + + // // the der_cert field has no trailing data + // let (trailing, parsed_cert) = parsed_cert.unwrap(); + // assert!(trailing.is_empty()); + + // // the der_serial field matches the encoded serial + // assert!(root.der_serial.len() > 2); + // assert!(root.der_serial[0] == 0x02); // der integer + // assert!(root.der_serial[1] <= 20); // no more than 20 bytes long + // assert!(root.der_serial[1] as usize == root.der_serial.len() - 2); + // assert!(parsed_cert.raw_serial().eq(&root.der_serial[2..])); + + // // the der_name field matches the encoded subject + // assert!(parsed_cert.subject.as_raw().eq(root.der_name)); + + // // the der_name field matches the encoded issuer + // assert!(parsed_cert.issuer.as_raw().eq(root.der_name)); + + // // The server_distrust_after field is None or a valid UTC time + // if let Some(utctime) = root.server_distrust_after { + // assert!(is_valid_utctime(&utctime)); + // } + + // // The email_distrust_after field is None or a valid UTC time + // if let Some(utctime) = root.email_distrust_after { + // assert!(is_valid_utctime(&utctime)); + // } + + // assert!( + // root.trust_server == CKT_NSS_MUST_VERIFY_TRUST_BYTES + // || root.trust_server == CKT_NSS_TRUSTED_DELEGATOR_BYTES + // || root.trust_server == CKT_NSS_NOT_TRUSTED_BYTES + // ); + // assert!( + // root.trust_email == CKT_NSS_MUST_VERIFY_TRUST_BYTES + // || root.trust_email == CKT_NSS_TRUSTED_DELEGATOR_BYTES + // || root.trust_email == CKT_NSS_NOT_TRUSTED_BYTES + // ); + // } + // } + + #[test] + fn test_builtins_sorted() { + for i in 0..(BUILTINS.len() - 1) { + assert!(BUILTINS[i].der_name.le(BUILTINS[i + 1].der_name)); + } + } + + #[test] + fn test_search() { + // search for an element that will not be found + let result = search(&[(CKA_TOKEN, &[CK_FALSE])]); + assert_eq!(result.len(), 0); + + // search for root list + let result = search(&[(CKA_CLASS, CKO_NSS_BUILTIN_ROOT_LIST_BYTES)]); + assert!(result.len() == 1); + + // search by name + let result = search(&[ + (CKA_CLASS, CKO_CERTIFICATE_BYTES), + (CKA_SUBJECT, BUILTINS[0].der_name), + ]); + assert!(result.len() >= 1); + + // search by issuer and serial + let result = search(&[ + (CKA_ISSUER, BUILTINS[0].der_name), + (CKA_SERIAL_NUMBER, BUILTINS[0].der_serial), + ]); + assert!(result.len() >= 1); + } +} |