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/. */
6 extern crate byteorder;
8 extern crate clubcard_crlite;
9 extern crate crossbeam_utils;
12 extern crate firefox_on_glean;
15 extern crate moz_task;
17 extern crate nsstring;
19 extern crate rust_cascade;
21 extern crate thin_vec;
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};
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};
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 ),+ ) => {
88 let mut key = $prefix.as_bytes().to_owned();
89 $( key.extend_from_slice($part); )+
95 #[allow(non_camel_case_types, non_snake_case)]
97 /// `SecurityStateError` is a type to represent errors in accessing or
98 /// modifying security state.
100 struct SecurityStateError {
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 {
109 message: format!("{}", err),
119 impl MallocSizeOf for EnvAndStore {
120 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
124 let iter = self.store.iter_start(&reader)?.into_iter();
127 r.map(|(k, v)| k.len() + v.serialized_size().unwrap_or(0) as usize)
136 struct CascadeWithMetadata {
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 {
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"));
154 if !enrollment_path.exists() {
155 return Err(SecurityStateError::from("missing enrollment file"));
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")),
167 if (coverage_file_len - 1) % COVERAGE_V1_ENTRY_BYTES != 0 {
168 return Err(SecurityStateError::from("truncated CRLite coverage file"));
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) {
176 _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
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")),
185 match (&coverage_entry[40..48]).read_u64::<LittleEndian>() {
186 Ok(value) => max_timestamp = value,
187 _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
189 coverage.insert(log_id.to_vec(), (min_timestamp, max_timestamp));
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) => (),
200 return Err(SecurityStateError::from(
201 "unknown CRLite enrollment version",
205 if (enrollment_file_len - 1) % ENROLLMENT_V1_ENTRY_BYTES != 0 {
206 return Err(SecurityStateError::from("truncated CRLite enrollment file"));
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) {
214 _ => return Err(SecurityStateError::from("truncated CRLite enrollment file")),
216 let issuer_id = &enrollment_entry[..];
217 enrollment.insert(issuer_id.to_vec());
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;
237 covered_timestamp_count
238 >= static_prefs::pref!("security.pki.crlite_timestamps_for_coverage")
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())
249 fn has(&self, crlite_key: &[u8]) -> bool {
250 self.cascade.has(crlite_key.to_vec())
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)
263 Cascade(CascadeWithMetadata),
264 Clubcard(CRLiteClubcard),
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() {
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"));
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() {
290 let cascade_with_metadata =
291 CascadeWithMetadata::new(cascade, &coverage_path, &enrollment_path)?;
292 Ok(Some(Filter::Cascade(cascade_with_metadata)))
294 Err(SecurityStateError::from("invalid CRLite filter"))
302 issuer_spki_hash: &[u8],
304 clubcard_crlite_key: &CRLiteKey,
305 timestamps: &[CRLiteTimestamp],
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
319 nsICertStorage::STATE_UNSET
322 Filter::Clubcard(clubcard) => {
323 let timestamp_iter = timestamps
326 (&*timestamp.log_id) /* ThinVec -> &[u8; 32] */
329 .map(|log_id| (log_id, timestamp.timestamp))
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())
337 covered_timestamp_count += 1;
340 if covered_timestamp_count
341 < static_prefs::pref!("security.pki.crlite_timestamps_for_coverage")
343 return nsICertStorage::STATE_NOT_COVERED;
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,
356 impl MallocSizeOf for Filter {
357 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
359 Filter::Cascade(cascade) => cascade.size_of(ops),
360 Filter::Clubcard(clubcard) => clubcard.approximate_size_of(),
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.
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.
383 crlite_filters: vec![],
389 pub fn db_needs_opening(&self) -> bool {
390 self.env_and_store.is_none()
393 pub fn open_db(&mut self) -> Result<(), SecurityStateError> {
394 if self.env_and_store.is_some() {
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
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())
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)?;
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?)",
425 self.load_crlite_filter()?;
430 revocations_path: &PathBuf,
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")) {
444 Err(e) => return Err(e),
446 if l.len() == 0 || l.starts_with("#") {
449 let leading_char = match l.chars().next() {
452 return Err(SecurityStateError::from(
453 "couldn't get char from non-empty str?",
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);
466 let l_sans_prefix = match BASE64_STANDARD.decode(&l[1..]) {
467 Ok(decoded) => decoded,
470 if let Some(name) = &dn {
471 if leading_char == '\t' {
474 &make_key!(PREFIX_REV_SPK, name, &l_sans_prefix),
480 &make_key!(PREFIX_REV_IS, name, &l_sans_prefix),
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?")),
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")
503 Ok(None) => Ok(None),
504 Ok(_) => Err(SecurityStateError::from(
505 "Unexpected type when trying to get a Value::I64",
507 Err(_) => Err(SecurityStateError::from(
508 "There was a problem getting the value",
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());
517 if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_INCREMENTAL {
518 return Ok(self.crlite_stash.is_some() || self.crlite_filters.len() > 1);
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?")),
525 let reader = env_and_store.env.read()?;
528 .get(&reader, &make_key!(PREFIX_DATA_TYPE, &[data_type]))
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",
535 Err(_) => Err(SecurityStateError::from(
536 "There was a problem getting the value",
541 pub fn set_batch_state(
543 entries: &[EncodedSecurityState],
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?")),
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(
554 &make_key!(PREFIX_DATA_TYPE, &[typ]),
558 for entry in entries {
559 let key = match entry.key() {
562 warn!("error base64-decoding key parts - ignoring: {}", e.message);
568 .put(&mut writer, &key, &Value::I64(entry.state() as i64))?;
575 pub fn get_revocation_state(
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,
593 return Err(SecurityStateError::from(
594 "problem reading revocation state (from issuer / serial)",
599 if st != nsICertStorage::STATE_UNSET {
603 match self.read_entry(&subject_pubkey) {
604 Ok(Some(value)) => Ok(value),
605 Ok(None) => Ok(nsICertStorage::STATE_UNSET),
607 return Err(SecurityStateError::from(
608 "problem reading revocation state (from subject / pubkey)",
614 fn note_crlite_update_time(&mut self) -> Result<(), SecurityStateError> {
615 let seconds_since_epoch = Value::U64(
617 .duration_since(UNIX_EPOCH)
618 .map_err(|_| SecurityStateError::from("could not get current time"))?
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?")),
625 let mut writer = env_and_store.env.write()?;
628 .put(&mut writer, LAST_CRLITE_UPDATE_KEY, &seconds_since_epoch)
629 .map_err(|_| SecurityStateError::from("could not store timestamp"))?;
634 fn is_crlite_fresh(&self) -> bool {
635 let now = match SystemTime::now().duration_since(UNIX_EPOCH) {
636 Ok(t) => t.as_secs(),
639 let env_and_store = match self.env_and_store.as_ref() {
640 Some(env_and_store) => env_and_store,
641 None => return false,
643 let reader = match env_and_store.env.read() {
644 Ok(reader) => reader,
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
655 pub fn set_full_crlite_filter(
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 {
672 let entry_path = entry.path();
673 let extension = entry_path
675 .map(|os_str| os_str.to_str())
677 if extension == Some("stash") || extension == Some("delta") {
678 let _ = std::fs::remove_file(entry_path);
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,
695 warn!("malformed log ID - skipping: {}", b64_log_id);
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());
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,
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,
716 warn!("malformed issuer ID - skipping: {}", b64_issuer_id);
720 enrollment_bytes.extend_from_slice(&issuer_id);
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();
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",
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);
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();
759 pub fn add_crlite_delta(
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);
770 self.note_crlite_update_time()?;
771 self.note_memory_usage();
775 pub fn is_cert_revoked_by_stash(
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),
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),
791 Ok(serials.contains(&serial.to_vec()))
794 pub fn get_crlite_revocation_state(
798 serial_number: &[u8],
799 timestamps: &[CRLiteTimestamp],
801 if !self.is_crlite_fresh() {
802 return nsICertStorage::STATE_NO_FILTER;
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;
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 {
818 issuer_spki_hash.as_ref(),
820 &clubcard_crlite_key,
823 nsICertStorage::STATE_ENFORCE => return nsICertStorage::STATE_ENFORCE,
824 nsICertStorage::STATE_UNSET => maybe_good = true,
825 nsICertStorage::STATE_NOT_ENROLLED => covered = true,
830 return nsICertStorage::STATE_UNSET;
833 return nsICertStorage::STATE_NOT_ENROLLED;
835 nsICertStorage::STATE_NOT_COVERED
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.
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?")),
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(
860 &make_key!(PREFIX_DATA_TYPE, &[nsICertStorage::DATA_TYPE_CERTIFICATE]),
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,
868 warn!("error base64-decoding cert - skipping: {}", e);
872 let subject = match BASE64_STANDARD.decode(&subject_base64) {
873 Ok(subject) => subject,
875 warn!("error base64-decoding subject - skipping: {}", e);
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)?;
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,
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(
899 &Value::Blob(&new_cert_hash_list),
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(
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?")),
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) {
927 warn!("error decoding hash - ignoring: {}", e);
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,
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(
948 &Value::Blob(&new_cert_hash_list),
953 match env_and_store.store.delete(&mut writer, &cert_key) {
955 Err(StoreError::KeyValuePairNotFound) => {}
956 Err(e) => return Err(SecurityStateError::from(e)),
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(
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?")),
976 let reader = env_and_store.env.read()?;
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,
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);
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);
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)
1016 .map(|filter| filter.size_of(ops))
1018 + self.crlite_stash.size_of(ops)
1019 + self.remaining_ops.size_of(ops)
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).
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"));
1040 if subject.len() > u16::MAX.into() {
1041 return Err(SecurityStateError::from("subject is too long"));
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?"));
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"));
1060 if rest.len() < size_of::<u16>() {
1061 return Err(SecurityStateError::from("invalid Cert: no der len?"));
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?"));
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?"));
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?"));
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?"));
1083 let trust = rest.read_i16::<NetworkEndian>()?;
1085 return Err(SecurityStateError::from("invalid Cert: trailing data?"));
1095 fn to_bytes(&self) -> Result<Vec<u8>, SecurityStateError> {
1096 let mut bytes = Vec::with_capacity(
1101 + self.subject.len()
1104 bytes.write_u8(CERT_SERIALIZATION_VERSION_1)?;
1105 bytes.write_u16::<NetworkEndian>(
1109 .map_err(|_| SecurityStateError::from("certificate is too long"))?,
1111 bytes.extend_from_slice(&self.der);
1112 bytes.write_u16::<NetworkEndian>(
1116 .map_err(|_| SecurityStateError::from("subject is too long"))?,
1118 bytes.extend_from_slice(&self.subject);
1119 bytes.write_i16::<NetworkEndian>(self.trust)?;
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",
1136 let mut hashes = Vec::with_capacity(hashes_bytes.len() / Sha256::output_size());
1137 for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
1140 Ok(CertHashList { hashes })
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",
1149 if new_hash.len() != Sha256::output_size() {
1150 return Err(SecurityStateError::from("unexpected cert hash length"));
1152 for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
1153 if hash == new_hash {
1154 return Ok(hashes_bytes.to_owned());
1157 let mut combined = hashes_bytes.to_owned();
1158 combined.extend_from_slice(new_hash);
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",
1168 if cert_hash.len() != Sha256::output_size() {
1169 return Err(SecurityStateError::from("unexpected cert hash length"));
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);
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()
1190 // Helper struct for get_crlite_revocation_state.
1191 struct CRLiteTimestamp {
1192 log_id: ThinVec<u8>,
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,
1205 impl EncodedSecurityState {
1207 prefix: &'static str,
1208 key_part_1_base64: nsCString,
1209 key_part_2_base64: nsCString,
1211 ) -> EncodedSecurityState {
1212 EncodedSecurityState {
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))
1226 fn state(&self) -> i16 {
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();
1238 (*directory_service)
1241 &nsIFile::IID as *const nsIID,
1242 requested_dir.void_ptr(),
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())?;
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) {
1284 Err(e) => match e.kind() {
1285 std::io::ErrorKind::NotFound => Ok(()),
1286 _ => Err(SecurityStateError::from(e)),
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)?;
1305 // Helper function to read stash information from the given reader and insert the results into the
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
1324 let num_serials = match reader.read_u32::<LittleEndian>() {
1325 Ok(num_serials) => num_serials,
1326 Err(_) => break, // end of input, presumably
1328 let issuer_spki_hash_len = reader.read_u8().map_err(|e| {
1329 SecurityStateError::from(format!("error reading stash issuer_spki_hash_len: {}", e))
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))
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))
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))
1344 let _ = serials.insert(serial);
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 {
1359 profile_path: PathBuf,
1360 security_state: &Arc<RwLock<SecurityState>>,
1361 ) -> BackgroundReadStashTask {
1362 BackgroundReadStashTask {
1364 security_state: Arc::clone(security_state),
1369 impl Task for BackgroundReadStashTask {
1371 let Ok(store_path) = get_store_path(&self.profile_path) else {
1372 error!("error getting security_state path");
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
1382 .map(|os_str| os_str.to_str())
1384 if extension != Some("delta") {
1387 if let Ok(Some(filter)) = Filter::load(&store_path, &entry.file_name()) {
1388 delta_filters.push(filter);
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),
1403 error!("error loading crlite stash: {}", e.message);
1407 error!("error opening stash file");
1410 let Ok(mut ss) = self.security_state.write() else {
1413 if let Err(e) = ss.open_db() {
1414 error!("error opening security state db: {}", e.message);
1416 ss.crlite_filters.append(&mut delta_filters);
1417 ss.crlite_stash = maybe_crlite_stash;
1420 fn done(&self) -> Result<(), nsresult> {
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"))?,
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(
1451 &cert_storage.security_state,
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"
1460 unsafe { reporter_manager.RegisterStrongReporter(&*reporter) };
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>,
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>
1482 callback: &nsICertStorageCallback,
1483 security_state: &Arc<RwLock<SecurityState>>,
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)),
1498 impl<T: Default + VariantType, F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>> Task
1499 for SecurityStateTask<T, F>
1502 let mut ss = match self.security_state.write() {
1506 // this is a no-op if the DB is already open
1507 if ss.open_db().is_err() {
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);
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);
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;
1543 match do_construct_cert_storage(iid, result) {
1549 macro_rules! try_ns {
1553 Err(_) => return NS_ERROR_FAILURE,
1556 ($e:expr, or continue) => {
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
1574 macro_rules! get_security_state {
1576 let ss_read_only = try_ns!($self.security_state.read());
1577 if !ss_read_only.db_needs_opening() {
1582 let mut ss_write = try_ns!($self.security_state.write());
1583 try_ns!(ss_write.open_db());
1585 try_ns!($self.security_state.read())
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)]
1605 unsafe fn HasPriorData(
1608 callback: *const nsICertStorageCallback,
1609 ) -> nserror::nsresult {
1610 if !is_main_thread() {
1611 return NS_ERROR_NOT_SAME_THREAD;
1613 if callback.is_null() {
1614 return NS_ERROR_NULL_POINTER;
1616 let task = Box::new(try_ns!(SecurityStateTask::new(
1618 &self.security_state,
1619 move |ss| ss.get_has_prior_data(data_type),
1621 let runnable = try_ns!(TaskRunnable::new("HasPriorData", task));
1622 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1626 unsafe fn GetRemainingOperationCount(&self, state: *mut i32) -> nserror::nsresult {
1627 if !is_main_thread() {
1628 return NS_ERROR_NOT_SAME_THREAD;
1630 if state.is_null() {
1631 return NS_ERROR_NULL_POINTER;
1633 let ss = try_ns!(self.security_state.read());
1634 *state = ss.remaining_ops;
1638 unsafe fn SetRevocations(
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;
1646 if revocations.is_null() || callback.is_null() {
1647 return NS_ERROR_NULL_POINTER;
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
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>()
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(
1677 } else if let Some(revocation) =
1678 (*revocation).query_interface::<nsISubjectAndPubKeyRevocationState>()
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(
1695 let task = Box::new(try_ns!(SecurityStateTask::new(
1697 &self.security_state,
1698 move |ss| ss.set_batch_state(&entries, nsICertStorage::DATA_TYPE_REVOCATION),
1700 let runnable = try_ns!(TaskRunnable::new("SetRevocations", task));
1701 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1705 unsafe fn GetRevocationState(
1707 issuer: *const ThinVec<u8>,
1708 serial: *const ThinVec<u8>,
1709 subject: *const ThinVec<u8>,
1710 pub_key: *const ThinVec<u8>,
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;
1718 *state = nsICertStorage::STATE_UNSET;
1719 let ss = get_security_state!(self);
1720 match ss.get_revocation_state(&*issuer, &*serial, &*subject, &*pub_key) {
1725 _ => NS_ERROR_FAILURE,
1729 unsafe fn SetFullCRLiteFilter(
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;
1740 || coverage.is_null()
1741 || callback.is_null()
1742 || enrolled_issuers.is_null()
1744 return NS_ERROR_NULL_POINTER;
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));
1762 let task = Box::new(try_ns!(SecurityStateTask::new(
1764 &self.security_state,
1765 move |ss| ss.set_full_crlite_filter(
1767 enrolled_issuers_owned,
1771 let runnable = try_ns!(TaskRunnable::new("SetFullCRLiteFilter", task));
1772 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1776 unsafe fn AddCRLiteStash(
1778 stash: *const ThinVec<u8>,
1779 callback: *const nsICertStorageCallback,
1780 ) -> nserror::nsresult {
1781 if !is_main_thread() {
1782 return NS_ERROR_NOT_SAME_THREAD;
1784 if stash.is_null() || callback.is_null() {
1785 return NS_ERROR_NULL_POINTER;
1787 let stash_owned = (*stash).to_vec();
1788 let task = Box::new(try_ns!(SecurityStateTask::new(
1790 &self.security_state,
1791 move |ss| ss.add_crlite_stash(stash_owned),
1793 let runnable = try_ns!(TaskRunnable::new("AddCRLiteStash", task));
1794 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1798 unsafe fn AddCRLiteDelta(
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;
1807 if delta.is_null() || filename.is_null() || callback.is_null() {
1808 return NS_ERROR_NULL_POINTER;
1810 let delta_owned = (*delta).to_vec();
1811 let filename_owned = (*filename).to_string();
1812 let task = Box::new(try_ns!(SecurityStateTask::new(
1814 &self.security_state,
1815 move |ss| ss.add_crlite_delta(delta_owned, filename_owned),
1817 let runnable = try_ns!(TaskRunnable::new("AddCRLiteDelta", task));
1818 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1822 unsafe fn IsCertRevokedByStash(
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;
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,
1839 unsafe fn GetCRLiteRevocationState(
1841 issuer: *const ThinVec<u8>,
1842 issuerSPKI: *const ThinVec<u8>,
1843 serialNumber: *const ThinVec<u8>,
1844 timestamps: *const ThinVec<Option<RefPtr<nsICRLiteTimestamp>>>,
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.
1850 || issuerSPKI.is_null()
1851 || serialNumber.is_null()
1853 || timestamps.is_null()
1855 return NS_ERROR_NULL_POINTER;
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 });
1866 let ss = get_security_state!(self);
1867 *state = ss.get_crlite_revocation_state(
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;
1884 if certs.is_null() || callback.is_null() {
1885 return NS_ERROR_NULL_POINTER;
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));
1898 let task = Box::new(try_ns!(SecurityStateTask::new(
1900 &self.security_state,
1901 move |ss| ss.add_certs(&cert_entries),
1903 let runnable = try_ns!(TaskRunnable::new("AddCerts", task));
1904 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1908 unsafe fn RemoveCertsByHashes(
1910 hashes: *const ThinVec<nsCString>,
1911 callback: *const nsICertStorageCallback,
1912 ) -> nserror::nsresult {
1913 if !is_main_thread() {
1914 return NS_ERROR_NOT_SAME_THREAD;
1916 if hashes.is_null() || callback.is_null() {
1917 return NS_ERROR_NULL_POINTER;
1919 let hashes = (*hashes).to_vec();
1920 let task = Box::new(try_ns!(SecurityStateTask::new(
1922 &self.security_state,
1923 move |ss| ss.remove_certs_by_hashes(&hashes),
1925 let runnable = try_ns!(TaskRunnable::new("RemoveCertsByHashes", task));
1926 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
1930 unsafe fn FindCertsBySubject(
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;
1940 let ss = get_security_state!(self);
1941 match ss.find_certs_by_subject(&*subject, &mut *certs) {
1943 Err(_) => NS_ERROR_FAILURE,
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(
1961 callback: *const nsIHandleReportCallback,
1962 data: *const nsISupports,
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) {
1969 None => return NS_ERROR_UNEXPECTED,
1971 // This does the same as MOZ_COLLECT_REPORT
1973 &nsCStr::new() as &nsACString,
1974 &nsCStr::from("explicit/cert-storage/storage") as &nsACString,
1975 nsIMemoryReporter::KIND_HEAP,
1976 nsIMemoryReporter::UNITS_BYTES,
1978 &nsCStr::from("Memory used by certificate storage") as &nsACString,