Bug 1933630 - Enable the partial history state update collection on GeckoView session...
[gecko.git] / security / manager / ssl / cert_storage / src / lib.rs
blobad79e6ce1352f2c029d8a11f077b053a4e878183
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 extern crate base64;
6 extern crate byteorder;
7 extern crate clubcard;
8 extern crate clubcard_crlite;
9 extern crate crossbeam_utils;
10 #[macro_use]
11 extern crate cstr;
12 extern crate firefox_on_glean;
13 #[macro_use]
14 extern crate log;
15 extern crate moz_task;
16 extern crate nserror;
17 extern crate nsstring;
18 extern crate rkv;
19 extern crate rust_cascade;
20 extern crate sha2;
21 extern crate thin_vec;
22 extern crate time;
23 #[macro_use]
24 extern crate xpcom;
25 extern crate storage_variant;
26 extern crate tempfile;
28 extern crate wr_malloc_size_of;
29 use wr_malloc_size_of as malloc_size_of;
31 use base64::prelude::*;
32 use byteorder::{LittleEndian, NetworkEndian, ReadBytesExt, WriteBytesExt};
33 use clubcard::{ApproximateSizeOf, Queryable};
34 use clubcard_crlite::{CRLiteClubcard, CRLiteKey, CRLiteQuery, CRLiteStatus};
35 use crossbeam_utils::atomic::AtomicCell;
36 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
37 use moz_task::{create_background_task_queue, is_main_thread, Task, TaskRunnable};
38 use nserror::{
39     nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_SAME_THREAD, NS_ERROR_NULL_POINTER,
40     NS_ERROR_UNEXPECTED, NS_OK,
42 use nsstring::{nsACString, nsCStr, nsCString, nsString};
43 use rkv::backend::{BackendEnvironmentBuilder, SafeMode, SafeModeDatabase, SafeModeEnvironment};
44 use rkv::{StoreError, StoreOptions, Value};
45 use rust_cascade::Cascade;
46 use sha2::{Digest, Sha256};
47 use std::collections::{HashMap, HashSet};
48 use std::convert::TryInto;
49 use std::ffi::{CString, OsString};
50 use std::fmt::Display;
51 use std::fs::{create_dir_all, remove_file, File, OpenOptions};
52 use std::io::{BufRead, BufReader, Read, Write};
53 use std::mem::size_of;
54 use std::path::{Path, PathBuf};
55 use std::str;
56 use std::sync::{Arc, RwLock};
57 use std::time::{SystemTime, UNIX_EPOCH};
58 use storage_variant::VariantType;
59 use thin_vec::ThinVec;
60 use xpcom::interfaces::{
61     nsICRLiteCoverage, nsICRLiteTimestamp, nsICertInfo, nsICertStorage, nsICertStorageCallback,
62     nsIFile, nsIHandleReportCallback, nsIIssuerAndSerialRevocationState, nsIMemoryReporter,
63     nsIMemoryReporterManager, nsIProperties, nsIRevocationState, nsISerialEventTarget,
64     nsISubjectAndPubKeyRevocationState, nsISupports,
66 use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom};
68 const PREFIX_REV_IS: &str = "is";
69 const PREFIX_REV_SPK: &str = "spk";
70 const PREFIX_SUBJECT: &str = "subject";
71 const PREFIX_CERT: &str = "cert";
72 const PREFIX_DATA_TYPE: &str = "datatype";
74 const LAST_CRLITE_UPDATE_KEY: &str = "last_crlite_update";
76 const COVERAGE_SERIALIZATION_VERSION: u8 = 1;
77 const COVERAGE_V1_ENTRY_BYTES: usize = 48;
79 const ENROLLMENT_SERIALIZATION_VERSION: u8 = 1;
80 const ENROLLMENT_V1_ENTRY_BYTES: usize = 32;
82 type Rkv = rkv::Rkv<SafeModeEnvironment>;
83 type SingleStore = rkv::SingleStore<SafeModeDatabase>;
85 macro_rules! make_key {
86     ( $prefix:expr, $( $part:expr ),+ ) => {
87         {
88             let mut key = $prefix.as_bytes().to_owned();
89             $( key.extend_from_slice($part); )+
90             key
91         }
92     }
95 #[allow(non_camel_case_types, non_snake_case)]
97 /// `SecurityStateError` is a type to represent errors in accessing or
98 /// modifying security state.
99 #[derive(Debug)]
100 struct SecurityStateError {
101     message: String,
104 impl<T: Display> From<T> for SecurityStateError {
105     /// Creates a new instance of `SecurityStateError` from something that
106     /// implements the `Display` trait.
107     fn from(err: T) -> SecurityStateError {
108         SecurityStateError {
109             message: format!("{}", err),
110         }
111     }
114 struct EnvAndStore {
115     env: Rkv,
116     store: SingleStore,
119 impl MallocSizeOf for EnvAndStore {
120     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
121         self.env
122             .read()
123             .and_then(|reader| {
124                 let iter = self.store.iter_start(&reader)?.into_iter();
125                 Ok(iter
126                     .map(|r| {
127                         r.map(|(k, v)| k.len() + v.serialized_size().unwrap_or(0) as usize)
128                             .unwrap_or(0)
129                     })
130                     .sum())
131             })
132             .unwrap_or(0)
133     }
136 struct CascadeWithMetadata {
137     cascade: Cascade,
138     /// Maps an RFC 6962 LogID to a pair of 64 bit unix timestamps
139     coverage: HashMap<Vec<u8>, (u64, u64)>,
140     /// Set of `SHA256(subject || spki)` values for enrolled issuers
141     enrollment: HashSet<Vec<u8>>,
144 impl CascadeWithMetadata {
145     fn new(
146         cascade: Cascade,
147         coverage_path: &PathBuf,
148         enrollment_path: &PathBuf,
149     ) -> Result<Self, SecurityStateError> {
150         if !coverage_path.exists() {
151             return Err(SecurityStateError::from("missing coverage file"));
152         }
154         if !enrollment_path.exists() {
155             return Err(SecurityStateError::from("missing enrollment file"));
156         }
158         // Deserialize the coverage metadata.
159         // The format is described in `set_full_crlite_filter`.
160         let coverage_file = File::open(coverage_path)?;
161         let coverage_file_len = coverage_file.metadata()?.len() as usize;
162         let mut coverage_reader = BufReader::new(coverage_file);
163         match coverage_reader.read_u8() {
164             Ok(COVERAGE_SERIALIZATION_VERSION) => (),
165             _ => return Err(SecurityStateError::from("unknown CRLite coverage version")),
166         }
167         if (coverage_file_len - 1) % COVERAGE_V1_ENTRY_BYTES != 0 {
168             return Err(SecurityStateError::from("truncated CRLite coverage file"));
169         }
170         let coverage_count = (coverage_file_len - 1) / COVERAGE_V1_ENTRY_BYTES;
171         let mut coverage = HashMap::new();
172         for _ in 0..coverage_count {
173             let mut coverage_entry = [0u8; COVERAGE_V1_ENTRY_BYTES];
174             match coverage_reader.read_exact(&mut coverage_entry) {
175                 Ok(()) => (),
176                 _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
177             };
178             let log_id = &coverage_entry[0..32];
179             let min_timestamp: u64;
180             let max_timestamp: u64;
181             match (&coverage_entry[32..40]).read_u64::<LittleEndian>() {
182                 Ok(value) => min_timestamp = value,
183                 _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
184             }
185             match (&coverage_entry[40..48]).read_u64::<LittleEndian>() {
186                 Ok(value) => max_timestamp = value,
187                 _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
188             }
189             coverage.insert(log_id.to_vec(), (min_timestamp, max_timestamp));
190         }
192         // Deserialize the enrollment metadata.
193         // The format is described in `set_full_crlite_filter`.
194         let enrollment_file = File::open(enrollment_path)?;
195         let enrollment_file_len = enrollment_file.metadata()?.len() as usize;
196         let mut enrollment_reader = BufReader::new(enrollment_file);
197         match enrollment_reader.read_u8() {
198             Ok(ENROLLMENT_SERIALIZATION_VERSION) => (),
199             _ => {
200                 return Err(SecurityStateError::from(
201                     "unknown CRLite enrollment version",
202                 ))
203             }
204         }
205         if (enrollment_file_len - 1) % ENROLLMENT_V1_ENTRY_BYTES != 0 {
206             return Err(SecurityStateError::from("truncated CRLite enrollment file"));
207         }
208         let enrollment_count = (enrollment_file_len - 1) / ENROLLMENT_V1_ENTRY_BYTES;
209         let mut enrollment = HashSet::new();
210         for _ in 0..enrollment_count {
211             let mut enrollment_entry = [0u8; ENROLLMENT_V1_ENTRY_BYTES];
212             match enrollment_reader.read_exact(&mut enrollment_entry) {
213                 Ok(()) => (),
214                 _ => return Err(SecurityStateError::from("truncated CRLite enrollment file")),
215             };
216             let issuer_id = &enrollment_entry[..];
217             enrollment.insert(issuer_id.to_vec());
218         }
220         Ok(Self {
221             cascade,
222             coverage,
223             enrollment,
224         })
225     }
227     fn filter_covers_some_timestamp(&self, timestamps: &[CRLiteTimestamp]) -> bool {
228         let mut covered_timestamp_count = 0;
229         for entry in timestamps {
230             if let Some(&(low, high)) = self.coverage.get(entry.log_id.as_ref()) {
231                 if low <= entry.timestamp && entry.timestamp <= high {
232                     covered_timestamp_count += 1;
233                 }
234             }
235         }
237         covered_timestamp_count
238             >= static_prefs::pref!("security.pki.crlite_timestamps_for_coverage")
239     }
241     fn issuer_is_enrolled(&self, subject: &[u8], pub_key: &[u8]) -> bool {
242         let mut digest = Sha256::default();
243         digest.update(subject);
244         digest.update(pub_key);
245         let issuer_id = digest.finalize();
246         self.enrollment.contains(issuer_id.as_slice())
247     }
249     fn has(&self, crlite_key: &[u8]) -> bool {
250         self.cascade.has(crlite_key.to_vec())
251     }
254 impl MallocSizeOf for CascadeWithMetadata {
255     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
256         self.cascade.approximate_size_of()
257             + self.coverage.size_of(ops)
258             + self.enrollment.size_of(ops)
259     }
262 enum Filter {
263     Cascade(CascadeWithMetadata),
264     Clubcard(CRLiteClubcard),
267 impl Filter {
268     fn load(
269         store_path: &PathBuf,
270         filter_filename: &OsString,
271     ) -> Result<Option<Filter>, SecurityStateError> {
272         // Before we've downloaded any filters, this file won't exist.
273         let filter_path = store_path.join(filter_filename);
274         if !filter_path.exists() {
275             return Ok(None);
276         }
277         let filter_bytes = std::fs::read(&filter_path)?;
279         if let Ok(clubcard) = CRLiteClubcard::from_bytes(&filter_bytes) {
280             Ok(Some(Filter::Clubcard(clubcard)))
281         } else if let Ok(maybe_cascade) = Cascade::from_bytes(filter_bytes) {
282             let Some(cascade) = maybe_cascade else {
283                 return Err(SecurityStateError::from("expecting non-empty filter"));
284             };
285             let coverage_path = store_path.join("crlite.coverage");
286             let enrollment_path = store_path.join("crlite.enrollment");
287             if !coverage_path.exists() || !enrollment_path.exists() {
288                 return Ok(None);
289             }
290             let cascade_with_metadata =
291                 CascadeWithMetadata::new(cascade, &coverage_path, &enrollment_path)?;
292             Ok(Some(Filter::Cascade(cascade_with_metadata)))
293         } else {
294             Err(SecurityStateError::from("invalid CRLite filter"))
295         }
296     }
298     fn has(
299         &self,
300         issuer_dn: &[u8],
301         issuer_spki: &[u8],
302         issuer_spki_hash: &[u8],
303         serial: &[u8],
304         clubcard_crlite_key: &CRLiteKey,
305         timestamps: &[CRLiteTimestamp],
306     ) -> i16 {
307         match self {
308             Filter::Cascade(cascade) => {
309                 let mut crlite_key = Vec::with_capacity(issuer_spki_hash.len() + serial.len());
310                 crlite_key.extend_from_slice(issuer_spki_hash);
311                 crlite_key.extend_from_slice(serial);
312                 if !cascade.issuer_is_enrolled(issuer_dn, issuer_spki) {
313                     nsICertStorage::STATE_NOT_ENROLLED
314                 } else if !cascade.filter_covers_some_timestamp(timestamps) {
315                     nsICertStorage::STATE_NOT_COVERED
316                 } else if cascade.has(&crlite_key) {
317                     nsICertStorage::STATE_ENFORCE
318                 } else {
319                     nsICertStorage::STATE_UNSET
320                 }
321             }
322             Filter::Clubcard(clubcard) => {
323                 let timestamp_iter = timestamps
324                     .iter()
325                     .map(|timestamp| {
326                         (&*timestamp.log_id) /* ThinVec -> &[u8; 32] */
327                             .try_into()
328                             .ok()
329                             .map(|log_id| (log_id, timestamp.timestamp))
330                     })
331                     .flatten();
332                 let mut covered_timestamp_count = 0;
333                 for timestamp in timestamp_iter.clone() {
334                     if CRLiteQuery::new(&clubcard_crlite_key, Some(timestamp))
335                         .in_universe(clubcard.universe())
336                     {
337                         covered_timestamp_count += 1;
338                     }
339                 }
340                 if covered_timestamp_count
341                     < static_prefs::pref!("security.pki.crlite_timestamps_for_coverage")
342                 {
343                     return nsICertStorage::STATE_NOT_COVERED;
344                 }
345                 match clubcard.contains(&clubcard_crlite_key, timestamp_iter) {
346                     CRLiteStatus::Good => nsICertStorage::STATE_UNSET,
347                     CRLiteStatus::NotCovered => nsICertStorage::STATE_NOT_COVERED,
348                     CRLiteStatus::NotEnrolled => nsICertStorage::STATE_NOT_ENROLLED,
349                     CRLiteStatus::Revoked => nsICertStorage::STATE_ENFORCE,
350                 }
351             }
352         }
353     }
356 impl MallocSizeOf for Filter {
357     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
358         match self {
359             Filter::Cascade(cascade) => cascade.size_of(ops),
360             Filter::Clubcard(clubcard) => clubcard.approximate_size_of(),
361         }
362     }
365 /// `SecurityState`
366 struct SecurityState {
367     profile_path: PathBuf,
368     env_and_store: Option<EnvAndStore>,
369     crlite_filters: Vec<Filter>,
370     /// Maps issuer spki hashes to sets of serial numbers.
371     crlite_stash: Option<HashMap<Vec<u8>, HashSet<Vec<u8>>>>,
372     /// Tracks the number of asynchronous operations which have been dispatched but not completed.
373     remaining_ops: i32,
376 impl SecurityState {
377     pub fn new(profile_path: PathBuf) -> SecurityState {
378         // Since this gets called on the main thread, we don't actually want to open the DB yet.
379         // We do this on-demand later, when we're probably on a certificate verification thread.
380         SecurityState {
381             profile_path,
382             env_and_store: None,
383             crlite_filters: vec![],
384             crlite_stash: None,
385             remaining_ops: 0,
386         }
387     }
389     pub fn db_needs_opening(&self) -> bool {
390         self.env_and_store.is_none()
391     }
393     pub fn open_db(&mut self) -> Result<(), SecurityStateError> {
394         if self.env_and_store.is_some() {
395             return Ok(());
396         }
398         let store_path = get_store_path(&self.profile_path)?;
400         // Open the store in read-write mode to create it (if needed) and migrate data from the old
401         // store (if any).
402         // If opening initially fails, try to remove and recreate the database. Consumers will
403         // repopulate the database as necessary if this happens (see bug 1546361).
404         let env = make_env(store_path.as_path()).or_else(|_| {
405             remove_db(store_path.as_path())?;
406             make_env(store_path.as_path())
407         })?;
408         let store = env.open_single("cert_storage", StoreOptions::create())?;
410         // if the profile has a revocations.txt, migrate it and remove the file
411         let mut revocations_path = self.profile_path.clone();
412         revocations_path.push("revocations.txt");
413         if revocations_path.exists() {
414             SecurityState::migrate(&revocations_path, &env, &store)?;
415             remove_file(revocations_path)?;
416         }
418         // We already returned early if env_and_store was Some, so this should take the None branch.
419         match self.env_and_store.replace(EnvAndStore { env, store }) {
420             Some(_) => Err(SecurityStateError::from(
421                 "env and store already initialized? (did we mess up our threading model?)",
422             )),
423             None => Ok(()),
424         }?;
425         self.load_crlite_filter()?;
426         Ok(())
427     }
429     fn migrate(
430         revocations_path: &PathBuf,
431         env: &Rkv,
432         store: &SingleStore,
433     ) -> Result<(), SecurityStateError> {
434         let f = File::open(revocations_path)?;
435         let file = BufReader::new(f);
436         let value = Value::I64(nsICertStorage::STATE_ENFORCE as i64);
437         let mut writer = env.write()?;
439         // Add the data from revocations.txt
440         let mut dn: Option<Vec<u8>> = None;
441         for line in file.lines() {
442             let l = match line.map_err(|_| SecurityStateError::from("io error reading line data")) {
443                 Ok(data) => data,
444                 Err(e) => return Err(e),
445             };
446             if l.len() == 0 || l.starts_with("#") {
447                 continue;
448             }
449             let leading_char = match l.chars().next() {
450                 Some(c) => c,
451                 None => {
452                     return Err(SecurityStateError::from(
453                         "couldn't get char from non-empty str?",
454                     ));
455                 }
456             };
457             // In future, we can maybe log migration failures. For now, ignore decoding and storage
458             // errors and attempt to continue.
459             // Check if we have a new DN
460             if leading_char != '\t' && leading_char != ' ' {
461                 if let Ok(decoded_dn) = BASE64_STANDARD.decode(&l) {
462                     dn = Some(decoded_dn);
463                 }
464                 continue;
465             }
466             let l_sans_prefix = match BASE64_STANDARD.decode(&l[1..]) {
467                 Ok(decoded) => decoded,
468                 Err(_) => continue,
469             };
470             if let Some(name) = &dn {
471                 if leading_char == '\t' {
472                     let _ = store.put(
473                         &mut writer,
474                         &make_key!(PREFIX_REV_SPK, name, &l_sans_prefix),
475                         &value,
476                     );
477                 } else {
478                     let _ = store.put(
479                         &mut writer,
480                         &make_key!(PREFIX_REV_IS, name, &l_sans_prefix),
481                         &value,
482                     );
483                 }
484             }
485         }
487         writer.commit()?;
488         Ok(())
489     }
491     fn read_entry(&self, key: &[u8]) -> Result<Option<i16>, SecurityStateError> {
492         let env_and_store = match self.env_and_store.as_ref() {
493             Some(env_and_store) => env_and_store,
494             None => return Err(SecurityStateError::from("env and store not initialized?")),
495         };
496         let reader = env_and_store.env.read()?;
497         match env_and_store.store.get(&reader, key) {
498             Ok(Some(Value::I64(i))) => {
499                 Ok(Some(i.try_into().map_err(|_| {
500                     SecurityStateError::from("Stored value out of range for i16")
501                 })?))
502             }
503             Ok(None) => Ok(None),
504             Ok(_) => Err(SecurityStateError::from(
505                 "Unexpected type when trying to get a Value::I64",
506             )),
507             Err(_) => Err(SecurityStateError::from(
508                 "There was a problem getting the value",
509             )),
510         }
511     }
513     pub fn get_has_prior_data(&self, data_type: u8) -> Result<bool, SecurityStateError> {
514         if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_FULL {
515             return Ok(!self.crlite_filters.is_empty());
516         }
517         if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_INCREMENTAL {
518             return Ok(self.crlite_stash.is_some() || self.crlite_filters.len() > 1);
519         }
521         let env_and_store = match self.env_and_store.as_ref() {
522             Some(env_and_store) => env_and_store,
523             None => return Err(SecurityStateError::from("env and store not initialized?")),
524         };
525         let reader = env_and_store.env.read()?;
526         match env_and_store
527             .store
528             .get(&reader, &make_key!(PREFIX_DATA_TYPE, &[data_type]))
529         {
530             Ok(Some(Value::Bool(true))) => Ok(true),
531             Ok(None) => Ok(false),
532             Ok(_) => Err(SecurityStateError::from(
533                 "Unexpected type when trying to get a Value::Bool",
534             )),
535             Err(_) => Err(SecurityStateError::from(
536                 "There was a problem getting the value",
537             )),
538         }
539     }
541     pub fn set_batch_state(
542         &mut self,
543         entries: &[EncodedSecurityState],
544         typ: u8,
545     ) -> Result<(), SecurityStateError> {
546         let env_and_store = match self.env_and_store.as_mut() {
547             Some(env_and_store) => env_and_store,
548             None => return Err(SecurityStateError::from("env and store not initialized?")),
549         };
550         let mut writer = env_and_store.env.write()?;
551         // Make a note that we have prior data of the given type now.
552         env_and_store.store.put(
553             &mut writer,
554             &make_key!(PREFIX_DATA_TYPE, &[typ]),
555             &Value::Bool(true),
556         )?;
558         for entry in entries {
559             let key = match entry.key() {
560                 Ok(key) => key,
561                 Err(e) => {
562                     warn!("error base64-decoding key parts - ignoring: {}", e.message);
563                     continue;
564                 }
565             };
566             env_and_store
567                 .store
568                 .put(&mut writer, &key, &Value::I64(entry.state() as i64))?;
569         }
571         writer.commit()?;
572         Ok(())
573     }
575     pub fn get_revocation_state(
576         &self,
577         issuer: &[u8],
578         serial: &[u8],
579         subject: &[u8],
580         pub_key: &[u8],
581     ) -> Result<i16, SecurityStateError> {
582         let mut digest = Sha256::default();
583         digest.update(pub_key);
584         let pub_key_hash = digest.finalize();
586         let subject_pubkey = make_key!(PREFIX_REV_SPK, subject, &pub_key_hash);
587         let issuer_serial = make_key!(PREFIX_REV_IS, issuer, serial);
589         let st: i16 = match self.read_entry(&issuer_serial) {
590             Ok(Some(value)) => value,
591             Ok(None) => nsICertStorage::STATE_UNSET,
592             Err(_) => {
593                 return Err(SecurityStateError::from(
594                     "problem reading revocation state (from issuer / serial)",
595                 ));
596             }
597         };
599         if st != nsICertStorage::STATE_UNSET {
600             return Ok(st);
601         }
603         match self.read_entry(&subject_pubkey) {
604             Ok(Some(value)) => Ok(value),
605             Ok(None) => Ok(nsICertStorage::STATE_UNSET),
606             Err(_) => {
607                 return Err(SecurityStateError::from(
608                     "problem reading revocation state (from subject / pubkey)",
609                 ));
610             }
611         }
612     }
614     fn note_crlite_update_time(&mut self) -> Result<(), SecurityStateError> {
615         let seconds_since_epoch = Value::U64(
616             SystemTime::now()
617                 .duration_since(UNIX_EPOCH)
618                 .map_err(|_| SecurityStateError::from("could not get current time"))?
619                 .as_secs(),
620         );
621         let env_and_store = match self.env_and_store.as_mut() {
622             Some(env_and_store) => env_and_store,
623             None => return Err(SecurityStateError::from("env and store not initialized?")),
624         };
625         let mut writer = env_and_store.env.write()?;
626         env_and_store
627             .store
628             .put(&mut writer, LAST_CRLITE_UPDATE_KEY, &seconds_since_epoch)
629             .map_err(|_| SecurityStateError::from("could not store timestamp"))?;
630         writer.commit()?;
631         Ok(())
632     }
634     fn is_crlite_fresh(&self) -> bool {
635         let now = match SystemTime::now().duration_since(UNIX_EPOCH) {
636             Ok(t) => t.as_secs(),
637             _ => return false,
638         };
639         let env_and_store = match self.env_and_store.as_ref() {
640             Some(env_and_store) => env_and_store,
641             None => return false,
642         };
643         let reader = match env_and_store.env.read() {
644             Ok(reader) => reader,
645             _ => return false,
646         };
647         match env_and_store.store.get(&reader, LAST_CRLITE_UPDATE_KEY) {
648             Ok(Some(Value::U64(last_update))) if last_update < u64::MAX / 2 => {
649                 now < last_update + 60 * 60 * 24 * 10
650             }
651             _ => false,
652         }
653     }
655     pub fn set_full_crlite_filter(
656         &mut self,
657         filter: Vec<u8>,
658         enrolled_issuers: Vec<nsCString>,
659         coverage_entries: &[(nsCString, u64, u64)],
660     ) -> Result<(), SecurityStateError> {
661         let store_path = get_store_path(&self.profile_path)?;
663         // Drop any existing crlite filter and clear the accumulated stash.
664         self.crlite_filters.clear();
665         self.crlite_stash = None;
667         // Delete the backing data for any stash or delta files.
668         for entry in std::fs::read_dir(&store_path)? {
669             let Ok(entry) = entry else {
670                 continue;
671             };
672             let entry_path = entry.path();
673             let extension = entry_path
674                 .extension()
675                 .map(|os_str| os_str.to_str())
676                 .flatten();
677             if extension == Some("stash") || extension == Some("delta") {
678                 let _ = std::fs::remove_file(entry_path);
679             }
680         }
682         // Write the new full filter.
683         std::fs::write(store_path.join("crlite.filter"), &filter)?;
685         // Serialize the coverage metadata as a 1 byte version number followed by any number of 48
686         // byte entries. Each entry is a 32 byte (opaque) log id, followed by two 8 byte
687         // timestamps. Each timestamp is an 8 byte unsigned integer in little endian.
688         let mut coverage_bytes =
689             Vec::with_capacity(size_of::<u8>() + coverage_entries.len() * COVERAGE_V1_ENTRY_BYTES);
690         coverage_bytes.push(COVERAGE_SERIALIZATION_VERSION);
691         for (b64_log_id, min_t, max_t) in coverage_entries {
692             let log_id = match BASE64_STANDARD.decode(&b64_log_id) {
693                 Ok(log_id) if log_id.len() == 32 => log_id,
694                 _ => {
695                     warn!("malformed log ID - skipping: {}", b64_log_id);
696                     continue;
697                 }
698             };
699             coverage_bytes.extend_from_slice(&log_id);
700             coverage_bytes.extend_from_slice(&min_t.to_le_bytes());
701             coverage_bytes.extend_from_slice(&max_t.to_le_bytes());
702         }
703         // Write the coverage file for the new filter
704         std::fs::write(store_path.join("crlite.coverage"), &coverage_bytes)?;
706         // Serialize the enrollment list as a 1 byte version number followed by:
707         // Version 1: any number of 32 byte values of the form `SHA256(subject || spki)`.
708         let mut enrollment_bytes = Vec::with_capacity(
709             size_of::<u8>() + enrolled_issuers.len() * ENROLLMENT_V1_ENTRY_BYTES,
710         );
711         enrollment_bytes.push(ENROLLMENT_SERIALIZATION_VERSION);
712         for b64_issuer_id in enrolled_issuers {
713             let issuer_id = match BASE64_STANDARD.decode(&b64_issuer_id) {
714                 Ok(issuer_id) if issuer_id.len() == 32 => issuer_id,
715                 _ => {
716                     warn!("malformed issuer ID - skipping: {}", b64_issuer_id);
717                     continue;
718                 }
719             };
720             enrollment_bytes.extend_from_slice(&issuer_id);
721         }
722         // Write the enrollment file for the new filter
723         std::fs::write(store_path.join("crlite.enrollment"), &enrollment_bytes)?;
725         self.note_crlite_update_time()?;
726         self.load_crlite_filter()?;
727         self.note_memory_usage();
728         Ok(())
729     }
731     fn load_crlite_filter(&mut self) -> Result<(), SecurityStateError> {
732         if !self.crlite_filters.is_empty() {
733             return Err(SecurityStateError::from(
734                 "crlite_filters should be empty here",
735             ));
736         }
738         let store_path = get_store_path(&self.profile_path)?;
739         let filter_filename = OsString::from("crlite.filter");
740         if let Ok(Some(filter)) = Filter::load(&store_path, &filter_filename) {
741             self.crlite_filters.push(filter);
742         }
743         Ok(())
744     }
746     pub fn add_crlite_stash(&mut self, stash: Vec<u8>) -> Result<(), SecurityStateError> {
747         // Append the update to the previously-seen stashes.
748         let mut path = get_store_path(&self.profile_path)?;
749         path.push("crlite.stash");
750         let mut stash_file = OpenOptions::new().append(true).create(true).open(path)?;
751         stash_file.write_all(&stash)?;
752         let crlite_stash = self.crlite_stash.get_or_insert(HashMap::new());
753         load_crlite_stash_from_reader_into_map(&mut stash.as_slice(), crlite_stash)?;
754         self.note_crlite_update_time()?;
755         self.note_memory_usage();
756         Ok(())
757     }
759     pub fn add_crlite_delta(
760         &mut self,
761         delta: Vec<u8>,
762         filename: String,
763     ) -> Result<(), SecurityStateError> {
764         let store_path = get_store_path(&self.profile_path)?;
765         let delta_path = store_path.join(&filename);
766         std::fs::write(&delta_path, &delta)?;
767         if let Ok(Some(filter)) = Filter::load(&store_path, &filename.into()) {
768             self.crlite_filters.push(filter);
769         }
770         self.note_crlite_update_time()?;
771         self.note_memory_usage();
772         Ok(())
773     }
775     pub fn is_cert_revoked_by_stash(
776         &self,
777         issuer_spki: &[u8],
778         serial: &[u8],
779     ) -> Result<bool, SecurityStateError> {
780         let crlite_stash = match self.crlite_stash.as_ref() {
781             Some(crlite_stash) => crlite_stash,
782             None => return Ok(false),
783         };
784         let mut digest = Sha256::default();
785         digest.update(issuer_spki);
786         let lookup_key = digest.finalize().to_vec();
787         let serials = match crlite_stash.get(&lookup_key) {
788             Some(serials) => serials,
789             None => return Ok(false),
790         };
791         Ok(serials.contains(&serial.to_vec()))
792     }
794     pub fn get_crlite_revocation_state(
795         &self,
796         issuer: &[u8],
797         issuer_spki: &[u8],
798         serial_number: &[u8],
799         timestamps: &[CRLiteTimestamp],
800     ) -> i16 {
801         if !self.is_crlite_fresh() {
802             return nsICertStorage::STATE_NO_FILTER;
803         }
804         if self.crlite_filters.is_empty() {
805             // This can only happen if the backing file was deleted or if it or our database has
806             // become corrupted. In any case, we have no information.
807             return nsICertStorage::STATE_NO_FILTER;
808         }
809         let mut maybe_good = false;
810         let mut covered = false;
812         let issuer_spki_hash = Sha256::digest(issuer_spki);
813         let clubcard_crlite_key = CRLiteKey::new(issuer_spki_hash.as_ref(), serial_number);
814         for filter in &self.crlite_filters {
815             match filter.has(
816                 issuer,
817                 issuer_spki,
818                 issuer_spki_hash.as_ref(),
819                 serial_number,
820                 &clubcard_crlite_key,
821                 timestamps,
822             ) {
823                 nsICertStorage::STATE_ENFORCE => return nsICertStorage::STATE_ENFORCE,
824                 nsICertStorage::STATE_UNSET => maybe_good = true,
825                 nsICertStorage::STATE_NOT_ENROLLED => covered = true,
826                 _ => (),
827             }
828         }
829         if maybe_good {
830             return nsICertStorage::STATE_UNSET;
831         }
832         if covered {
833             return nsICertStorage::STATE_NOT_ENROLLED;
834         }
835         nsICertStorage::STATE_NOT_COVERED
836     }
838     // To store certificates, we create a Cert out of each given cert, subject, and trust tuple. We
839     // hash each certificate with sha-256 to obtain a unique* key for that certificate, and we store
840     // the Cert in the database. We also look up or create a CertHashList for the given subject and
841     // add the new certificate's hash if it isn't present in the list. If it wasn't present, we
842     // write out the updated CertHashList.
843     // *By the pigeon-hole principle, there exist collisions for sha-256, so this key is not
844     // actually unique. We rely on the assumption that sha-256 is a cryptographically strong hash.
845     // If an adversary can find two different certificates with the same sha-256 hash, they can
846     // probably forge a sha-256-based signature, so assuming the keys we create here are unique is
847     // not a security issue.
848     pub fn add_certs(
849         &mut self,
850         certs: &[(nsCString, nsCString, i16)],
851     ) -> Result<(), SecurityStateError> {
852         let env_and_store = match self.env_and_store.as_mut() {
853             Some(env_and_store) => env_and_store,
854             None => return Err(SecurityStateError::from("env and store not initialized?")),
855         };
856         let mut writer = env_and_store.env.write()?;
857         // Make a note that we have prior cert data now.
858         env_and_store.store.put(
859             &mut writer,
860             &make_key!(PREFIX_DATA_TYPE, &[nsICertStorage::DATA_TYPE_CERTIFICATE]),
861             &Value::Bool(true),
862         )?;
864         for (cert_der_base64, subject_base64, trust) in certs {
865             let cert_der = match BASE64_STANDARD.decode(&cert_der_base64) {
866                 Ok(cert_der) => cert_der,
867                 Err(e) => {
868                     warn!("error base64-decoding cert - skipping: {}", e);
869                     continue;
870                 }
871             };
872             let subject = match BASE64_STANDARD.decode(&subject_base64) {
873                 Ok(subject) => subject,
874                 Err(e) => {
875                     warn!("error base64-decoding subject - skipping: {}", e);
876                     continue;
877                 }
878             };
879             let mut digest = Sha256::default();
880             digest.update(&cert_der);
881             let cert_hash = digest.finalize();
882             let cert_key = make_key!(PREFIX_CERT, &cert_hash);
883             let cert = Cert::new(&cert_der, &subject, *trust)?;
884             env_and_store
885                 .store
886                 .put(&mut writer, &cert_key, &Value::Blob(&cert.to_bytes()?))?;
887             let subject_key = make_key!(PREFIX_SUBJECT, &subject);
888             let empty_vec = Vec::new();
889             let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? {
890                 Some(Value::Blob(hashes)) => hashes.to_owned(),
891                 Some(_) => empty_vec,
892                 None => empty_vec,
893             };
894             let new_cert_hash_list = CertHashList::add(&old_cert_hash_list, &cert_hash)?;
895             if new_cert_hash_list.len() != old_cert_hash_list.len() {
896                 env_and_store.store.put(
897                     &mut writer,
898                     &subject_key,
899                     &Value::Blob(&new_cert_hash_list),
900                 )?;
901             }
902         }
904         writer.commit()?;
905         Ok(())
906     }
908     // Given a list of certificate sha-256 hashes, we can look up each Cert entry in the database.
909     // We use this to find the corresponding subject so we can look up the CertHashList it should
910     // appear in. If that list contains the given hash, we remove it and update the CertHashList.
911     // Finally we delete the Cert entry.
912     pub fn remove_certs_by_hashes(
913         &mut self,
914         hashes_base64: &[nsCString],
915     ) -> Result<(), SecurityStateError> {
916         let env_and_store = match self.env_and_store.as_mut() {
917             Some(env_and_store) => env_and_store,
918             None => return Err(SecurityStateError::from("env and store not initialized?")),
919         };
920         let mut writer = env_and_store.env.write()?;
921         let reader = env_and_store.env.read()?;
923         for hash in hashes_base64 {
924             let hash = match BASE64_STANDARD.decode(&hash) {
925                 Ok(hash) => hash,
926                 Err(e) => {
927                     warn!("error decoding hash - ignoring: {}", e);
928                     continue;
929                 }
930             };
931             let cert_key = make_key!(PREFIX_CERT, &hash);
932             if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? {
933                 if let Ok(cert) = Cert::from_bytes(cert_bytes) {
934                     let subject_key = make_key!(PREFIX_SUBJECT, &cert.subject);
935                     let empty_vec = Vec::new();
936                     // We have to use the writer here to make sure we have an up-to-date view of
937                     // the cert hash list.
938                     let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? {
939                         Some(Value::Blob(hashes)) => hashes.to_owned(),
940                         Some(_) => empty_vec,
941                         None => empty_vec,
942                     };
943                     let new_cert_hash_list = CertHashList::remove(&old_cert_hash_list, &hash)?;
944                     if new_cert_hash_list.len() != old_cert_hash_list.len() {
945                         env_and_store.store.put(
946                             &mut writer,
947                             &subject_key,
948                             &Value::Blob(&new_cert_hash_list),
949                         )?;
950                     }
951                 }
952             }
953             match env_and_store.store.delete(&mut writer, &cert_key) {
954                 Ok(()) => {}
955                 Err(StoreError::KeyValuePairNotFound) => {}
956                 Err(e) => return Err(SecurityStateError::from(e)),
957             };
958         }
959         writer.commit()?;
960         Ok(())
961     }
963     // Given a certificate's subject, we look up the corresponding CertHashList. In theory, each
964     // hash in that list corresponds to a certificate with the given subject, so we look up each of
965     // these (assuming the database is consistent and contains them) and add them to the given list.
966     // If we encounter an inconsistency, we continue looking as best we can.
967     pub fn find_certs_by_subject(
968         &self,
969         subject: &[u8],
970         certs: &mut ThinVec<ThinVec<u8>>,
971     ) -> Result<(), SecurityStateError> {
972         let env_and_store = match self.env_and_store.as_ref() {
973             Some(env_and_store) => env_and_store,
974             None => return Err(SecurityStateError::from("env and store not initialized?")),
975         };
976         let reader = env_and_store.env.read()?;
977         certs.clear();
978         let subject_key = make_key!(PREFIX_SUBJECT, subject);
979         let empty_vec = Vec::new();
980         let cert_hash_list_bytes = match env_and_store.store.get(&reader, &subject_key)? {
981             Some(Value::Blob(hashes)) => hashes,
982             Some(_) => &empty_vec,
983             None => &empty_vec,
984         };
985         let cert_hash_list = CertHashList::new(cert_hash_list_bytes)?;
986         for cert_hash in cert_hash_list.into_iter() {
987             let cert_key = make_key!(PREFIX_CERT, cert_hash);
988             // If there's some inconsistency, we don't want to fail the whole operation - just go
989             // for best effort and find as many certificates as we can.
990             if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? {
991                 if let Ok(cert) = Cert::from_bytes(cert_bytes) {
992                     let mut thin_vec_cert = ThinVec::with_capacity(cert.der.len());
993                     thin_vec_cert.extend_from_slice(&cert.der);
994                     certs.push(thin_vec_cert);
995                 }
996             }
997         }
998         Ok(())
999     }
1001     fn note_memory_usage(&self) -> usize {
1002         let mut ops = MallocSizeOfOps::new(cert_storage_malloc_size_of, None);
1003         let size = self.size_of(&mut ops);
1004         firefox_on_glean::metrics::cert_storage::memory.accumulate(size as u64);
1005         size
1006     }
1009 impl MallocSizeOf for SecurityState {
1010     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
1011         self.profile_path.size_of(ops)
1012             + self.env_and_store.size_of(ops)
1013             + self
1014                 .crlite_filters
1015                 .iter()
1016                 .map(|filter| filter.size_of(ops))
1017                 .sum::<usize>()
1018             + self.crlite_stash.size_of(ops)
1019             + self.remaining_ops.size_of(ops)
1020     }
1023 const CERT_SERIALIZATION_VERSION_1: u8 = 1;
1025 // A Cert consists of its DER encoding, its DER-encoded subject, and its trust (currently
1026 // nsICertStorage::TRUST_INHERIT, but in the future nsICertStorage::TRUST_ANCHOR may also be used).
1027 // The length of each encoding must be representable by a u16 (so 65535 bytes is the longest a
1028 // certificate can be).
1029 struct Cert<'a> {
1030     der: &'a [u8],
1031     subject: &'a [u8],
1032     trust: i16,
1035 impl<'a> Cert<'a> {
1036     fn new(der: &'a [u8], subject: &'a [u8], trust: i16) -> Result<Cert<'a>, SecurityStateError> {
1037         if der.len() > u16::MAX.into() {
1038             return Err(SecurityStateError::from("certificate is too long"));
1039         }
1040         if subject.len() > u16::MAX.into() {
1041             return Err(SecurityStateError::from("subject is too long"));
1042         }
1043         Ok(Cert {
1044             der,
1045             subject,
1046             trust,
1047         })
1048     }
1050     fn from_bytes(encoded: &'a [u8]) -> Result<Cert<'a>, SecurityStateError> {
1051         if encoded.len() < size_of::<u8>() {
1052             return Err(SecurityStateError::from("invalid Cert: no version?"));
1053         }
1054         let (mut version, rest) = encoded.split_at(size_of::<u8>());
1055         let version = version.read_u8()?;
1056         if version != CERT_SERIALIZATION_VERSION_1 {
1057             return Err(SecurityStateError::from("invalid Cert: unexpected version"));
1058         }
1060         if rest.len() < size_of::<u16>() {
1061             return Err(SecurityStateError::from("invalid Cert: no der len?"));
1062         }
1063         let (mut der_len, rest) = rest.split_at(size_of::<u16>());
1064         let der_len = der_len.read_u16::<NetworkEndian>()?.into();
1065         if rest.len() < der_len {
1066             return Err(SecurityStateError::from("invalid Cert: no der?"));
1067         }
1068         let (der, rest) = rest.split_at(der_len);
1070         if rest.len() < size_of::<u16>() {
1071             return Err(SecurityStateError::from("invalid Cert: no subject len?"));
1072         }
1073         let (mut subject_len, rest) = rest.split_at(size_of::<u16>());
1074         let subject_len = subject_len.read_u16::<NetworkEndian>()?.into();
1075         if rest.len() < subject_len {
1076             return Err(SecurityStateError::from("invalid Cert: no subject?"));
1077         }
1078         let (subject, mut rest) = rest.split_at(subject_len);
1080         if rest.len() < size_of::<i16>() {
1081             return Err(SecurityStateError::from("invalid Cert: no trust?"));
1082         }
1083         let trust = rest.read_i16::<NetworkEndian>()?;
1084         if rest.len() > 0 {
1085             return Err(SecurityStateError::from("invalid Cert: trailing data?"));
1086         }
1088         Ok(Cert {
1089             der,
1090             subject,
1091             trust,
1092         })
1093     }
1095     fn to_bytes(&self) -> Result<Vec<u8>, SecurityStateError> {
1096         let mut bytes = Vec::with_capacity(
1097             size_of::<u8>()
1098                 + size_of::<u16>()
1099                 + self.der.len()
1100                 + size_of::<u16>()
1101                 + self.subject.len()
1102                 + size_of::<i16>(),
1103         );
1104         bytes.write_u8(CERT_SERIALIZATION_VERSION_1)?;
1105         bytes.write_u16::<NetworkEndian>(
1106             self.der
1107                 .len()
1108                 .try_into()
1109                 .map_err(|_| SecurityStateError::from("certificate is too long"))?,
1110         )?;
1111         bytes.extend_from_slice(&self.der);
1112         bytes.write_u16::<NetworkEndian>(
1113             self.subject
1114                 .len()
1115                 .try_into()
1116                 .map_err(|_| SecurityStateError::from("subject is too long"))?,
1117         )?;
1118         bytes.extend_from_slice(&self.subject);
1119         bytes.write_i16::<NetworkEndian>(self.trust)?;
1120         Ok(bytes)
1121     }
1124 // A CertHashList is a list of sha-256 hashes of DER-encoded certificates.
1125 struct CertHashList<'a> {
1126     hashes: Vec<&'a [u8]>,
1129 impl<'a> CertHashList<'a> {
1130     fn new(hashes_bytes: &'a [u8]) -> Result<CertHashList<'a>, SecurityStateError> {
1131         if hashes_bytes.len() % Sha256::output_size() != 0 {
1132             return Err(SecurityStateError::from(
1133                 "unexpected length for cert hash list",
1134             ));
1135         }
1136         let mut hashes = Vec::with_capacity(hashes_bytes.len() / Sha256::output_size());
1137         for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
1138             hashes.push(hash);
1139         }
1140         Ok(CertHashList { hashes })
1141     }
1143     fn add(hashes_bytes: &[u8], new_hash: &[u8]) -> Result<Vec<u8>, SecurityStateError> {
1144         if hashes_bytes.len() % Sha256::output_size() != 0 {
1145             return Err(SecurityStateError::from(
1146                 "unexpected length for cert hash list",
1147             ));
1148         }
1149         if new_hash.len() != Sha256::output_size() {
1150             return Err(SecurityStateError::from("unexpected cert hash length"));
1151         }
1152         for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
1153             if hash == new_hash {
1154                 return Ok(hashes_bytes.to_owned());
1155             }
1156         }
1157         let mut combined = hashes_bytes.to_owned();
1158         combined.extend_from_slice(new_hash);
1159         Ok(combined)
1160     }
1162     fn remove(hashes_bytes: &[u8], cert_hash: &[u8]) -> Result<Vec<u8>, SecurityStateError> {
1163         if hashes_bytes.len() % Sha256::output_size() != 0 {
1164             return Err(SecurityStateError::from(
1165                 "unexpected length for cert hash list",
1166             ));
1167         }
1168         if cert_hash.len() != Sha256::output_size() {
1169             return Err(SecurityStateError::from("unexpected cert hash length"));
1170         }
1171         let mut result = Vec::with_capacity(hashes_bytes.len());
1172         for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
1173             if hash != cert_hash {
1174                 result.extend_from_slice(hash);
1175             }
1176         }
1177         Ok(result)
1178     }
1181 impl<'a> IntoIterator for CertHashList<'a> {
1182     type Item = &'a [u8];
1183     type IntoIter = std::vec::IntoIter<&'a [u8]>;
1185     fn into_iter(self) -> Self::IntoIter {
1186         self.hashes.into_iter()
1187     }
1190 // Helper struct for get_crlite_revocation_state.
1191 struct CRLiteTimestamp {
1192     log_id: ThinVec<u8>,
1193     timestamp: u64,
1196 // Helper struct for set_batch_state. Takes a prefix, two base64-encoded key
1197 // parts, and a security state value.
1198 struct EncodedSecurityState {
1199     prefix: &'static str,
1200     key_part_1_base64: nsCString,
1201     key_part_2_base64: nsCString,
1202     state: i16,
1205 impl EncodedSecurityState {
1206     fn new(
1207         prefix: &'static str,
1208         key_part_1_base64: nsCString,
1209         key_part_2_base64: nsCString,
1210         state: i16,
1211     ) -> EncodedSecurityState {
1212         EncodedSecurityState {
1213             prefix,
1214             key_part_1_base64,
1215             key_part_2_base64,
1216             state,
1217         }
1218     }
1220     fn key(&self) -> Result<Vec<u8>, SecurityStateError> {
1221         let key_part_1 = BASE64_STANDARD.decode(&self.key_part_1_base64)?;
1222         let key_part_2 = BASE64_STANDARD.decode(&self.key_part_2_base64)?;
1223         Ok(make_key!(self.prefix, &key_part_1, &key_part_2))
1224     }
1226     fn state(&self) -> i16 {
1227         self.state
1228     }
1231 fn get_path_from_directory_service(key: &str) -> Result<PathBuf, nserror::nsresult> {
1232     let directory_service: RefPtr<nsIProperties> =
1233         xpcom::components::Directory::service().map_err(|_| NS_ERROR_FAILURE)?;
1234     let cs_key = CString::new(key).map_err(|_| NS_ERROR_FAILURE)?;
1236     let mut requested_dir = GetterAddrefs::<nsIFile>::new();
1237     unsafe {
1238         (*directory_service)
1239             .Get(
1240                 (&cs_key).as_ptr(),
1241                 &nsIFile::IID as *const nsIID,
1242                 requested_dir.void_ptr(),
1243             )
1244             .to_result()
1245     }?;
1247     let dir_path = requested_dir.refptr().ok_or(NS_ERROR_FAILURE)?;
1248     let mut path = nsString::new();
1249     unsafe { (*dir_path).GetPath(&mut *path).to_result() }?;
1250     Ok(PathBuf::from(format!("{}", path)))
1253 fn get_profile_path() -> Result<PathBuf, nserror::nsresult> {
1254     get_path_from_directory_service("ProfD").or_else(|_| get_path_from_directory_service("TmpD"))
1257 fn get_store_path(profile_path: &PathBuf) -> Result<PathBuf, SecurityStateError> {
1258     let mut store_path = profile_path.clone();
1259     store_path.push("security_state");
1260     create_dir_all(store_path.as_path())?;
1261     Ok(store_path)
1264 fn make_env(path: &Path) -> Result<Rkv, SecurityStateError> {
1265     let mut builder = Rkv::environment_builder::<SafeMode>();
1266     builder.set_max_dbs(2);
1268     // 16MB is a little over twice the size of the current dataset. When we
1269     // eventually switch to the LMDB backend to create the builder above,
1270     // we should set this as the map size, since it cannot currently resize.
1271     // (The SafeMode backend warns when a map size is specified, so we skip it
1272     // for now to avoid console spam.)
1274     // builder.set_map_size(16777216);
1276     // Bug 1595004: Migrate databases between backends in the future,
1277     // and handle 32 and 64 bit architectures in case of LMDB.
1278     Rkv::from_builder(path, builder).map_err(SecurityStateError::from)
1281 fn unconditionally_remove_file(path: &Path) -> Result<(), SecurityStateError> {
1282     match remove_file(path) {
1283         Ok(()) => Ok(()),
1284         Err(e) => match e.kind() {
1285             std::io::ErrorKind::NotFound => Ok(()),
1286             _ => Err(SecurityStateError::from(e)),
1287         },
1288     }
1291 fn remove_db(path: &Path) -> Result<(), SecurityStateError> {
1292     // Remove LMDB-related files.
1293     let db = path.join("data.mdb");
1294     unconditionally_remove_file(&db)?;
1295     let lock = path.join("lock.mdb");
1296     unconditionally_remove_file(&lock)?;
1298     // Remove SafeMode-related files.
1299     let db = path.join("data.safe.bin");
1300     unconditionally_remove_file(&db)?;
1302     Ok(())
1305 // Helper function to read stash information from the given reader and insert the results into the
1306 // given stash map.
1307 fn load_crlite_stash_from_reader_into_map(
1308     reader: &mut dyn Read,
1309     dest: &mut HashMap<Vec<u8>, HashSet<Vec<u8>>>,
1310 ) -> Result<(), SecurityStateError> {
1311     // The basic unit of the stash file is an issuer subject public key info
1312     // hash (sha-256) followed by a number of serial numbers corresponding
1313     // to revoked certificates issued by that issuer. More specifically,
1314     // each unit consists of:
1315     //   4 bytes little-endian: the number of serial numbers following the issuer spki hash
1316     //   1 byte: the length of the issuer spki hash
1317     //   issuer spki hash length bytes: the issuer spki hash
1318     //   as many times as the indicated serial numbers:
1319     //     1 byte: the length of the serial number
1320     //     serial number length bytes: the serial number
1321     // The stash file consists of any number of these units concatenated
1322     // together.
1323     loop {
1324         let num_serials = match reader.read_u32::<LittleEndian>() {
1325             Ok(num_serials) => num_serials,
1326             Err(_) => break, // end of input, presumably
1327         };
1328         let issuer_spki_hash_len = reader.read_u8().map_err(|e| {
1329             SecurityStateError::from(format!("error reading stash issuer_spki_hash_len: {}", e))
1330         })?;
1331         let mut issuer_spki_hash = vec![0; issuer_spki_hash_len.into()];
1332         reader.read_exact(&mut issuer_spki_hash).map_err(|e| {
1333             SecurityStateError::from(format!("error reading stash issuer_spki_hash: {}", e))
1334         })?;
1335         let serials = dest.entry(issuer_spki_hash).or_insert(HashSet::new());
1336         for _ in 0..num_serials {
1337             let serial_len = reader.read_u8().map_err(|e| {
1338                 SecurityStateError::from(format!("error reading stash serial_len: {}", e))
1339             })?;
1340             let mut serial = vec![0; serial_len.into()];
1341             reader.read_exact(&mut serial).map_err(|e| {
1342                 SecurityStateError::from(format!("error reading stash serial: {}", e))
1343             })?;
1344             let _ = serials.insert(serial);
1345         }
1346     }
1347     Ok(())
1350 // This is a helper struct that implements the task that asynchronously reads the CRLite stash on a
1351 // background thread.
1352 struct BackgroundReadStashTask {
1353     profile_path: PathBuf,
1354     security_state: Arc<RwLock<SecurityState>>,
1357 impl BackgroundReadStashTask {
1358     fn new(
1359         profile_path: PathBuf,
1360         security_state: &Arc<RwLock<SecurityState>>,
1361     ) -> BackgroundReadStashTask {
1362         BackgroundReadStashTask {
1363             profile_path,
1364             security_state: Arc::clone(security_state),
1365         }
1366     }
1369 impl Task for BackgroundReadStashTask {
1370     fn run(&self) {
1371         let Ok(store_path) = get_store_path(&self.profile_path) else {
1372             error!("error getting security_state path");
1373             return;
1374         };
1376         let mut delta_filters = vec![];
1377         if let Ok(mut entries) = std::fs::read_dir(&store_path) {
1378             while let Some(Ok(entry)) = entries.next() {
1379                 let entry_path = entry.path();
1380                 let extension = entry_path
1381                     .extension()
1382                     .map(|os_str| os_str.to_str())
1383                     .flatten();
1384                 if extension != Some("delta") {
1385                     continue;
1386                 }
1387                 if let Ok(Some(filter)) = Filter::load(&store_path, &entry.file_name()) {
1388                     delta_filters.push(filter);
1389                 }
1390             }
1391         }
1393         let mut maybe_crlite_stash = None;
1394         let stash_path = store_path.join("crlite.stash");
1395         // Before we've downloaded any stashes, this file won't exist.
1396         if stash_path.exists() {
1397             if let Ok(stash_file) = File::open(stash_path) {
1398                 let mut stash_reader = BufReader::new(stash_file);
1399                 let mut crlite_stash = HashMap::new();
1400                 match load_crlite_stash_from_reader_into_map(&mut stash_reader, &mut crlite_stash) {
1401                     Ok(()) => maybe_crlite_stash = Some(crlite_stash),
1402                     Err(e) => {
1403                         error!("error loading crlite stash: {}", e.message);
1404                     }
1405                 }
1406             } else {
1407                 error!("error opening stash file");
1408             }
1409         }
1410         let Ok(mut ss) = self.security_state.write() else {
1411             return;
1412         };
1413         if let Err(e) = ss.open_db() {
1414             error!("error opening security state db: {}", e.message);
1415         }
1416         ss.crlite_filters.append(&mut delta_filters);
1417         ss.crlite_stash = maybe_crlite_stash;
1418     }
1420     fn done(&self) -> Result<(), nsresult> {
1421         Ok(())
1422     }
1425 fn do_construct_cert_storage(
1426     iid: *const xpcom::nsIID,
1427     result: *mut *mut xpcom::reexports::libc::c_void,
1428 ) -> Result<(), nserror::nsresult> {
1429     let path_buf = get_profile_path()?;
1430     let security_state = Arc::new(RwLock::new(SecurityState::new(path_buf.clone())));
1431     let cert_storage = CertStorage::allocate(InitCertStorage {
1432         security_state: security_state.clone(),
1433         queue: create_background_task_queue(cstr!("cert_storage"))?,
1434     });
1435     let memory_reporter = MemoryReporter::allocate(InitMemoryReporter { security_state });
1437     // Dispatch a task to the background task queue to asynchronously read the CRLite stash file (if
1438     // present) and load it into cert_storage. This task does not hold the
1439     // cert_storage.security_state mutex for the majority of its operation, which allows certificate
1440     // verification threads to query cert_storage without blocking. This is important for
1441     // performance, but it means that certificate verifications that happen before the task has
1442     // completed will not have stash information, and thus may not know of revocations that have
1443     // occurred since the last full CRLite filter was downloaded. As long as the last full filter
1444     // was downloaded no more than 10 days ago, this is no worse than relying on OCSP responses,
1445     // which have a maximum validity of 10 days.
1446     // NB: because the background task queue is serial, this task will complete before other tasks
1447     // later dispatched to the queue run. This means that other tasks that interact with the stash
1448     // will do so with the correct set of preconditions.
1449     let load_crlite_stash_task = Box::new(BackgroundReadStashTask::new(
1450         path_buf,
1451         &cert_storage.security_state,
1452     ));
1453     let runnable = TaskRunnable::new("LoadCrliteStash", load_crlite_stash_task)?;
1454     TaskRunnable::dispatch(runnable, cert_storage.queue.coerce())?;
1456     if let Some(reporter) = memory_reporter.query_interface::<nsIMemoryReporter>() {
1457         if let Some(reporter_manager) = xpcom::get_service::<nsIMemoryReporterManager>(cstr!(
1458             "@mozilla.org/memory-reporter-manager;1"
1459         )) {
1460             unsafe { reporter_manager.RegisterStrongReporter(&*reporter) };
1461         }
1462     }
1464     unsafe { cert_storage.QueryInterface(iid, result).to_result() }
1467 // This is a helper for creating a task that will perform a specific action on a background thread.
1468 struct SecurityStateTask<
1469     T: Default + VariantType,
1470     F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>,
1471 > {
1472     callback: AtomicCell<Option<ThreadBoundRefPtr<nsICertStorageCallback>>>,
1473     security_state: Arc<RwLock<SecurityState>>,
1474     result: AtomicCell<(nserror::nsresult, T)>,
1475     task_action: AtomicCell<Option<F>>,
1478 impl<T: Default + VariantType, F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>>
1479     SecurityStateTask<T, F>
1481     fn new(
1482         callback: &nsICertStorageCallback,
1483         security_state: &Arc<RwLock<SecurityState>>,
1484         task_action: F,
1485     ) -> Result<SecurityStateTask<T, F>, nsresult> {
1486         let mut ss = security_state.write().or(Err(NS_ERROR_FAILURE))?;
1487         ss.remaining_ops = ss.remaining_ops.wrapping_add(1);
1489         Ok(SecurityStateTask {
1490             callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(RefPtr::new(callback)))),
1491             security_state: Arc::clone(security_state),
1492             result: AtomicCell::new((NS_ERROR_FAILURE, T::default())),
1493             task_action: AtomicCell::new(Some(task_action)),
1494         })
1495     }
1498 impl<T: Default + VariantType, F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>> Task
1499     for SecurityStateTask<T, F>
1501     fn run(&self) {
1502         let mut ss = match self.security_state.write() {
1503             Ok(ss) => ss,
1504             Err(_) => return,
1505         };
1506         // this is a no-op if the DB is already open
1507         if ss.open_db().is_err() {
1508             return;
1509         }
1510         if let Some(task_action) = self.task_action.swap(None) {
1511             let rv = task_action(&mut ss)
1512                 .and_then(|v| Ok((NS_OK, v)))
1513                 .unwrap_or((NS_ERROR_FAILURE, T::default()));
1514             self.result.store(rv);
1515         }
1516     }
1518     fn done(&self) -> Result<(), nsresult> {
1519         let threadbound = self.callback.swap(None).ok_or(NS_ERROR_FAILURE)?;
1520         let callback = threadbound.get_ref().ok_or(NS_ERROR_FAILURE)?;
1521         let result = self.result.swap((NS_ERROR_FAILURE, T::default()));
1522         let variant = result.1.into_variant();
1523         let nsrv = unsafe { callback.Done(result.0, &*variant) };
1525         let mut ss = self.security_state.write().or(Err(NS_ERROR_FAILURE))?;
1526         ss.remaining_ops = ss.remaining_ops.wrapping_sub(1);
1528         match nsrv {
1529             NS_OK => Ok(()),
1530             e => Err(e),
1531         }
1532     }
1535 #[no_mangle]
1536 pub extern "C" fn cert_storage_constructor(
1537     iid: *const xpcom::nsIID,
1538     result: *mut *mut xpcom::reexports::libc::c_void,
1539 ) -> nserror::nsresult {
1540     if !is_main_thread() {
1541         return NS_ERROR_NOT_SAME_THREAD;
1542     }
1543     match do_construct_cert_storage(iid, result) {
1544         Ok(()) => NS_OK,
1545         Err(e) => e,
1546     }
1549 macro_rules! try_ns {
1550     ($e:expr) => {
1551         match $e {
1552             Ok(value) => value,
1553             Err(_) => return NS_ERROR_FAILURE,
1554         }
1555     };
1556     ($e:expr, or continue) => {
1557         match $e {
1558             Ok(value) => value,
1559             Err(err) => {
1560                 error!("{}", err);
1561                 continue;
1562             }
1563         }
1564     };
1567 // This macro is a way to ensure the DB has been opened while minimizing lock acquisitions in the
1568 // common (read-only) case. First we acquire a read lock and see if we even need to open the DB. If
1569 // not, we can continue with the read lock we already have. Otherwise, we drop the read lock,
1570 // acquire the write lock, open the DB, drop the write lock, and re-acquire the read lock. While it
1571 // is possible for two or more threads to all come to the conclusion that they need to open the DB,
1572 // this isn't ultimately an issue - `open_db` will exit early if another thread has already done the
1573 // work.
1574 macro_rules! get_security_state {
1575     ($self:expr) => {{
1576         let ss_read_only = try_ns!($self.security_state.read());
1577         if !ss_read_only.db_needs_opening() {
1578             ss_read_only
1579         } else {
1580             drop(ss_read_only);
1581             {
1582                 let mut ss_write = try_ns!($self.security_state.write());
1583                 try_ns!(ss_write.open_db());
1584             }
1585             try_ns!($self.security_state.read())
1586         }
1587     }};
1590 #[xpcom(implement(nsICertStorage), atomic)]
1591 struct CertStorage {
1592     security_state: Arc<RwLock<SecurityState>>,
1593     queue: RefPtr<nsISerialEventTarget>,
1596 /// CertStorage implements the nsICertStorage interface. The actual work is done by the
1597 /// SecurityState. To handle any threading issues, we have an atomic-refcounted read/write lock on
1598 /// the one and only SecurityState. So, only one thread can use SecurityState's &mut self functions
1599 /// at a time, while multiple threads can use &self functions simultaneously (as long as there are
1600 /// no threads using an &mut self function). The Arc is to allow for the creation of background
1601 /// tasks that use the SecurityState on the queue owned by CertStorage. This allows us to not block
1602 /// the main thread.
1603 #[allow(non_snake_case)]
1604 impl CertStorage {
1605     unsafe fn HasPriorData(
1606         &self,
1607         data_type: u8,
1608         callback: *const nsICertStorageCallback,
1609     ) -> nserror::nsresult {
1610         if !is_main_thread() {
1611             return NS_ERROR_NOT_SAME_THREAD;
1612         }
1613         if callback.is_null() {
1614             return NS_ERROR_NULL_POINTER;
1615         }
1616         let task = Box::new(try_ns!(SecurityStateTask::new(
1617             &*callback,
1618             &self.security_state,
1619             move |ss| ss.get_has_prior_data(data_type),
1620         )));
1621         let runnable = try_ns!(TaskRunnable::new("HasPriorData", task));
1622         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1623         NS_OK
1624     }
1626     unsafe fn GetRemainingOperationCount(&self, state: *mut i32) -> nserror::nsresult {
1627         if !is_main_thread() {
1628             return NS_ERROR_NOT_SAME_THREAD;
1629         }
1630         if state.is_null() {
1631             return NS_ERROR_NULL_POINTER;
1632         }
1633         let ss = try_ns!(self.security_state.read());
1634         *state = ss.remaining_ops;
1635         NS_OK
1636     }
1638     unsafe fn SetRevocations(
1639         &self,
1640         revocations: *const ThinVec<Option<RefPtr<nsIRevocationState>>>,
1641         callback: *const nsICertStorageCallback,
1642     ) -> nserror::nsresult {
1643         if !is_main_thread() {
1644             return NS_ERROR_NOT_SAME_THREAD;
1645         }
1646         if revocations.is_null() || callback.is_null() {
1647             return NS_ERROR_NULL_POINTER;
1648         }
1650         let revocations = &*revocations;
1651         let mut entries = Vec::with_capacity(revocations.len());
1653         // By continuing when an nsIRevocationState attribute value is invalid,
1654         // we prevent errors relating to individual blocklist entries from
1655         // causing sync to fail. We will accumulate telemetry on these failures
1656         // in bug 1254099.
1658         for revocation in revocations.iter().flatten() {
1659             let mut state: i16 = 0;
1660             try_ns!(revocation.GetState(&mut state).to_result(), or continue);
1662             if let Some(revocation) =
1663                 (*revocation).query_interface::<nsIIssuerAndSerialRevocationState>()
1664             {
1665                 let mut issuer = nsCString::new();
1666                 try_ns!(revocation.GetIssuer(&mut *issuer).to_result(), or continue);
1668                 let mut serial = nsCString::new();
1669                 try_ns!(revocation.GetSerial(&mut *serial).to_result(), or continue);
1671                 entries.push(EncodedSecurityState::new(
1672                     PREFIX_REV_IS,
1673                     issuer,
1674                     serial,
1675                     state,
1676                 ));
1677             } else if let Some(revocation) =
1678                 (*revocation).query_interface::<nsISubjectAndPubKeyRevocationState>()
1679             {
1680                 let mut subject = nsCString::new();
1681                 try_ns!(revocation.GetSubject(&mut *subject).to_result(), or continue);
1683                 let mut pub_key_hash = nsCString::new();
1684                 try_ns!(revocation.GetPubKey(&mut *pub_key_hash).to_result(), or continue);
1686                 entries.push(EncodedSecurityState::new(
1687                     PREFIX_REV_SPK,
1688                     subject,
1689                     pub_key_hash,
1690                     state,
1691                 ));
1692             }
1693         }
1695         let task = Box::new(try_ns!(SecurityStateTask::new(
1696             &*callback,
1697             &self.security_state,
1698             move |ss| ss.set_batch_state(&entries, nsICertStorage::DATA_TYPE_REVOCATION),
1699         )));
1700         let runnable = try_ns!(TaskRunnable::new("SetRevocations", task));
1701         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1702         NS_OK
1703     }
1705     unsafe fn GetRevocationState(
1706         &self,
1707         issuer: *const ThinVec<u8>,
1708         serial: *const ThinVec<u8>,
1709         subject: *const ThinVec<u8>,
1710         pub_key: *const ThinVec<u8>,
1711         state: *mut i16,
1712     ) -> nserror::nsresult {
1713         // TODO (bug 1541212): We really want to restrict this to non-main-threads only in non-test
1714         // contexts, but we can't do so until bug 1406854 is fixed.
1715         if issuer.is_null() || serial.is_null() || subject.is_null() || pub_key.is_null() {
1716             return NS_ERROR_NULL_POINTER;
1717         }
1718         *state = nsICertStorage::STATE_UNSET;
1719         let ss = get_security_state!(self);
1720         match ss.get_revocation_state(&*issuer, &*serial, &*subject, &*pub_key) {
1721             Ok(st) => {
1722                 *state = st;
1723                 NS_OK
1724             }
1725             _ => NS_ERROR_FAILURE,
1726         }
1727     }
1729     unsafe fn SetFullCRLiteFilter(
1730         &self,
1731         filter: *const ThinVec<u8>,
1732         enrolled_issuers: *const ThinVec<nsCString>,
1733         coverage: *const ThinVec<Option<RefPtr<nsICRLiteCoverage>>>,
1734         callback: *const nsICertStorageCallback,
1735     ) -> nserror::nsresult {
1736         if !is_main_thread() {
1737             return NS_ERROR_NOT_SAME_THREAD;
1738         }
1739         if filter.is_null()
1740             || coverage.is_null()
1741             || callback.is_null()
1742             || enrolled_issuers.is_null()
1743         {
1744             return NS_ERROR_NULL_POINTER;
1745         }
1747         let filter_owned = (*filter).to_vec();
1748         let enrolled_issuers_owned = (*enrolled_issuers).to_vec();
1750         let coverage = &*coverage;
1751         let mut coverage_entries = Vec::with_capacity(coverage.len());
1752         for entry in coverage.iter().flatten() {
1753             let mut b64_log_id = nsCString::new();
1754             try_ns!((*entry).GetB64LogID(&mut *b64_log_id).to_result(), or continue);
1755             let mut min_timestamp: u64 = 0;
1756             try_ns!((*entry).GetMinTimestamp(&mut min_timestamp).to_result(), or continue);
1757             let mut max_timestamp: u64 = 0;
1758             try_ns!((*entry).GetMaxTimestamp(&mut max_timestamp).to_result(), or continue);
1759             coverage_entries.push((b64_log_id, min_timestamp, max_timestamp));
1760         }
1762         let task = Box::new(try_ns!(SecurityStateTask::new(
1763             &*callback,
1764             &self.security_state,
1765             move |ss| ss.set_full_crlite_filter(
1766                 filter_owned,
1767                 enrolled_issuers_owned,
1768                 &coverage_entries
1769             ),
1770         )));
1771         let runnable = try_ns!(TaskRunnable::new("SetFullCRLiteFilter", task));
1772         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1773         NS_OK
1774     }
1776     unsafe fn AddCRLiteStash(
1777         &self,
1778         stash: *const ThinVec<u8>,
1779         callback: *const nsICertStorageCallback,
1780     ) -> nserror::nsresult {
1781         if !is_main_thread() {
1782             return NS_ERROR_NOT_SAME_THREAD;
1783         }
1784         if stash.is_null() || callback.is_null() {
1785             return NS_ERROR_NULL_POINTER;
1786         }
1787         let stash_owned = (*stash).to_vec();
1788         let task = Box::new(try_ns!(SecurityStateTask::new(
1789             &*callback,
1790             &self.security_state,
1791             move |ss| ss.add_crlite_stash(stash_owned),
1792         )));
1793         let runnable = try_ns!(TaskRunnable::new("AddCRLiteStash", task));
1794         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1795         NS_OK
1796     }
1798     unsafe fn AddCRLiteDelta(
1799         &self,
1800         delta: *const ThinVec<u8>,
1801         filename: *const nsACString,
1802         callback: *const nsICertStorageCallback,
1803     ) -> nserror::nsresult {
1804         if !is_main_thread() {
1805             return NS_ERROR_NOT_SAME_THREAD;
1806         }
1807         if delta.is_null() || filename.is_null() || callback.is_null() {
1808             return NS_ERROR_NULL_POINTER;
1809         }
1810         let delta_owned = (*delta).to_vec();
1811         let filename_owned = (*filename).to_string();
1812         let task = Box::new(try_ns!(SecurityStateTask::new(
1813             &*callback,
1814             &self.security_state,
1815             move |ss| ss.add_crlite_delta(delta_owned, filename_owned),
1816         )));
1817         let runnable = try_ns!(TaskRunnable::new("AddCRLiteDelta", task));
1818         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1819         NS_OK
1820     }
1822     unsafe fn IsCertRevokedByStash(
1823         &self,
1824         issuer_spki: *const ThinVec<u8>,
1825         serial_number: *const ThinVec<u8>,
1826         is_revoked: *mut bool,
1827     ) -> nserror::nsresult {
1828         if issuer_spki.is_null() || serial_number.is_null() || is_revoked.is_null() {
1829             return NS_ERROR_NULL_POINTER;
1830         }
1831         let ss = get_security_state!(self);
1832         *is_revoked = match ss.is_cert_revoked_by_stash(&*issuer_spki, &*serial_number) {
1833             Ok(is_revoked) => is_revoked,
1834             Err(_) => return NS_ERROR_FAILURE,
1835         };
1836         NS_OK
1837     }
1839     unsafe fn GetCRLiteRevocationState(
1840         &self,
1841         issuer: *const ThinVec<u8>,
1842         issuerSPKI: *const ThinVec<u8>,
1843         serialNumber: *const ThinVec<u8>,
1844         timestamps: *const ThinVec<Option<RefPtr<nsICRLiteTimestamp>>>,
1845         state: *mut i16,
1846     ) -> nserror::nsresult {
1847         // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
1848         // can't do so until bug 1406854 is fixed.
1849         if issuer.is_null()
1850             || issuerSPKI.is_null()
1851             || serialNumber.is_null()
1852             || state.is_null()
1853             || timestamps.is_null()
1854         {
1855             return NS_ERROR_NULL_POINTER;
1856         }
1857         let timestamps = &*timestamps;
1858         let mut timestamp_entries = Vec::with_capacity(timestamps.len());
1859         for timestamp_entry in timestamps.iter().flatten() {
1860             let mut log_id = ThinVec::with_capacity(32);
1861             try_ns!(timestamp_entry.GetLogID(&mut log_id).to_result(), or continue);
1862             let mut timestamp: u64 = 0;
1863             try_ns!(timestamp_entry.GetTimestamp(&mut timestamp).to_result(), or continue);
1864             timestamp_entries.push(CRLiteTimestamp { log_id, timestamp });
1865         }
1866         let ss = get_security_state!(self);
1867         *state = ss.get_crlite_revocation_state(
1868             &*issuer,
1869             &*issuerSPKI,
1870             &*serialNumber,
1871             &timestamp_entries,
1872         );
1873         NS_OK
1874     }
1876     unsafe fn AddCerts(
1877         &self,
1878         certs: *const ThinVec<Option<RefPtr<nsICertInfo>>>,
1879         callback: *const nsICertStorageCallback,
1880     ) -> nserror::nsresult {
1881         if !is_main_thread() {
1882             return NS_ERROR_NOT_SAME_THREAD;
1883         }
1884         if certs.is_null() || callback.is_null() {
1885             return NS_ERROR_NULL_POINTER;
1886         }
1887         let certs = &*certs;
1888         let mut cert_entries = Vec::with_capacity(certs.len());
1889         for cert in certs.iter().flatten() {
1890             let mut der = nsCString::new();
1891             try_ns!((*cert).GetCert(&mut *der).to_result(), or continue);
1892             let mut subject = nsCString::new();
1893             try_ns!((*cert).GetSubject(&mut *subject).to_result(), or continue);
1894             let mut trust: i16 = 0;
1895             try_ns!((*cert).GetTrust(&mut trust).to_result(), or continue);
1896             cert_entries.push((der, subject, trust));
1897         }
1898         let task = Box::new(try_ns!(SecurityStateTask::new(
1899             &*callback,
1900             &self.security_state,
1901             move |ss| ss.add_certs(&cert_entries),
1902         )));
1903         let runnable = try_ns!(TaskRunnable::new("AddCerts", task));
1904         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1905         NS_OK
1906     }
1908     unsafe fn RemoveCertsByHashes(
1909         &self,
1910         hashes: *const ThinVec<nsCString>,
1911         callback: *const nsICertStorageCallback,
1912     ) -> nserror::nsresult {
1913         if !is_main_thread() {
1914             return NS_ERROR_NOT_SAME_THREAD;
1915         }
1916         if hashes.is_null() || callback.is_null() {
1917             return NS_ERROR_NULL_POINTER;
1918         }
1919         let hashes = (*hashes).to_vec();
1920         let task = Box::new(try_ns!(SecurityStateTask::new(
1921             &*callback,
1922             &self.security_state,
1923             move |ss| ss.remove_certs_by_hashes(&hashes),
1924         )));
1925         let runnable = try_ns!(TaskRunnable::new("RemoveCertsByHashes", task));
1926         try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1927         NS_OK
1928     }
1930     unsafe fn FindCertsBySubject(
1931         &self,
1932         subject: *const ThinVec<u8>,
1933         certs: *mut ThinVec<ThinVec<u8>>,
1934     ) -> nserror::nsresult {
1935         // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
1936         // can't do so until bug 1406854 is fixed.
1937         if subject.is_null() || certs.is_null() {
1938             return NS_ERROR_NULL_POINTER;
1939         }
1940         let ss = get_security_state!(self);
1941         match ss.find_certs_by_subject(&*subject, &mut *certs) {
1942             Ok(()) => NS_OK,
1943             Err(_) => NS_ERROR_FAILURE,
1944         }
1945     }
1948 extern "C" {
1949     fn cert_storage_malloc_size_of(ptr: *const xpcom::reexports::libc::c_void) -> usize;
1952 #[xpcom(implement(nsIMemoryReporter), atomic)]
1953 struct MemoryReporter {
1954     security_state: Arc<RwLock<SecurityState>>,
1957 #[allow(non_snake_case)]
1958 impl MemoryReporter {
1959     unsafe fn CollectReports(
1960         &self,
1961         callback: *const nsIHandleReportCallback,
1962         data: *const nsISupports,
1963         _anonymize: bool,
1964     ) -> nserror::nsresult {
1965         let ss = try_ns!(self.security_state.read());
1966         let size = ss.note_memory_usage();
1967         let callback = match RefPtr::from_raw(callback) {
1968             Some(ptr) => ptr,
1969             None => return NS_ERROR_UNEXPECTED,
1970         };
1971         // This does the same as MOZ_COLLECT_REPORT
1972         callback.Callback(
1973             &nsCStr::new() as &nsACString,
1974             &nsCStr::from("explicit/cert-storage/storage") as &nsACString,
1975             nsIMemoryReporter::KIND_HEAP,
1976             nsIMemoryReporter::UNITS_BYTES,
1977             size as i64,
1978             &nsCStr::from("Memory used by certificate storage") as &nsACString,
1979             data,
1980         );
1981         NS_OK
1982     }