/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
externcrate wr_malloc_size_of; use wr_malloc_size_of as malloc_size_of;
use base64::prelude::*; use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; use clubcard::{ApproximateSizeOf, Queryable}; use clubcard_crlite::{
CRLiteClubcard, CRLiteKey, CRLiteQuery, CRLiteStatus, IssuerSpkiHash, LogId, Timestamp,
}; use crossbeam_utils::atomic::AtomicCell; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use moz_task::{create_background_task_queue, is_main_thread, Task, TaskRunnable}; use nserror::{
nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_SAME_THREAD, NS_ERROR_NULL_POINTER,
NS_ERROR_UNEXPECTED, NS_OK,
}; use nsstring::{nsACString, nsCStr, nsCString, nsString}; use rkv::backend::{BackendEnvironmentBuilder, SafeMode, SafeModeDatabase, SafeModeEnvironment}; use rkv::{StoreError, StoreOptions, Value}; use sha2::{Digest, Sha256}; use std::convert::TryInto; use std::ffi::{CString, OsString}; use std::fmt::Display; use std::fs::{create_dir_all, remove_file, File}; use std::io::{BufRead, BufReader}; use std::mem::size_of; use std::path::{Path, PathBuf}; use std::str; use std::sync::{Arc, RwLock}; use std::time::{SystemTime, UNIX_EPOCH}; use storage_variant::VariantType; use thin_vec::ThinVec; use xpcom::interfaces::{
nsICRLiteTimestamp, nsICertInfo, nsICertStorage, nsICertStorageCallback, nsIFile,
nsIHandleReportCallback, nsIIssuerAndSerialRevocationState, nsIMemoryReporter,
nsIMemoryReporterManager, nsIProperties, nsIRevocationState, nsISerialEventTarget,
nsISubjectAndPubKeyRevocationState, nsISupports,
}; use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom};
/// `SecurityStateError` is a type to represent errors in accessing or /// modifying security state. #[derive(Debug)] struct SecurityStateError {
message: String,
}
impl<T: Display> From<T> for SecurityStateError { /// Creates a new instance of `SecurityStateError` from something that /// implements the `Display` trait. fn from(err: T) -> SecurityStateError {
SecurityStateError {
message: format!("{}", err),
}
}
}
/// `SecurityState` struct SecurityState {
profile_path: PathBuf,
env_and_store: Option<EnvAndStore>,
crlite_filters: Vec<Filter>, /// Tracks the number of asynchronous operations which have been dispatched but not completed.
remaining_ops: i32,
}
impl SecurityState { pubfn new(profile_path: PathBuf) -> SecurityState { // Since this gets called on the main thread, we don't actually want to open the DB yet. // We do this on-demand later, when we're probably on a certificate verification thread.
SecurityState {
profile_path,
env_and_store: None,
crlite_filters: vec![],
remaining_ops: 0,
}
}
let store_path = get_store_path(&self.profile_path)?;
// Open the store in read-write mode to create it (if needed) and migrate data from the old // store (if any). // If opening initially fails, try to remove and recreate the database. Consumers will // repopulate the database as necessary if this happens (see bug 1546361). let env = make_env(store_path.as_path()).or_else(|_| {
remove_db(store_path.as_path())?;
make_env(store_path.as_path())
})?; let store = env.open_single("cert_storage", StoreOptions::create())?;
// if the profile has a revocations.txt, migrate it and remove the file letmut revocations_path = self.profile_path.clone();
revocations_path.push("revocations.txt"); if revocations_path.exists() {
SecurityState::migrate(&revocations_path, &env, &store)?;
remove_file(revocations_path)?;
}
// We already returned early if env_and_store was Some, so this should take the None branch. matchself.env_and_store.replace(EnvAndStore { env, store }) {
Some(_) => Err(SecurityStateError::from( "env and store already initialized? (did we mess up our threading model?)",
)),
None => Ok(()),
}?; self.load_crlite_filter()?;
Ok(())
}
fn migrate(
revocations_path: &PathBuf,
env: &Rkv,
store: &SingleStore,
) -> Result<(), SecurityStateError> { let f = File::open(revocations_path)?; let file = BufReader::new(f); let value = Value::I64(nsICertStorage::STATE_ENFORCE as i64); letmut writer = env.write()?;
// Add the data from revocations.txt letmut dn: Option<Vec<u8>> = None; for line in file.lines() { let l = match line.map_err(|_| SecurityStateError::from("io error reading line data")) {
Ok(data) => data,
Err(e) => return Err(e),
}; if l.len() == 0 || l.starts_with("#") { continue;
} let leading_char = match l.chars().next() {
Some(c) => c,
None => { return Err(SecurityStateError::from( "couldn't get char from non-empty str?",
));
}
}; // In future, we can maybe log migration failures. For now, ignore decoding and storage // errors and attempt to continue. // Check if we have a new DN if leading_char != '\t' && leading_char != ' ' { iflet Ok(decoded_dn) = BASE64_STANDARD.decode(&l) {
dn = Some(decoded_dn);
} continue;
} let l_sans_prefix = match BASE64_STANDARD.decode(&l[1..]) {
Ok(decoded) => decoded,
Err(_) => continue,
}; iflet Some(name) = &dn { if leading_char == '\t' { let _ = store.put(
&mut writer,
&make_key!(PREFIX_REV_SPK, name, &l_sans_prefix),
&value,
);
} else { let _ = store.put(
&mut writer,
&make_key!(PREFIX_REV_IS, name, &l_sans_prefix),
&value,
);
}
}
}
writer.commit()?;
Ok(())
}
fn read_entry(&self, key: &[u8]) -> Result<Option<i16>, SecurityStateError> { let env_and_store = matchself.env_and_store.as_ref() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; let reader = env_and_store.env.read()?; match env_and_store.store.get(&reader, key) {
Ok(Some(Value::I64(i))) => {
Ok(Some(i.try_into().map_err(|_| {
SecurityStateError::from("Stored value out of range for i16")
})?))
}
Ok(None) => Ok(None),
Ok(_) => Err(SecurityStateError::from( "Unexpected type when trying to get a Value::I64",
)),
Err(_) => Err(SecurityStateError::from( "There was a problem getting the value",
)),
}
}
pubfn get_has_prior_data(&self, data_type: u8) -> Result<bool, SecurityStateError> { let env_and_store = matchself.env_and_store.as_ref() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; let reader = env_and_store.env.read()?; match env_and_store
.store
.get(&reader, &make_key!(PREFIX_DATA_TYPE, &[data_type]))
{
Ok(Some(Value::Bool(true))) => Ok(true),
Ok(None) => Ok(false),
Ok(_) => Err(SecurityStateError::from( "Unexpected type when trying to get a Value::Bool",
)),
Err(_) => Err(SecurityStateError::from( "There was a problem getting the value",
)),
}
}
pubfn set_batch_state(
&mutself,
entries: &[EncodedSecurityState],
typ: u8,
) -> Result<(), SecurityStateError> { let env_and_store = matchself.env_and_store.as_mut() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; letmut writer = env_and_store.env.write()?; // Make a note that we have prior data of the given type now.
env_and_store.store.put(
&mut writer,
&make_key!(PREFIX_DATA_TYPE, &[typ]),
&Value::Bool(true),
)?;
for entry in entries { let key = match entry.key() {
Ok(key) => key,
Err(e) => {
warn!("error base64-decoding key parts - ignoring: {}", e.message); continue;
}
};
env_and_store
.store
.put(&mut writer, &key, &Value::I64(entry.state() as i64))?;
}
// Drop any existing crlite filters self.crlite_filters.clear();
// Delete the backing data for the previous collection of filters. We may be migrating // from a bloom filter cascade, so check for and delete "coverage", "enrollment", and // "stash" files in addition to "delta" and "filter" files. for entry in std::fs::read_dir(&store_path)? { let Ok(entry) = entry else { continue;
}; let entry_path = entry.path(); let extension = entry_path
.extension()
.map(|os_str| os_str.to_str())
.flatten(); if extension == Some("coverage")
|| extension == Some("delta")
|| extension == Some("enrollment")
|| extension == Some("filter")
|| extension == Some("stash")
{ let _ = std::fs::remove_file(entry_path);
}
}
// Write the new full filter.
std::fs::write(store_path.join("crlite.filter"), &filter)?;
pubfn get_crlite_revocation_state(
&self,
issuer_spki: &[u8],
serial_number: &[u8],
timestamps: &[CRLiteTimestamp],
) -> i16 { if !self.is_crlite_fresh() { return nsICertStorage::STATE_NO_FILTER;
} ifself.crlite_filters.is_empty() { // This can only happen if the backing file was deleted or if it or our database has // become corrupted. In any case, we have no information. return nsICertStorage::STATE_NO_FILTER;
} letmut maybe_good = false; letmut covered = false;
let issuer_spki_hash = IssuerSpkiHash(Sha256::digest(issuer_spki).into()); let clubcard_crlite_key = CRLiteKey::new(&issuer_spki_hash, serial_number); for filter in &self.crlite_filters { match filter.has(&clubcard_crlite_key, timestamps) {
nsICertStorage::STATE_ENFORCE => return nsICertStorage::STATE_ENFORCE,
nsICertStorage::STATE_UNSET => maybe_good = true,
nsICertStorage::STATE_NOT_ENROLLED => covered = true,
_ => (),
}
} if maybe_good { return nsICertStorage::STATE_UNSET;
} if covered { return nsICertStorage::STATE_NOT_ENROLLED;
}
nsICertStorage::STATE_NOT_COVERED
}
// To store certificates, we create a Cert out of each given cert, subject, and trust tuple. We // hash each certificate with sha-256 to obtain a unique* key for that certificate, and we store // the Cert in the database. We also look up or create a CertHashList for the given subject and // add the new certificate's hash if it isn't present in the list. If it wasn't present, we // write out the updated CertHashList. // *By the pigeon-hole principle, there exist collisions for sha-256, so this key is not // actually unique. We rely on the assumption that sha-256 is a cryptographically strong hash. // If an adversary can find two different certificates with the same sha-256 hash, they can // probably forge a sha-256-based signature, so assuming the keys we create here are unique is // not a security issue. pubfn add_certs(
&mutself,
certs: &[(nsCString, nsCString, i16)],
) -> Result<(), SecurityStateError> { let env_and_store = matchself.env_and_store.as_mut() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; letmut writer = env_and_store.env.write()?; // Make a note that we have prior cert data now.
env_and_store.store.put(
&mut writer,
&make_key!(PREFIX_DATA_TYPE, &[nsICertStorage::DATA_TYPE_CERTIFICATE]),
&Value::Bool(true),
)?;
for (cert_der_base64, subject_base64, trust) in certs { let cert_der = match BASE64_STANDARD.decode(&cert_der_base64) {
Ok(cert_der) => cert_der,
Err(e) => {
warn!("error base64-decoding cert - skipping: {}", e); continue;
}
}; let subject = match BASE64_STANDARD.decode(&subject_base64) {
Ok(subject) => subject,
Err(e) => {
warn!("error base64-decoding subject - skipping: {}", e); continue;
}
}; letmut digest = Sha256::default();
digest.update(&cert_der); let cert_hash = digest.finalize(); let cert_key = make_key!(PREFIX_CERT, &cert_hash); let cert = Cert::new(&cert_der, &subject, *trust)?;
env_and_store
.store
.put(&mut writer, &cert_key, &Value::Blob(&cert.to_bytes()?))?; let subject_key = make_key!(PREFIX_SUBJECT, &subject); let empty_vec = Vec::new(); let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? {
Some(Value::Blob(hashes)) => hashes.to_owned(),
Some(_) => empty_vec,
None => empty_vec,
}; let new_cert_hash_list = CertHashList::add(&old_cert_hash_list, &cert_hash)?; if new_cert_hash_list.len() != old_cert_hash_list.len() {
env_and_store.store.put(
&mut writer,
&subject_key,
&Value::Blob(&new_cert_hash_list),
)?;
}
}
writer.commit()?;
Ok(())
}
// Given a list of certificate sha-256 hashes, we can look up each Cert entry in the database. // We use this to find the corresponding subject so we can look up the CertHashList it should // appear in. If that list contains the given hash, we remove it and update the CertHashList. // Finally we delete the Cert entry. pubfn remove_certs_by_hashes(
&mutself,
hashes_base64: &[nsCString],
) -> Result<(), SecurityStateError> { let env_and_store = matchself.env_and_store.as_mut() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; letmut writer = env_and_store.env.write()?; let reader = env_and_store.env.read()?;
for hash in hashes_base64 { let hash = match BASE64_STANDARD.decode(&hash) {
Ok(hash) => hash,
Err(e) => {
warn!("error decoding hash - ignoring: {}", e); continue;
}
}; let cert_key = make_key!(PREFIX_CERT, &hash); iflet Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? { iflet Ok(cert) = Cert::from_bytes(cert_bytes) { let subject_key = make_key!(PREFIX_SUBJECT, &cert.subject); let empty_vec = Vec::new(); // We have to use the writer here to make sure we have an up-to-date view of // the cert hash list. let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? {
Some(Value::Blob(hashes)) => hashes.to_owned(),
Some(_) => empty_vec,
None => empty_vec,
}; let new_cert_hash_list = CertHashList::remove(&old_cert_hash_list, &hash)?; if new_cert_hash_list.len() != old_cert_hash_list.len() {
env_and_store.store.put(
&mut writer,
&subject_key,
&Value::Blob(&new_cert_hash_list),
)?;
}
}
} match env_and_store.store.delete(&mut writer, &cert_key) {
Ok(()) => {}
Err(StoreError::KeyValuePairNotFound) => {}
Err(e) => return Err(SecurityStateError::from(e)),
};
}
writer.commit()?;
Ok(())
}
// Given a certificate's subject, we look up the corresponding CertHashList. In theory, each // hash in that list corresponds to a certificate with the given subject, so we look up each of // these (assuming the database is consistent and contains them) and add them to the given list. // If we encounter an inconsistency, we continue looking as best we can. pubfn find_certs_by_subject(
&self,
subject: &[u8],
certs: &mut ThinVec<ThinVec<u8>>,
) -> Result<(), SecurityStateError> { let env_and_store = matchself.env_and_store.as_ref() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; let reader = env_and_store.env.read()?;
certs.clear(); let subject_key = make_key!(PREFIX_SUBJECT, subject); let empty_vec = Vec::new(); let cert_hash_list_bytes = match env_and_store.store.get(&reader, &subject_key)? {
Some(Value::Blob(hashes)) => hashes,
Some(_) => &empty_vec,
None => &empty_vec,
}; let cert_hash_list = CertHashList::new(cert_hash_list_bytes)?; for cert_hash in cert_hash_list.into_iter() { let cert_key = make_key!(PREFIX_CERT, cert_hash); // If there's some inconsistency, we don't want to fail the whole operation - just go // for best effort and find as many certificates as we can. iflet Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? { iflet Ok(cert) = Cert::from_bytes(cert_bytes) { letmut thin_vec_cert = ThinVec::with_capacity(cert.der.len());
thin_vec_cert.extend_from_slice(&cert.der);
certs.push(thin_vec_cert);
}
}
}
Ok(())
}
pubfn find_cert_by_hash(
&self,
cert_hash: &ThinVec<u8>,
maybe_cert_bytes_out: Option<&mut ThinVec<u8>>,
) -> Result<bool, SecurityStateError> { let env_and_store = matchself.env_and_store.as_ref() {
Some(env_and_store) => env_and_store,
None => return Err(SecurityStateError::from("env and store not initialized?")),
}; let reader = env_and_store.env.read()?; let cert_key = make_key!(PREFIX_CERT, &cert_hash); iflet Some(Value::Blob(cert_bytes_stored)) = env_and_store.store.get(&reader, &cert_key)? { iflet Some(maybe_cert_bytes_out) = maybe_cert_bytes_out {
maybe_cert_bytes_out.clear(); let cert = Cert::from_bytes(cert_bytes_stored)?;
maybe_cert_bytes_out.extend_from_slice(cert.der);
} return Ok(true);
}
Ok(false)
}
pubfn has_all_certs_by_hash(
&self,
cert_hashes: &ThinVec<ThinVec<u8>>,
) -> Result<bool, SecurityStateError> { // Bug 1950140 - Implement a cache for this function to improve performance, // based on a caller-supplied key identifiying the list of hashes. for cert_hash in cert_hashes { matchself.find_cert_by_hash(&cert_hash, None) {
Ok(true) => {}
Ok(false) => return Ok(false),
Err(err) => return Err(err),
}
}
Ok(true)
}
}
// A Cert consists of its DER encoding, its DER-encoded subject, and its trust (currently // nsICertStorage::TRUST_INHERIT, but in the future nsICertStorage::TRUST_ANCHOR may also be used). // The length of each encoding must be representable by a u16 (so 65535 bytes is the longest a // certificate can be). struct Cert<'a> {
der: &'a [u8],
subject: &'a [u8],
trust: i16,
}
impl<'a> Cert<'a> { fn new(der: &'a [u8], subject: &'a [u8], trust: i16) -> Result<Cert<'a>, SecurityStateError> { if der.len() > u16::MAX.into() { return Err(SecurityStateError::from("certificate is too long"));
} if subject.len() > u16::MAX.into() { return Err(SecurityStateError::from("subject is too long"));
}
Ok(Cert {
der,
subject,
trust,
})
}
fn from_bytes(encoded: &'a [u8]) -> Result<Cert<'a>, SecurityStateError> { if encoded.len() < size_of::<u8>() { return Err(SecurityStateError::from("invalid Cert: no version?"));
} let (mut version, rest) = encoded.split_at(size_of::<u8>()); let version = version.read_u8()?; if version != CERT_SERIALIZATION_VERSION_1 { return Err(SecurityStateError::from("invalid Cert: unexpected version"));
}
if rest.len() < size_of::<u16>() { return Err(SecurityStateError::from("invalid Cert: no der len?"));
} let (mut der_len, rest) = rest.split_at(size_of::<u16>()); let der_len = der_len.read_u16::<NetworkEndian>()?.into(); if rest.len() < der_len { return Err(SecurityStateError::from("invalid Cert: no der?"));
} let (der, rest) = rest.split_at(der_len);
if rest.len() < size_of::<u16>() { return Err(SecurityStateError::from("invalid Cert: no subject len?"));
} let (mut subject_len, rest) = rest.split_at(size_of::<u16>()); let subject_len = subject_len.read_u16::<NetworkEndian>()?.into(); if rest.len() < subject_len { return Err(SecurityStateError::from("invalid Cert: no subject?"));
} let (subject, mut rest) = rest.split_at(subject_len);
if rest.len() < size_of::<i16>() { return Err(SecurityStateError::from("invalid Cert: no trust?"));
} let trust = rest.read_i16::<NetworkEndian>()?; if rest.len() > 0 { return Err(SecurityStateError::from("invalid Cert: trailing data?"));
}
// 16MB is a little over twice the size of the current dataset. When we // eventually switch to the LMDB backend to create the builder above, // we should set this as the map size, since it cannot currently resize. // (The SafeMode backend warns when a map size is specified, so we skip it // for now to avoid console spam.)
// builder.set_map_size(16777216);
// Bug 1595004: Migrate databases between backends in the future, // and handle 32 and 64 bit architectures in case of LMDB.
Rkv::from_builder(path, builder).map_err(SecurityStateError::from)
}
fn remove_db(path: &Path) -> Result<(), SecurityStateError> { // Remove LMDB-related files. let db = path.join("data.mdb");
unconditionally_remove_file(&db)?; let lock = path.join("lock.mdb");
unconditionally_remove_file(&lock)?;
// Remove SafeMode-related files. let db = path.join("data.safe.bin");
unconditionally_remove_file(&db)?;
Ok(())
}
// This is a helper struct that implements the task that asynchronously reads CRLite deltas on // a background thread. struct BackgroundReadDeltasTask {
profile_path: PathBuf,
security_state: Arc<RwLock<SecurityState>>,
}
fn do_construct_cert_storage(
iid: *const xpcom::nsIID,
result: *mut *mut xpcom::reexports::libc::c_void,
) -> Result<(), nserror::nsresult> { let path_buf = get_profile_path()?; let security_state = Arc::new(RwLock::new(SecurityState::new(path_buf.clone()))); let cert_storage = CertStorage::allocate(InitCertStorage {
security_state: security_state.clone(),
queue: create_background_task_queue(cstr!("cert_storage"))?,
}); let memory_reporter = MemoryReporter::allocate(InitMemoryReporter { security_state });
// Dispatch a task to the background task queue to asynchronously read CRLite delta files (if // present) and load them into cert_storage. This task does not hold the // cert_storage.security_state mutex for the majority of its operation, which allows certificate // verification threads to query cert_storage without blocking. This is important for // performance, but it means that certificate verifications that happen before the task has // completed will not have delta information, and thus may not know of revocations that have // occurred since the last full CRLite filter was downloaded. // NB: because the background task queue is serial, this task will complete before other tasks // later dispatched to the queue run. This means that other tasks that depend on deltas will do // so with the correct set of preconditions. let load_crlite_deltas_task = Box::new(BackgroundReadDeltasTask::new(
path_buf,
&cert_storage.security_state,
)); let runnable = TaskRunnable::new("LoadCrliteDeltas", load_crlite_deltas_task)?;
TaskRunnable::dispatch(runnable, cert_storage.queue.coerce())?;
// This is a helper for creating a task that will perform a specific action on a background thread. struct SecurityStateTask<
T: Default + VariantType,
F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>,
> {
callback: AtomicCell<Option<ThreadBoundRefPtr<nsICertStorageCallback>>>,
security_state: Arc<RwLock<SecurityState>>,
result: AtomicCell<(nserror::nsresult, T)>,
task_action: AtomicCell<Option<F>>,
}
// This macro is a way to ensure the DB has been opened while minimizing lock acquisitions in the // common (read-only) case. First we acquire a read lock and see if we even need to open the DB. If // not, we can continue with the read lock we already have. Otherwise, we drop the read lock, // acquire the write lock, open the DB, drop the write lock, and re-acquire the read lock. While it // is possible for two or more threads to all come to the conclusion that they need to open the DB, // this isn't ultimately an issue - `open_db` will exit early if another thread has already done the // work.
macro_rules! get_security_state {
($self:expr) => {{ let ss_read_only = try_ns!($self.security_state.read()); if !ss_read_only.db_needs_opening() {
ss_read_only
} else {
drop(ss_read_only);
{ letmut ss_write = try_ns!($self.security_state.write());
try_ns!(ss_write.open_db());
}
try_ns!($self.security_state.read())
}
}};
}
/// CertStorage implements the nsICertStorage interface. The actual work is done by the /// SecurityState. To handle any threading issues, we have an atomic-refcounted read/write lock on /// the one and only SecurityState. So, only one thread can use SecurityState's &mut self functions /// at a time, while multiple threads can use &self functions simultaneously (as long as there are /// no threads using an &mut self function). The Arc is to allow for the creation of background /// tasks that use the SecurityState on the queue owned by CertStorage. This allows us to not block /// the main thread. #[allow(non_snake_case)] impl CertStorage { unsafefn HasPriorData(
&self,
data_type: u8,
callback: *const nsICertStorageCallback,
) -> nserror::nsresult { if !is_main_thread() { return NS_ERROR_NOT_SAME_THREAD;
} if callback.is_null() { return NS_ERROR_NULL_POINTER;
} let task = Box::new(try_ns!(SecurityStateTask::new(
&*callback,
&self.security_state, move |ss| ss.get_has_prior_data(data_type),
))); let runnable = try_ns!(TaskRunnable::new("HasPriorData", task));
try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
NS_OK
}
unsafefn GetCRLiteFilterHashes(
&self,
callback: *const nsICertStorageCallback,
) -> nserror::nsresult { if !is_main_thread() { return NS_ERROR_NOT_SAME_THREAD;
} if callback.is_null() { return NS_ERROR_NULL_POINTER;
} let task = Box::new(try_ns!(SecurityStateTask::new(
&*callback,
&self.security_state,
|ss| ss.get_crlite_filter_hashes(),
))); let runnable = try_ns!(TaskRunnable::new("GetCRLiteFilterHashes", task));
try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
NS_OK
}
unsafefn GetRemainingOperationCount(&self, state: *mut i32) -> nserror::nsresult { if !is_main_thread() { return NS_ERROR_NOT_SAME_THREAD;
} if state.is_null() { return NS_ERROR_NULL_POINTER;
} let ss = try_ns!(self.security_state.read());
*state = ss.remaining_ops;
NS_OK
}
let revocations = &*revocations; letmut entries = Vec::with_capacity(revocations.len());
// By continuing when an nsIRevocationState attribute value is invalid, // we prevent errors relating to individual blocklist entries from // causing sync to fail. We will accumulate telemetry on these failures // in bug 1254099.
for revocation in revocations.iter().flatten() { letmut state: i16 = 0;
try_ns!(revocation.GetState(&mut state).to_result(), or continue);
unsafefn GetCRLiteRevocationState(
&self,
issuerSPKI: *const ThinVec<u8>,
serialNumber: *const ThinVec<u8>,
timestamps: *const ThinVec<Option<RefPtr<nsICRLiteTimestamp>>>,
state: *mut i16,
) -> nserror::nsresult { // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we // can't do so until bug 1406854 is fixed. if issuerSPKI.is_null() || serialNumber.is_null() || state.is_null() || timestamps.is_null()
{ return NS_ERROR_NULL_POINTER;
} let timestamps = &*timestamps; letmut timestamp_entries = Vec::with_capacity(timestamps.len()); for timestamp_entry in timestamps.iter().flatten() { letmut log_id = ThinVec::with_capacity(32);
try_ns!(timestamp_entry.GetLogID(&mut log_id).to_result(), or continue); letmut timestamp: u64 = 0;
try_ns!(timestamp_entry.GetTimestamp(&mut timestamp).to_result(), or continue);
timestamp_entries.push(CRLiteTimestamp { log_id, timestamp });
} let ss = get_security_state!(self);
*state = ss.get_crlite_revocation_state(&*issuerSPKI, &*serialNumber, ×tamp_entries);
NS_OK
}
unsafefn AddCerts(
&self,
certs: *const ThinVec<Option<RefPtr<nsICertInfo>>>,
callback: *const nsICertStorageCallback,
) -> nserror::nsresult { if !is_main_thread() { return NS_ERROR_NOT_SAME_THREAD;
} if certs.is_null() || callback.is_null() { return NS_ERROR_NULL_POINTER;
} let certs = &*certs; letmut cert_entries = Vec::with_capacity(certs.len()); for cert in certs.iter().flatten() { letmut der = nsCString::new();
try_ns!((*cert).GetCert(&mut *der).to_result(), or continue); letmut subject = nsCString::new();
try_ns!((*cert).GetSubject(&mut *subject).to_result(), or continue); letmut trust: i16 = 0;
try_ns!((*cert).GetTrust(&mut trust).to_result(), or continue);
cert_entries.push((der, subject, trust));
} let task = Box::new(try_ns!(SecurityStateTask::new(
&*callback,
&self.security_state, move |ss| ss.add_certs(&cert_entries),
))); let runnable = try_ns!(TaskRunnable::new("AddCerts", task));
try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
NS_OK
}
// Synchronous helper for testing purposes only unsafefn TestHelperAddCert(
&self,
cert: *const nsACString,
subject: *const nsACString,
trust: i16,
) -> nserror::nsresult { let cert = nsCString::from(&*cert); let subject = nsCString::from(&*subject); letmut ss = self.security_state.write().unwrap();
ss.open_db().unwrap();
ss.add_certs(&[(cert, subject, trust)]).unwrap();
NS_OK
}
unsafefn RemoveCertsByHashes(
&self,
hashes: *const ThinVec<nsCString>,
callback: *const nsICertStorageCallback,
) -> nserror::nsresult { if !is_main_thread() { return NS_ERROR_NOT_SAME_THREAD;
} if hashes.is_null() || callback.is_null() { return NS_ERROR_NULL_POINTER;
} let hashes = (*hashes).to_vec(); let task = Box::new(try_ns!(SecurityStateTask::new(
&*callback,
&self.security_state, move |ss| ss.remove_certs_by_hashes(&hashes),
))); let runnable = try_ns!(TaskRunnable::new("RemoveCertsByHashes", task));
try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
NS_OK
}
unsafefn FindCertsBySubject(
&self,
subject: *const ThinVec<u8>,
certs: *mut ThinVec<ThinVec<u8>>,
) -> nserror::nsresult { // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we // can't do so until bug 1406854 is fixed. if subject.is_null() || certs.is_null() { return NS_ERROR_NULL_POINTER;
} let ss = get_security_state!(self); match ss.find_certs_by_subject(&*subject, &mut *certs) {
Ok(()) => NS_OK,
Err(_) => NS_ERROR_FAILURE,
}
}
#[allow(non_snake_case)] impl MemoryReporter { unsafefn CollectReports(
&self,
callback: *const nsIHandleReportCallback,
data: *const nsISupports,
_anonymize: bool,
) -> nserror::nsresult { let ss = try_ns!(self.security_state.read()); letmut ops = MallocSizeOfOps::new(cert_storage_malloc_size_of, None); let size = ss.size_of(&mut ops); let callback = match RefPtr::from_raw(callback) {
Some(ptr) => ptr,
None => return NS_ERROR_UNEXPECTED,
}; // This does the same as MOZ_COLLECT_REPORT
callback.Callback(
&nsCStr::new() as &nsACString,
&nsCStr::from("explicit/cert-storage/storage") as &nsACString,
nsIMemoryReporter::KIND_HEAP,
nsIMemoryReporter::UNITS_BYTES,
size as i64,
&nsCStr::from("Memory used by certificate storage") as &nsACString,
data,
);
NS_OK
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.