lib.rs (62474B)
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/. */ 4 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 #[macro_use] 13 extern crate log; 14 extern crate moz_task; 15 extern crate nserror; 16 extern crate nsstring; 17 extern crate rkv; 18 extern crate sha2; 19 extern crate thin_vec; 20 extern crate time; 21 #[macro_use] 22 extern crate xpcom; 23 extern crate storage_variant; 24 extern crate tempfile; 25 26 extern crate wr_malloc_size_of; 27 use wr_malloc_size_of as malloc_size_of; 28 29 use base64::prelude::*; 30 use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; 31 use clubcard::{ApproximateSizeOf, Queryable}; 32 use clubcard_crlite::{CRLiteClubcard, CRLiteKey, CRLiteQuery, CRLiteStatus}; 33 use crossbeam_utils::atomic::AtomicCell; 34 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; 35 use moz_task::{create_background_task_queue, is_main_thread, Task, TaskRunnable}; 36 use nserror::{ 37 nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_SAME_THREAD, NS_ERROR_NULL_POINTER, 38 NS_ERROR_UNEXPECTED, NS_OK, 39 }; 40 use nsstring::{nsACString, nsCStr, nsCString, nsString}; 41 use rkv::backend::{BackendEnvironmentBuilder, SafeMode, SafeModeDatabase, SafeModeEnvironment}; 42 use rkv::{StoreError, StoreOptions, Value}; 43 use sha2::{Digest, Sha256}; 44 use std::convert::TryInto; 45 use std::ffi::{CString, OsString}; 46 use std::fmt::Display; 47 use std::fs::{create_dir_all, remove_file, File}; 48 use std::io::{BufRead, BufReader}; 49 use std::mem::size_of; 50 use std::path::{Path, PathBuf}; 51 use std::str; 52 use std::sync::{Arc, RwLock}; 53 use std::time::{SystemTime, UNIX_EPOCH}; 54 use storage_variant::VariantType; 55 use thin_vec::ThinVec; 56 use xpcom::interfaces::{ 57 nsICRLiteTimestamp, nsICertInfo, nsICertStorage, nsICertStorageCallback, nsIFile, 58 nsIHandleReportCallback, nsIIssuerAndSerialRevocationState, nsIMemoryReporter, 59 nsIMemoryReporterManager, nsIProperties, nsIRevocationState, nsISerialEventTarget, 60 nsISubjectAndPubKeyRevocationState, nsISupports, 61 }; 62 use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom}; 63 64 const PREFIX_REV_IS: &str = "is"; 65 const PREFIX_REV_SPK: &str = "spk"; 66 const PREFIX_SUBJECT: &str = "subject"; 67 const PREFIX_CERT: &str = "cert"; 68 const PREFIX_DATA_TYPE: &str = "datatype"; 69 70 const LAST_CRLITE_UPDATE_KEY: &str = "last_crlite_update"; 71 72 type Rkv = rkv::Rkv<SafeModeEnvironment>; 73 type SingleStore = rkv::SingleStore<SafeModeDatabase>; 74 75 macro_rules! make_key { 76 ( $prefix:expr, $( $part:expr ),+ ) => { 77 { 78 let mut key = $prefix.as_bytes().to_owned(); 79 $( key.extend_from_slice($part); )+ 80 key 81 } 82 } 83 } 84 85 #[allow(non_camel_case_types, non_snake_case)] 86 87 /// `SecurityStateError` is a type to represent errors in accessing or 88 /// modifying security state. 89 #[derive(Debug)] 90 struct SecurityStateError { 91 message: String, 92 } 93 94 impl<T: Display> From<T> for SecurityStateError { 95 /// Creates a new instance of `SecurityStateError` from something that 96 /// implements the `Display` trait. 97 fn from(err: T) -> SecurityStateError { 98 SecurityStateError { 99 message: format!("{}", err), 100 } 101 } 102 } 103 104 struct EnvAndStore { 105 env: Rkv, 106 store: SingleStore, 107 } 108 109 impl MallocSizeOf for EnvAndStore { 110 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 111 self.env 112 .read() 113 .and_then(|reader| { 114 let iter = self.store.iter_start(&reader)?.into_iter(); 115 Ok(iter 116 .map(|r| { 117 r.map(|(k, v)| k.len() + v.serialized_size().unwrap_or(0) as usize) 118 .unwrap_or(0) 119 }) 120 .sum()) 121 }) 122 .unwrap_or(0) 123 } 124 } 125 126 enum Filter { 127 Clubcard(CRLiteClubcard), 128 } 129 130 impl Filter { 131 fn load( 132 store_path: &PathBuf, 133 filter_filename: &OsString, 134 ) -> Result<Option<Filter>, SecurityStateError> { 135 // Before we've downloaded any filters, this file won't exist. 136 let filter_path = store_path.join(filter_filename); 137 if !filter_path.exists() { 138 return Ok(None); 139 } 140 let filter_bytes = std::fs::read(&filter_path)?; 141 142 if let Ok(clubcard) = CRLiteClubcard::from_bytes(&filter_bytes) { 143 Ok(Some(Filter::Clubcard(clubcard))) 144 } else { 145 Err(SecurityStateError::from("invalid CRLite filter")) 146 } 147 } 148 149 fn has(&self, clubcard_crlite_key: &CRLiteKey, timestamps: &[CRLiteTimestamp]) -> i16 { 150 match self { 151 Filter::Clubcard(clubcard) => { 152 let timestamp_iter = timestamps 153 .iter() 154 .map(|timestamp| { 155 (&*timestamp.log_id) /* ThinVec -> &[u8; 32] */ 156 .try_into() 157 .ok() 158 .map(|log_id| (log_id, timestamp.timestamp)) 159 }) 160 .flatten(); 161 let mut covered_timestamp_count = 0; 162 for timestamp in timestamp_iter.clone() { 163 if CRLiteQuery::new(&clubcard_crlite_key, Some(timestamp)) 164 .in_universe(clubcard.universe()) 165 { 166 covered_timestamp_count += 1; 167 } 168 } 169 if covered_timestamp_count 170 < static_prefs::pref!("security.pki.crlite_timestamps_for_coverage") 171 { 172 return nsICertStorage::STATE_NOT_COVERED; 173 } 174 match clubcard.contains(&clubcard_crlite_key, timestamp_iter) { 175 CRLiteStatus::Good => nsICertStorage::STATE_UNSET, 176 CRLiteStatus::NotCovered => nsICertStorage::STATE_NOT_COVERED, 177 CRLiteStatus::NotEnrolled => nsICertStorage::STATE_NOT_ENROLLED, 178 CRLiteStatus::Revoked => nsICertStorage::STATE_ENFORCE, 179 } 180 } 181 } 182 } 183 } 184 185 impl MallocSizeOf for Filter { 186 fn size_of(&self, _: &mut MallocSizeOfOps) -> usize { 187 match self { 188 Filter::Clubcard(clubcard) => clubcard.approximate_size_of(), 189 } 190 } 191 } 192 193 /// `SecurityState` 194 struct SecurityState { 195 profile_path: PathBuf, 196 env_and_store: Option<EnvAndStore>, 197 crlite_filters: Vec<Filter>, 198 /// Tracks the number of asynchronous operations which have been dispatched but not completed. 199 remaining_ops: i32, 200 } 201 202 impl SecurityState { 203 pub fn new(profile_path: PathBuf) -> SecurityState { 204 // Since this gets called on the main thread, we don't actually want to open the DB yet. 205 // We do this on-demand later, when we're probably on a certificate verification thread. 206 SecurityState { 207 profile_path, 208 env_and_store: None, 209 crlite_filters: vec![], 210 remaining_ops: 0, 211 } 212 } 213 214 pub fn db_needs_opening(&self) -> bool { 215 self.env_and_store.is_none() 216 } 217 218 pub fn open_db(&mut self) -> Result<(), SecurityStateError> { 219 if self.env_and_store.is_some() { 220 return Ok(()); 221 } 222 223 let store_path = get_store_path(&self.profile_path)?; 224 225 // Open the store in read-write mode to create it (if needed) and migrate data from the old 226 // store (if any). 227 // If opening initially fails, try to remove and recreate the database. Consumers will 228 // repopulate the database as necessary if this happens (see bug 1546361). 229 let env = make_env(store_path.as_path()).or_else(|_| { 230 remove_db(store_path.as_path())?; 231 make_env(store_path.as_path()) 232 })?; 233 let store = env.open_single("cert_storage", StoreOptions::create())?; 234 235 // if the profile has a revocations.txt, migrate it and remove the file 236 let mut revocations_path = self.profile_path.clone(); 237 revocations_path.push("revocations.txt"); 238 if revocations_path.exists() { 239 SecurityState::migrate(&revocations_path, &env, &store)?; 240 remove_file(revocations_path)?; 241 } 242 243 // We already returned early if env_and_store was Some, so this should take the None branch. 244 match self.env_and_store.replace(EnvAndStore { env, store }) { 245 Some(_) => Err(SecurityStateError::from( 246 "env and store already initialized? (did we mess up our threading model?)", 247 )), 248 None => Ok(()), 249 }?; 250 self.load_crlite_filter()?; 251 Ok(()) 252 } 253 254 fn migrate( 255 revocations_path: &PathBuf, 256 env: &Rkv, 257 store: &SingleStore, 258 ) -> Result<(), SecurityStateError> { 259 let f = File::open(revocations_path)?; 260 let file = BufReader::new(f); 261 let value = Value::I64(nsICertStorage::STATE_ENFORCE as i64); 262 let mut writer = env.write()?; 263 264 // Add the data from revocations.txt 265 let mut dn: Option<Vec<u8>> = None; 266 for line in file.lines() { 267 let l = match line.map_err(|_| SecurityStateError::from("io error reading line data")) { 268 Ok(data) => data, 269 Err(e) => return Err(e), 270 }; 271 if l.len() == 0 || l.starts_with("#") { 272 continue; 273 } 274 let leading_char = match l.chars().next() { 275 Some(c) => c, 276 None => { 277 return Err(SecurityStateError::from( 278 "couldn't get char from non-empty str?", 279 )); 280 } 281 }; 282 // In future, we can maybe log migration failures. For now, ignore decoding and storage 283 // errors and attempt to continue. 284 // Check if we have a new DN 285 if leading_char != '\t' && leading_char != ' ' { 286 if let Ok(decoded_dn) = BASE64_STANDARD.decode(&l) { 287 dn = Some(decoded_dn); 288 } 289 continue; 290 } 291 let l_sans_prefix = match BASE64_STANDARD.decode(&l[1..]) { 292 Ok(decoded) => decoded, 293 Err(_) => continue, 294 }; 295 if let Some(name) = &dn { 296 if leading_char == '\t' { 297 let _ = store.put( 298 &mut writer, 299 &make_key!(PREFIX_REV_SPK, name, &l_sans_prefix), 300 &value, 301 ); 302 } else { 303 let _ = store.put( 304 &mut writer, 305 &make_key!(PREFIX_REV_IS, name, &l_sans_prefix), 306 &value, 307 ); 308 } 309 } 310 } 311 312 writer.commit()?; 313 Ok(()) 314 } 315 316 fn read_entry(&self, key: &[u8]) -> Result<Option<i16>, SecurityStateError> { 317 let env_and_store = match self.env_and_store.as_ref() { 318 Some(env_and_store) => env_and_store, 319 None => return Err(SecurityStateError::from("env and store not initialized?")), 320 }; 321 let reader = env_and_store.env.read()?; 322 match env_and_store.store.get(&reader, key) { 323 Ok(Some(Value::I64(i))) => { 324 Ok(Some(i.try_into().map_err(|_| { 325 SecurityStateError::from("Stored value out of range for i16") 326 })?)) 327 } 328 Ok(None) => Ok(None), 329 Ok(_) => Err(SecurityStateError::from( 330 "Unexpected type when trying to get a Value::I64", 331 )), 332 Err(_) => Err(SecurityStateError::from( 333 "There was a problem getting the value", 334 )), 335 } 336 } 337 338 pub fn get_has_prior_data(&self, data_type: u8) -> Result<bool, SecurityStateError> { 339 if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_FULL { 340 return Ok(!self.crlite_filters.is_empty()); 341 } 342 if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_INCREMENTAL { 343 return Ok(self.crlite_filters.len() > 1); 344 } 345 346 let env_and_store = match self.env_and_store.as_ref() { 347 Some(env_and_store) => env_and_store, 348 None => return Err(SecurityStateError::from("env and store not initialized?")), 349 }; 350 let reader = env_and_store.env.read()?; 351 match env_and_store 352 .store 353 .get(&reader, &make_key!(PREFIX_DATA_TYPE, &[data_type])) 354 { 355 Ok(Some(Value::Bool(true))) => Ok(true), 356 Ok(None) => Ok(false), 357 Ok(_) => Err(SecurityStateError::from( 358 "Unexpected type when trying to get a Value::Bool", 359 )), 360 Err(_) => Err(SecurityStateError::from( 361 "There was a problem getting the value", 362 )), 363 } 364 } 365 366 pub fn set_batch_state( 367 &mut self, 368 entries: &[EncodedSecurityState], 369 typ: u8, 370 ) -> Result<(), SecurityStateError> { 371 let env_and_store = match self.env_and_store.as_mut() { 372 Some(env_and_store) => env_and_store, 373 None => return Err(SecurityStateError::from("env and store not initialized?")), 374 }; 375 let mut writer = env_and_store.env.write()?; 376 // Make a note that we have prior data of the given type now. 377 env_and_store.store.put( 378 &mut writer, 379 &make_key!(PREFIX_DATA_TYPE, &[typ]), 380 &Value::Bool(true), 381 )?; 382 383 for entry in entries { 384 let key = match entry.key() { 385 Ok(key) => key, 386 Err(e) => { 387 warn!("error base64-decoding key parts - ignoring: {}", e.message); 388 continue; 389 } 390 }; 391 env_and_store 392 .store 393 .put(&mut writer, &key, &Value::I64(entry.state() as i64))?; 394 } 395 396 writer.commit()?; 397 Ok(()) 398 } 399 400 pub fn get_revocation_state( 401 &self, 402 issuer: &[u8], 403 serial: &[u8], 404 subject: &[u8], 405 pub_key: &[u8], 406 ) -> Result<i16, SecurityStateError> { 407 let mut digest = Sha256::default(); 408 digest.update(pub_key); 409 let pub_key_hash = digest.finalize(); 410 411 let subject_pubkey = make_key!(PREFIX_REV_SPK, subject, &pub_key_hash); 412 let issuer_serial = make_key!(PREFIX_REV_IS, issuer, serial); 413 414 let st: i16 = match self.read_entry(&issuer_serial) { 415 Ok(Some(value)) => value, 416 Ok(None) => nsICertStorage::STATE_UNSET, 417 Err(_) => { 418 return Err(SecurityStateError::from( 419 "problem reading revocation state (from issuer / serial)", 420 )); 421 } 422 }; 423 424 if st != nsICertStorage::STATE_UNSET { 425 return Ok(st); 426 } 427 428 match self.read_entry(&subject_pubkey) { 429 Ok(Some(value)) => Ok(value), 430 Ok(None) => Ok(nsICertStorage::STATE_UNSET), 431 Err(_) => { 432 return Err(SecurityStateError::from( 433 "problem reading revocation state (from subject / pubkey)", 434 )); 435 } 436 } 437 } 438 439 fn note_crlite_update_time(&mut self) -> Result<(), SecurityStateError> { 440 let seconds_since_epoch = Value::U64( 441 SystemTime::now() 442 .duration_since(UNIX_EPOCH) 443 .map_err(|_| SecurityStateError::from("could not get current time"))? 444 .as_secs(), 445 ); 446 let env_and_store = match self.env_and_store.as_mut() { 447 Some(env_and_store) => env_and_store, 448 None => return Err(SecurityStateError::from("env and store not initialized?")), 449 }; 450 let mut writer = env_and_store.env.write()?; 451 env_and_store 452 .store 453 .put(&mut writer, LAST_CRLITE_UPDATE_KEY, &seconds_since_epoch) 454 .map_err(|_| SecurityStateError::from("could not store timestamp"))?; 455 writer.commit()?; 456 Ok(()) 457 } 458 459 fn is_crlite_fresh(&self) -> bool { 460 let now = match SystemTime::now().duration_since(UNIX_EPOCH) { 461 Ok(t) => t.as_secs(), 462 _ => return false, 463 }; 464 let env_and_store = match self.env_and_store.as_ref() { 465 Some(env_and_store) => env_and_store, 466 None => return false, 467 }; 468 let reader = match env_and_store.env.read() { 469 Ok(reader) => reader, 470 _ => return false, 471 }; 472 match env_and_store.store.get(&reader, LAST_CRLITE_UPDATE_KEY) { 473 Ok(Some(Value::U64(last_update))) if last_update < u64::MAX / 2 => { 474 now < last_update + 60 * 60 * 24 * 10 475 } 476 _ => false, 477 } 478 } 479 480 pub fn set_full_crlite_filter(&mut self, filter: Vec<u8>) -> Result<(), SecurityStateError> { 481 let store_path = get_store_path(&self.profile_path)?; 482 483 // Drop any existing crlite filters 484 self.crlite_filters.clear(); 485 486 // Delete the backing data for the previous collection of filters. We may be migrating 487 // from a bloom filter cascade, so check for and delete "coverage", "enrollment", and 488 // "stash" files in addition to "delta" and "filter" files. 489 for entry in std::fs::read_dir(&store_path)? { 490 let Ok(entry) = entry else { 491 continue; 492 }; 493 let entry_path = entry.path(); 494 let extension = entry_path 495 .extension() 496 .map(|os_str| os_str.to_str()) 497 .flatten(); 498 if extension == Some("coverage") 499 || extension == Some("delta") 500 || extension == Some("enrollment") 501 || extension == Some("filter") 502 || extension == Some("stash") 503 { 504 let _ = std::fs::remove_file(entry_path); 505 } 506 } 507 508 // Write the new full filter. 509 std::fs::write(store_path.join("crlite.filter"), &filter)?; 510 511 self.note_crlite_update_time()?; 512 self.load_crlite_filter()?; 513 Ok(()) 514 } 515 516 fn load_crlite_filter(&mut self) -> Result<(), SecurityStateError> { 517 if !self.crlite_filters.is_empty() { 518 return Err(SecurityStateError::from( 519 "crlite_filters should be empty here", 520 )); 521 } 522 523 let store_path = get_store_path(&self.profile_path)?; 524 let filter_filename = OsString::from("crlite.filter"); 525 if let Ok(Some(filter)) = Filter::load(&store_path, &filter_filename) { 526 self.crlite_filters.push(filter); 527 } 528 Ok(()) 529 } 530 531 pub fn add_crlite_delta( 532 &mut self, 533 delta: Vec<u8>, 534 filename: String, 535 ) -> Result<(), SecurityStateError> { 536 let store_path = get_store_path(&self.profile_path)?; 537 let delta_path = store_path.join(&filename); 538 std::fs::write(&delta_path, &delta)?; 539 if let Ok(Some(filter)) = Filter::load(&store_path, &filename.into()) { 540 self.crlite_filters.push(filter); 541 } 542 self.note_crlite_update_time()?; 543 Ok(()) 544 } 545 546 pub fn get_crlite_revocation_state( 547 &self, 548 issuer_spki: &[u8], 549 serial_number: &[u8], 550 timestamps: &[CRLiteTimestamp], 551 ) -> i16 { 552 if !self.is_crlite_fresh() { 553 return nsICertStorage::STATE_NO_FILTER; 554 } 555 if self.crlite_filters.is_empty() { 556 // This can only happen if the backing file was deleted or if it or our database has 557 // become corrupted. In any case, we have no information. 558 return nsICertStorage::STATE_NO_FILTER; 559 } 560 let mut maybe_good = false; 561 let mut covered = false; 562 563 let issuer_spki_hash = Sha256::digest(issuer_spki); 564 let clubcard_crlite_key = CRLiteKey::new(issuer_spki_hash.as_ref(), serial_number); 565 for filter in &self.crlite_filters { 566 match filter.has(&clubcard_crlite_key, timestamps) { 567 nsICertStorage::STATE_ENFORCE => return nsICertStorage::STATE_ENFORCE, 568 nsICertStorage::STATE_UNSET => maybe_good = true, 569 nsICertStorage::STATE_NOT_ENROLLED => covered = true, 570 _ => (), 571 } 572 } 573 if maybe_good { 574 return nsICertStorage::STATE_UNSET; 575 } 576 if covered { 577 return nsICertStorage::STATE_NOT_ENROLLED; 578 } 579 nsICertStorage::STATE_NOT_COVERED 580 } 581 582 // To store certificates, we create a Cert out of each given cert, subject, and trust tuple. We 583 // hash each certificate with sha-256 to obtain a unique* key for that certificate, and we store 584 // the Cert in the database. We also look up or create a CertHashList for the given subject and 585 // add the new certificate's hash if it isn't present in the list. If it wasn't present, we 586 // write out the updated CertHashList. 587 // *By the pigeon-hole principle, there exist collisions for sha-256, so this key is not 588 // actually unique. We rely on the assumption that sha-256 is a cryptographically strong hash. 589 // If an adversary can find two different certificates with the same sha-256 hash, they can 590 // probably forge a sha-256-based signature, so assuming the keys we create here are unique is 591 // not a security issue. 592 pub fn add_certs( 593 &mut self, 594 certs: &[(nsCString, nsCString, i16)], 595 ) -> Result<(), SecurityStateError> { 596 let env_and_store = match self.env_and_store.as_mut() { 597 Some(env_and_store) => env_and_store, 598 None => return Err(SecurityStateError::from("env and store not initialized?")), 599 }; 600 let mut writer = env_and_store.env.write()?; 601 // Make a note that we have prior cert data now. 602 env_and_store.store.put( 603 &mut writer, 604 &make_key!(PREFIX_DATA_TYPE, &[nsICertStorage::DATA_TYPE_CERTIFICATE]), 605 &Value::Bool(true), 606 )?; 607 608 for (cert_der_base64, subject_base64, trust) in certs { 609 let cert_der = match BASE64_STANDARD.decode(&cert_der_base64) { 610 Ok(cert_der) => cert_der, 611 Err(e) => { 612 warn!("error base64-decoding cert - skipping: {}", e); 613 continue; 614 } 615 }; 616 let subject = match BASE64_STANDARD.decode(&subject_base64) { 617 Ok(subject) => subject, 618 Err(e) => { 619 warn!("error base64-decoding subject - skipping: {}", e); 620 continue; 621 } 622 }; 623 let mut digest = Sha256::default(); 624 digest.update(&cert_der); 625 let cert_hash = digest.finalize(); 626 let cert_key = make_key!(PREFIX_CERT, &cert_hash); 627 let cert = Cert::new(&cert_der, &subject, *trust)?; 628 env_and_store 629 .store 630 .put(&mut writer, &cert_key, &Value::Blob(&cert.to_bytes()?))?; 631 let subject_key = make_key!(PREFIX_SUBJECT, &subject); 632 let empty_vec = Vec::new(); 633 let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? { 634 Some(Value::Blob(hashes)) => hashes.to_owned(), 635 Some(_) => empty_vec, 636 None => empty_vec, 637 }; 638 let new_cert_hash_list = CertHashList::add(&old_cert_hash_list, &cert_hash)?; 639 if new_cert_hash_list.len() != old_cert_hash_list.len() { 640 env_and_store.store.put( 641 &mut writer, 642 &subject_key, 643 &Value::Blob(&new_cert_hash_list), 644 )?; 645 } 646 } 647 648 writer.commit()?; 649 Ok(()) 650 } 651 652 // Given a list of certificate sha-256 hashes, we can look up each Cert entry in the database. 653 // We use this to find the corresponding subject so we can look up the CertHashList it should 654 // appear in. If that list contains the given hash, we remove it and update the CertHashList. 655 // Finally we delete the Cert entry. 656 pub fn remove_certs_by_hashes( 657 &mut self, 658 hashes_base64: &[nsCString], 659 ) -> Result<(), SecurityStateError> { 660 let env_and_store = match self.env_and_store.as_mut() { 661 Some(env_and_store) => env_and_store, 662 None => return Err(SecurityStateError::from("env and store not initialized?")), 663 }; 664 let mut writer = env_and_store.env.write()?; 665 let reader = env_and_store.env.read()?; 666 667 for hash in hashes_base64 { 668 let hash = match BASE64_STANDARD.decode(&hash) { 669 Ok(hash) => hash, 670 Err(e) => { 671 warn!("error decoding hash - ignoring: {}", e); 672 continue; 673 } 674 }; 675 let cert_key = make_key!(PREFIX_CERT, &hash); 676 if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? { 677 if let Ok(cert) = Cert::from_bytes(cert_bytes) { 678 let subject_key = make_key!(PREFIX_SUBJECT, &cert.subject); 679 let empty_vec = Vec::new(); 680 // We have to use the writer here to make sure we have an up-to-date view of 681 // the cert hash list. 682 let old_cert_hash_list = match env_and_store.store.get(&writer, &subject_key)? { 683 Some(Value::Blob(hashes)) => hashes.to_owned(), 684 Some(_) => empty_vec, 685 None => empty_vec, 686 }; 687 let new_cert_hash_list = CertHashList::remove(&old_cert_hash_list, &hash)?; 688 if new_cert_hash_list.len() != old_cert_hash_list.len() { 689 env_and_store.store.put( 690 &mut writer, 691 &subject_key, 692 &Value::Blob(&new_cert_hash_list), 693 )?; 694 } 695 } 696 } 697 match env_and_store.store.delete(&mut writer, &cert_key) { 698 Ok(()) => {} 699 Err(StoreError::KeyValuePairNotFound) => {} 700 Err(e) => return Err(SecurityStateError::from(e)), 701 }; 702 } 703 writer.commit()?; 704 Ok(()) 705 } 706 707 // Given a certificate's subject, we look up the corresponding CertHashList. In theory, each 708 // hash in that list corresponds to a certificate with the given subject, so we look up each of 709 // these (assuming the database is consistent and contains them) and add them to the given list. 710 // If we encounter an inconsistency, we continue looking as best we can. 711 pub fn find_certs_by_subject( 712 &self, 713 subject: &[u8], 714 certs: &mut ThinVec<ThinVec<u8>>, 715 ) -> Result<(), SecurityStateError> { 716 let env_and_store = match self.env_and_store.as_ref() { 717 Some(env_and_store) => env_and_store, 718 None => return Err(SecurityStateError::from("env and store not initialized?")), 719 }; 720 let reader = env_and_store.env.read()?; 721 certs.clear(); 722 let subject_key = make_key!(PREFIX_SUBJECT, subject); 723 let empty_vec = Vec::new(); 724 let cert_hash_list_bytes = match env_and_store.store.get(&reader, &subject_key)? { 725 Some(Value::Blob(hashes)) => hashes, 726 Some(_) => &empty_vec, 727 None => &empty_vec, 728 }; 729 let cert_hash_list = CertHashList::new(cert_hash_list_bytes)?; 730 for cert_hash in cert_hash_list.into_iter() { 731 let cert_key = make_key!(PREFIX_CERT, cert_hash); 732 // If there's some inconsistency, we don't want to fail the whole operation - just go 733 // for best effort and find as many certificates as we can. 734 if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? { 735 if let Ok(cert) = Cert::from_bytes(cert_bytes) { 736 let mut thin_vec_cert = ThinVec::with_capacity(cert.der.len()); 737 thin_vec_cert.extend_from_slice(&cert.der); 738 certs.push(thin_vec_cert); 739 } 740 } 741 } 742 Ok(()) 743 } 744 745 pub fn find_cert_by_hash( 746 &self, 747 cert_hash: &ThinVec<u8>, 748 maybe_cert_bytes_out: Option<&mut ThinVec<u8>>, 749 ) -> Result<bool, SecurityStateError> { 750 let env_and_store = match self.env_and_store.as_ref() { 751 Some(env_and_store) => env_and_store, 752 None => return Err(SecurityStateError::from("env and store not initialized?")), 753 }; 754 let reader = env_and_store.env.read()?; 755 let cert_key = make_key!(PREFIX_CERT, &cert_hash); 756 if let Some(Value::Blob(cert_bytes_stored)) = env_and_store.store.get(&reader, &cert_key)? { 757 if let Some(maybe_cert_bytes_out) = maybe_cert_bytes_out { 758 maybe_cert_bytes_out.clear(); 759 let cert = Cert::from_bytes(cert_bytes_stored)?; 760 maybe_cert_bytes_out.extend_from_slice(cert.der); 761 } 762 return Ok(true); 763 } 764 Ok(false) 765 } 766 767 pub fn has_all_certs_by_hash( 768 &self, 769 cert_hashes: &ThinVec<ThinVec<u8>>, 770 ) -> Result<bool, SecurityStateError> { 771 // Bug 1950140 - Implement a cache for this function to improve performance, 772 // based on a caller-supplied key identifiying the list of hashes. 773 for cert_hash in cert_hashes { 774 match self.find_cert_by_hash(&cert_hash, None) { 775 Ok(true) => {} 776 Ok(false) => return Ok(false), 777 Err(err) => return Err(err), 778 } 779 } 780 Ok(true) 781 } 782 } 783 784 impl MallocSizeOf for SecurityState { 785 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { 786 self.profile_path.size_of(ops) 787 + self.env_and_store.size_of(ops) 788 + self 789 .crlite_filters 790 .iter() 791 .map(|filter| filter.size_of(ops)) 792 .sum::<usize>() 793 + self.remaining_ops.size_of(ops) 794 } 795 } 796 797 const CERT_SERIALIZATION_VERSION_1: u8 = 1; 798 799 // A Cert consists of its DER encoding, its DER-encoded subject, and its trust (currently 800 // nsICertStorage::TRUST_INHERIT, but in the future nsICertStorage::TRUST_ANCHOR may also be used). 801 // The length of each encoding must be representable by a u16 (so 65535 bytes is the longest a 802 // certificate can be). 803 struct Cert<'a> { 804 der: &'a [u8], 805 subject: &'a [u8], 806 trust: i16, 807 } 808 809 impl<'a> Cert<'a> { 810 fn new(der: &'a [u8], subject: &'a [u8], trust: i16) -> Result<Cert<'a>, SecurityStateError> { 811 if der.len() > u16::MAX.into() { 812 return Err(SecurityStateError::from("certificate is too long")); 813 } 814 if subject.len() > u16::MAX.into() { 815 return Err(SecurityStateError::from("subject is too long")); 816 } 817 Ok(Cert { 818 der, 819 subject, 820 trust, 821 }) 822 } 823 824 fn from_bytes(encoded: &'a [u8]) -> Result<Cert<'a>, SecurityStateError> { 825 if encoded.len() < size_of::<u8>() { 826 return Err(SecurityStateError::from("invalid Cert: no version?")); 827 } 828 let (mut version, rest) = encoded.split_at(size_of::<u8>()); 829 let version = version.read_u8()?; 830 if version != CERT_SERIALIZATION_VERSION_1 { 831 return Err(SecurityStateError::from("invalid Cert: unexpected version")); 832 } 833 834 if rest.len() < size_of::<u16>() { 835 return Err(SecurityStateError::from("invalid Cert: no der len?")); 836 } 837 let (mut der_len, rest) = rest.split_at(size_of::<u16>()); 838 let der_len = der_len.read_u16::<NetworkEndian>()?.into(); 839 if rest.len() < der_len { 840 return Err(SecurityStateError::from("invalid Cert: no der?")); 841 } 842 let (der, rest) = rest.split_at(der_len); 843 844 if rest.len() < size_of::<u16>() { 845 return Err(SecurityStateError::from("invalid Cert: no subject len?")); 846 } 847 let (mut subject_len, rest) = rest.split_at(size_of::<u16>()); 848 let subject_len = subject_len.read_u16::<NetworkEndian>()?.into(); 849 if rest.len() < subject_len { 850 return Err(SecurityStateError::from("invalid Cert: no subject?")); 851 } 852 let (subject, mut rest) = rest.split_at(subject_len); 853 854 if rest.len() < size_of::<i16>() { 855 return Err(SecurityStateError::from("invalid Cert: no trust?")); 856 } 857 let trust = rest.read_i16::<NetworkEndian>()?; 858 if rest.len() > 0 { 859 return Err(SecurityStateError::from("invalid Cert: trailing data?")); 860 } 861 862 Ok(Cert { 863 der, 864 subject, 865 trust, 866 }) 867 } 868 869 fn to_bytes(&self) -> Result<Vec<u8>, SecurityStateError> { 870 let mut bytes = Vec::with_capacity( 871 size_of::<u8>() 872 + size_of::<u16>() 873 + self.der.len() 874 + size_of::<u16>() 875 + self.subject.len() 876 + size_of::<i16>(), 877 ); 878 bytes.write_u8(CERT_SERIALIZATION_VERSION_1)?; 879 bytes.write_u16::<NetworkEndian>( 880 self.der 881 .len() 882 .try_into() 883 .map_err(|_| SecurityStateError::from("certificate is too long"))?, 884 )?; 885 bytes.extend_from_slice(&self.der); 886 bytes.write_u16::<NetworkEndian>( 887 self.subject 888 .len() 889 .try_into() 890 .map_err(|_| SecurityStateError::from("subject is too long"))?, 891 )?; 892 bytes.extend_from_slice(&self.subject); 893 bytes.write_i16::<NetworkEndian>(self.trust)?; 894 Ok(bytes) 895 } 896 } 897 898 // A CertHashList is a list of sha-256 hashes of DER-encoded certificates. 899 struct CertHashList<'a> { 900 hashes: Vec<&'a [u8]>, 901 } 902 903 impl<'a> CertHashList<'a> { 904 fn new(hashes_bytes: &'a [u8]) -> Result<CertHashList<'a>, SecurityStateError> { 905 if hashes_bytes.len() % Sha256::output_size() != 0 { 906 return Err(SecurityStateError::from( 907 "unexpected length for cert hash list", 908 )); 909 } 910 let mut hashes = Vec::with_capacity(hashes_bytes.len() / Sha256::output_size()); 911 for hash in hashes_bytes.chunks_exact(Sha256::output_size()) { 912 hashes.push(hash); 913 } 914 Ok(CertHashList { hashes }) 915 } 916 917 fn add(hashes_bytes: &[u8], new_hash: &[u8]) -> Result<Vec<u8>, SecurityStateError> { 918 if hashes_bytes.len() % Sha256::output_size() != 0 { 919 return Err(SecurityStateError::from( 920 "unexpected length for cert hash list", 921 )); 922 } 923 if new_hash.len() != Sha256::output_size() { 924 return Err(SecurityStateError::from("unexpected cert hash length")); 925 } 926 for hash in hashes_bytes.chunks_exact(Sha256::output_size()) { 927 if hash == new_hash { 928 return Ok(hashes_bytes.to_owned()); 929 } 930 } 931 let mut combined = hashes_bytes.to_owned(); 932 combined.extend_from_slice(new_hash); 933 Ok(combined) 934 } 935 936 fn remove(hashes_bytes: &[u8], cert_hash: &[u8]) -> Result<Vec<u8>, SecurityStateError> { 937 if hashes_bytes.len() % Sha256::output_size() != 0 { 938 return Err(SecurityStateError::from( 939 "unexpected length for cert hash list", 940 )); 941 } 942 if cert_hash.len() != Sha256::output_size() { 943 return Err(SecurityStateError::from("unexpected cert hash length")); 944 } 945 let mut result = Vec::with_capacity(hashes_bytes.len()); 946 for hash in hashes_bytes.chunks_exact(Sha256::output_size()) { 947 if hash != cert_hash { 948 result.extend_from_slice(hash); 949 } 950 } 951 Ok(result) 952 } 953 } 954 955 impl<'a> IntoIterator for CertHashList<'a> { 956 type Item = &'a [u8]; 957 type IntoIter = std::vec::IntoIter<&'a [u8]>; 958 959 fn into_iter(self) -> Self::IntoIter { 960 self.hashes.into_iter() 961 } 962 } 963 964 // Helper struct for get_crlite_revocation_state. 965 struct CRLiteTimestamp { 966 log_id: ThinVec<u8>, 967 timestamp: u64, 968 } 969 970 // Helper struct for set_batch_state. Takes a prefix, two base64-encoded key 971 // parts, and a security state value. 972 struct EncodedSecurityState { 973 prefix: &'static str, 974 key_part_1_base64: nsCString, 975 key_part_2_base64: nsCString, 976 state: i16, 977 } 978 979 impl EncodedSecurityState { 980 fn new( 981 prefix: &'static str, 982 key_part_1_base64: nsCString, 983 key_part_2_base64: nsCString, 984 state: i16, 985 ) -> EncodedSecurityState { 986 EncodedSecurityState { 987 prefix, 988 key_part_1_base64, 989 key_part_2_base64, 990 state, 991 } 992 } 993 994 fn key(&self) -> Result<Vec<u8>, SecurityStateError> { 995 let key_part_1 = BASE64_STANDARD.decode(&self.key_part_1_base64)?; 996 let key_part_2 = BASE64_STANDARD.decode(&self.key_part_2_base64)?; 997 Ok(make_key!(self.prefix, &key_part_1, &key_part_2)) 998 } 999 1000 fn state(&self) -> i16 { 1001 self.state 1002 } 1003 } 1004 1005 fn get_path_from_directory_service(key: &str) -> Result<PathBuf, nserror::nsresult> { 1006 let directory_service: RefPtr<nsIProperties> = 1007 xpcom::components::Directory::service().map_err(|_| NS_ERROR_FAILURE)?; 1008 let cs_key = CString::new(key).map_err(|_| NS_ERROR_FAILURE)?; 1009 1010 let mut requested_dir = GetterAddrefs::<nsIFile>::new(); 1011 unsafe { 1012 (*directory_service) 1013 .Get( 1014 (&cs_key).as_ptr(), 1015 &nsIFile::IID as *const nsIID, 1016 requested_dir.void_ptr(), 1017 ) 1018 .to_result() 1019 }?; 1020 1021 let dir_path = requested_dir.refptr().ok_or(NS_ERROR_FAILURE)?; 1022 let mut path = nsString::new(); 1023 unsafe { (*dir_path).GetPath(&mut *path).to_result() }?; 1024 Ok(PathBuf::from(format!("{}", path))) 1025 } 1026 1027 fn get_profile_path() -> Result<PathBuf, nserror::nsresult> { 1028 get_path_from_directory_service("ProfD").or_else(|_| get_path_from_directory_service("TmpD")) 1029 } 1030 1031 fn get_store_path(profile_path: &PathBuf) -> Result<PathBuf, SecurityStateError> { 1032 let mut store_path = profile_path.clone(); 1033 store_path.push("security_state"); 1034 create_dir_all(store_path.as_path())?; 1035 Ok(store_path) 1036 } 1037 1038 fn make_env(path: &Path) -> Result<Rkv, SecurityStateError> { 1039 let mut builder = Rkv::environment_builder::<SafeMode>(); 1040 builder.set_max_dbs(2); 1041 1042 // 16MB is a little over twice the size of the current dataset. When we 1043 // eventually switch to the LMDB backend to create the builder above, 1044 // we should set this as the map size, since it cannot currently resize. 1045 // (The SafeMode backend warns when a map size is specified, so we skip it 1046 // for now to avoid console spam.) 1047 1048 // builder.set_map_size(16777216); 1049 1050 // Bug 1595004: Migrate databases between backends in the future, 1051 // and handle 32 and 64 bit architectures in case of LMDB. 1052 Rkv::from_builder(path, builder).map_err(SecurityStateError::from) 1053 } 1054 1055 fn unconditionally_remove_file(path: &Path) -> Result<(), SecurityStateError> { 1056 match remove_file(path) { 1057 Ok(()) => Ok(()), 1058 Err(e) => match e.kind() { 1059 std::io::ErrorKind::NotFound => Ok(()), 1060 _ => Err(SecurityStateError::from(e)), 1061 }, 1062 } 1063 } 1064 1065 fn remove_db(path: &Path) -> Result<(), SecurityStateError> { 1066 // Remove LMDB-related files. 1067 let db = path.join("data.mdb"); 1068 unconditionally_remove_file(&db)?; 1069 let lock = path.join("lock.mdb"); 1070 unconditionally_remove_file(&lock)?; 1071 1072 // Remove SafeMode-related files. 1073 let db = path.join("data.safe.bin"); 1074 unconditionally_remove_file(&db)?; 1075 1076 Ok(()) 1077 } 1078 1079 // This is a helper struct that implements the task that asynchronously reads CRLite deltas on 1080 // a background thread. 1081 struct BackgroundReadDeltasTask { 1082 profile_path: PathBuf, 1083 security_state: Arc<RwLock<SecurityState>>, 1084 } 1085 1086 impl BackgroundReadDeltasTask { 1087 fn new( 1088 profile_path: PathBuf, 1089 security_state: &Arc<RwLock<SecurityState>>, 1090 ) -> BackgroundReadDeltasTask { 1091 BackgroundReadDeltasTask { 1092 profile_path, 1093 security_state: Arc::clone(security_state), 1094 } 1095 } 1096 } 1097 1098 impl Task for BackgroundReadDeltasTask { 1099 fn run(&self) { 1100 let Ok(store_path) = get_store_path(&self.profile_path) else { 1101 error!("error getting security_state path"); 1102 return; 1103 }; 1104 1105 let mut delta_filters = vec![]; 1106 if let Ok(mut entries) = std::fs::read_dir(&store_path) { 1107 while let Some(Ok(entry)) = entries.next() { 1108 let entry_path = entry.path(); 1109 let extension = entry_path 1110 .extension() 1111 .map(|os_str| os_str.to_str()) 1112 .flatten(); 1113 if extension != Some("delta") { 1114 continue; 1115 } 1116 if let Ok(Some(filter)) = Filter::load(&store_path, &entry.file_name()) { 1117 delta_filters.push(filter); 1118 } 1119 } 1120 } 1121 1122 let Ok(mut ss) = self.security_state.write() else { 1123 return; 1124 }; 1125 if let Err(e) = ss.open_db() { 1126 error!("error opening security state db: {}", e.message); 1127 } 1128 ss.crlite_filters.append(&mut delta_filters); 1129 } 1130 1131 fn done(&self) -> Result<(), nsresult> { 1132 Ok(()) 1133 } 1134 } 1135 1136 fn do_construct_cert_storage( 1137 iid: *const xpcom::nsIID, 1138 result: *mut *mut xpcom::reexports::libc::c_void, 1139 ) -> Result<(), nserror::nsresult> { 1140 let path_buf = get_profile_path()?; 1141 let security_state = Arc::new(RwLock::new(SecurityState::new(path_buf.clone()))); 1142 let cert_storage = CertStorage::allocate(InitCertStorage { 1143 security_state: security_state.clone(), 1144 queue: create_background_task_queue(cstr!("cert_storage"))?, 1145 }); 1146 let memory_reporter = MemoryReporter::allocate(InitMemoryReporter { security_state }); 1147 1148 // Dispatch a task to the background task queue to asynchronously read CRLite delta files (if 1149 // present) and load them into cert_storage. This task does not hold the 1150 // cert_storage.security_state mutex for the majority of its operation, which allows certificate 1151 // verification threads to query cert_storage without blocking. This is important for 1152 // performance, but it means that certificate verifications that happen before the task has 1153 // completed will not have delta information, and thus may not know of revocations that have 1154 // occurred since the last full CRLite filter was downloaded. 1155 // NB: because the background task queue is serial, this task will complete before other tasks 1156 // later dispatched to the queue run. This means that other tasks that depend on deltas will do 1157 // so with the correct set of preconditions. 1158 let load_crlite_deltas_task = Box::new(BackgroundReadDeltasTask::new( 1159 path_buf, 1160 &cert_storage.security_state, 1161 )); 1162 let runnable = TaskRunnable::new("LoadCrliteDeltas", load_crlite_deltas_task)?; 1163 TaskRunnable::dispatch(runnable, cert_storage.queue.coerce())?; 1164 1165 if let Some(reporter) = memory_reporter.query_interface::<nsIMemoryReporter>() { 1166 if let Some(reporter_manager) = xpcom::get_service::<nsIMemoryReporterManager>(cstr!( 1167 "@mozilla.org/memory-reporter-manager;1" 1168 )) { 1169 unsafe { reporter_manager.RegisterStrongReporter(&*reporter) }; 1170 } 1171 } 1172 1173 unsafe { cert_storage.QueryInterface(iid, result).to_result() } 1174 } 1175 1176 // This is a helper for creating a task that will perform a specific action on a background thread. 1177 struct SecurityStateTask< 1178 T: Default + VariantType, 1179 F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>, 1180 > { 1181 callback: AtomicCell<Option<ThreadBoundRefPtr<nsICertStorageCallback>>>, 1182 security_state: Arc<RwLock<SecurityState>>, 1183 result: AtomicCell<(nserror::nsresult, T)>, 1184 task_action: AtomicCell<Option<F>>, 1185 } 1186 1187 impl<T: Default + VariantType, F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>> 1188 SecurityStateTask<T, F> 1189 { 1190 fn new( 1191 callback: &nsICertStorageCallback, 1192 security_state: &Arc<RwLock<SecurityState>>, 1193 task_action: F, 1194 ) -> Result<SecurityStateTask<T, F>, nsresult> { 1195 let mut ss = security_state.write().or(Err(NS_ERROR_FAILURE))?; 1196 ss.remaining_ops = ss.remaining_ops.wrapping_add(1); 1197 1198 Ok(SecurityStateTask { 1199 callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(RefPtr::new(callback)))), 1200 security_state: Arc::clone(security_state), 1201 result: AtomicCell::new((NS_ERROR_FAILURE, T::default())), 1202 task_action: AtomicCell::new(Some(task_action)), 1203 }) 1204 } 1205 } 1206 1207 impl<T: Default + VariantType, F: FnOnce(&mut SecurityState) -> Result<T, SecurityStateError>> Task 1208 for SecurityStateTask<T, F> 1209 { 1210 fn run(&self) { 1211 let mut ss = match self.security_state.write() { 1212 Ok(ss) => ss, 1213 Err(_) => return, 1214 }; 1215 // this is a no-op if the DB is already open 1216 if ss.open_db().is_err() { 1217 return; 1218 } 1219 if let Some(task_action) = self.task_action.swap(None) { 1220 let rv = task_action(&mut ss) 1221 .and_then(|v| Ok((NS_OK, v))) 1222 .unwrap_or((NS_ERROR_FAILURE, T::default())); 1223 self.result.store(rv); 1224 } 1225 } 1226 1227 fn done(&self) -> Result<(), nsresult> { 1228 let threadbound = self.callback.swap(None).ok_or(NS_ERROR_FAILURE)?; 1229 let callback = threadbound.get_ref().ok_or(NS_ERROR_FAILURE)?; 1230 let result = self.result.swap((NS_ERROR_FAILURE, T::default())); 1231 let variant = result.1.into_variant(); 1232 let nsrv = unsafe { callback.Done(result.0, &*variant) }; 1233 1234 let mut ss = self.security_state.write().or(Err(NS_ERROR_FAILURE))?; 1235 ss.remaining_ops = ss.remaining_ops.wrapping_sub(1); 1236 1237 match nsrv { 1238 NS_OK => Ok(()), 1239 e => Err(e), 1240 } 1241 } 1242 } 1243 1244 #[no_mangle] 1245 pub extern "C" fn cert_storage_constructor( 1246 iid: *const xpcom::nsIID, 1247 result: *mut *mut xpcom::reexports::libc::c_void, 1248 ) -> nserror::nsresult { 1249 if !is_main_thread() { 1250 return NS_ERROR_NOT_SAME_THREAD; 1251 } 1252 match do_construct_cert_storage(iid, result) { 1253 Ok(()) => NS_OK, 1254 Err(e) => e, 1255 } 1256 } 1257 1258 macro_rules! try_ns { 1259 ($e:expr) => { 1260 match $e { 1261 Ok(value) => value, 1262 Err(_) => return NS_ERROR_FAILURE, 1263 } 1264 }; 1265 ($e:expr, or continue) => { 1266 match $e { 1267 Ok(value) => value, 1268 Err(err) => { 1269 error!("{}", err); 1270 continue; 1271 } 1272 } 1273 }; 1274 } 1275 1276 // This macro is a way to ensure the DB has been opened while minimizing lock acquisitions in the 1277 // common (read-only) case. First we acquire a read lock and see if we even need to open the DB. If 1278 // not, we can continue with the read lock we already have. Otherwise, we drop the read lock, 1279 // acquire the write lock, open the DB, drop the write lock, and re-acquire the read lock. While it 1280 // is possible for two or more threads to all come to the conclusion that they need to open the DB, 1281 // this isn't ultimately an issue - `open_db` will exit early if another thread has already done the 1282 // work. 1283 macro_rules! get_security_state { 1284 ($self:expr) => {{ 1285 let ss_read_only = try_ns!($self.security_state.read()); 1286 if !ss_read_only.db_needs_opening() { 1287 ss_read_only 1288 } else { 1289 drop(ss_read_only); 1290 { 1291 let mut ss_write = try_ns!($self.security_state.write()); 1292 try_ns!(ss_write.open_db()); 1293 } 1294 try_ns!($self.security_state.read()) 1295 } 1296 }}; 1297 } 1298 1299 #[xpcom(implement(nsICertStorage), atomic)] 1300 struct CertStorage { 1301 security_state: Arc<RwLock<SecurityState>>, 1302 queue: RefPtr<nsISerialEventTarget>, 1303 } 1304 1305 /// CertStorage implements the nsICertStorage interface. The actual work is done by the 1306 /// SecurityState. To handle any threading issues, we have an atomic-refcounted read/write lock on 1307 /// the one and only SecurityState. So, only one thread can use SecurityState's &mut self functions 1308 /// at a time, while multiple threads can use &self functions simultaneously (as long as there are 1309 /// no threads using an &mut self function). The Arc is to allow for the creation of background 1310 /// tasks that use the SecurityState on the queue owned by CertStorage. This allows us to not block 1311 /// the main thread. 1312 #[allow(non_snake_case)] 1313 impl CertStorage { 1314 unsafe fn HasPriorData( 1315 &self, 1316 data_type: u8, 1317 callback: *const nsICertStorageCallback, 1318 ) -> nserror::nsresult { 1319 if !is_main_thread() { 1320 return NS_ERROR_NOT_SAME_THREAD; 1321 } 1322 if callback.is_null() { 1323 return NS_ERROR_NULL_POINTER; 1324 } 1325 let task = Box::new(try_ns!(SecurityStateTask::new( 1326 &*callback, 1327 &self.security_state, 1328 move |ss| ss.get_has_prior_data(data_type), 1329 ))); 1330 let runnable = try_ns!(TaskRunnable::new("HasPriorData", task)); 1331 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1332 NS_OK 1333 } 1334 1335 unsafe fn GetRemainingOperationCount(&self, state: *mut i32) -> nserror::nsresult { 1336 if !is_main_thread() { 1337 return NS_ERROR_NOT_SAME_THREAD; 1338 } 1339 if state.is_null() { 1340 return NS_ERROR_NULL_POINTER; 1341 } 1342 let ss = try_ns!(self.security_state.read()); 1343 *state = ss.remaining_ops; 1344 NS_OK 1345 } 1346 1347 unsafe fn SetRevocations( 1348 &self, 1349 revocations: *const ThinVec<Option<RefPtr<nsIRevocationState>>>, 1350 callback: *const nsICertStorageCallback, 1351 ) -> nserror::nsresult { 1352 if !is_main_thread() { 1353 return NS_ERROR_NOT_SAME_THREAD; 1354 } 1355 if revocations.is_null() || callback.is_null() { 1356 return NS_ERROR_NULL_POINTER; 1357 } 1358 1359 let revocations = &*revocations; 1360 let mut entries = Vec::with_capacity(revocations.len()); 1361 1362 // By continuing when an nsIRevocationState attribute value is invalid, 1363 // we prevent errors relating to individual blocklist entries from 1364 // causing sync to fail. We will accumulate telemetry on these failures 1365 // in bug 1254099. 1366 1367 for revocation in revocations.iter().flatten() { 1368 let mut state: i16 = 0; 1369 try_ns!(revocation.GetState(&mut state).to_result(), or continue); 1370 1371 if let Some(revocation) = 1372 (*revocation).query_interface::<nsIIssuerAndSerialRevocationState>() 1373 { 1374 let mut issuer = nsCString::new(); 1375 try_ns!(revocation.GetIssuer(&mut *issuer).to_result(), or continue); 1376 1377 let mut serial = nsCString::new(); 1378 try_ns!(revocation.GetSerial(&mut *serial).to_result(), or continue); 1379 1380 entries.push(EncodedSecurityState::new( 1381 PREFIX_REV_IS, 1382 issuer, 1383 serial, 1384 state, 1385 )); 1386 } else if let Some(revocation) = 1387 (*revocation).query_interface::<nsISubjectAndPubKeyRevocationState>() 1388 { 1389 let mut subject = nsCString::new(); 1390 try_ns!(revocation.GetSubject(&mut *subject).to_result(), or continue); 1391 1392 let mut pub_key_hash = nsCString::new(); 1393 try_ns!(revocation.GetPubKey(&mut *pub_key_hash).to_result(), or continue); 1394 1395 entries.push(EncodedSecurityState::new( 1396 PREFIX_REV_SPK, 1397 subject, 1398 pub_key_hash, 1399 state, 1400 )); 1401 } 1402 } 1403 1404 let task = Box::new(try_ns!(SecurityStateTask::new( 1405 &*callback, 1406 &self.security_state, 1407 move |ss| ss.set_batch_state(&entries, nsICertStorage::DATA_TYPE_REVOCATION), 1408 ))); 1409 let runnable = try_ns!(TaskRunnable::new("SetRevocations", task)); 1410 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1411 NS_OK 1412 } 1413 1414 unsafe fn GetRevocationState( 1415 &self, 1416 issuer: *const ThinVec<u8>, 1417 serial: *const ThinVec<u8>, 1418 subject: *const ThinVec<u8>, 1419 pub_key: *const ThinVec<u8>, 1420 state: *mut i16, 1421 ) -> nserror::nsresult { 1422 // TODO (bug 1541212): We really want to restrict this to non-main-threads only in non-test 1423 // contexts, but we can't do so until bug 1406854 is fixed. 1424 if issuer.is_null() || serial.is_null() || subject.is_null() || pub_key.is_null() { 1425 return NS_ERROR_NULL_POINTER; 1426 } 1427 *state = nsICertStorage::STATE_UNSET; 1428 let ss = get_security_state!(self); 1429 match ss.get_revocation_state(&*issuer, &*serial, &*subject, &*pub_key) { 1430 Ok(st) => { 1431 *state = st; 1432 NS_OK 1433 } 1434 _ => NS_ERROR_FAILURE, 1435 } 1436 } 1437 1438 unsafe fn SetFullCRLiteFilter( 1439 &self, 1440 filter: *const ThinVec<u8>, 1441 callback: *const nsICertStorageCallback, 1442 ) -> nserror::nsresult { 1443 if !is_main_thread() { 1444 return NS_ERROR_NOT_SAME_THREAD; 1445 } 1446 if filter.is_null() || callback.is_null() { 1447 return NS_ERROR_NULL_POINTER; 1448 } 1449 1450 let filter_owned = (*filter).to_vec(); 1451 1452 let task = Box::new(try_ns!(SecurityStateTask::new( 1453 &*callback, 1454 &self.security_state, 1455 move |ss| ss.set_full_crlite_filter(filter_owned), 1456 ))); 1457 let runnable = try_ns!(TaskRunnable::new("SetFullCRLiteFilter", task)); 1458 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1459 NS_OK 1460 } 1461 1462 unsafe fn AddCRLiteDelta( 1463 &self, 1464 delta: *const ThinVec<u8>, 1465 filename: *const nsACString, 1466 callback: *const nsICertStorageCallback, 1467 ) -> nserror::nsresult { 1468 if !is_main_thread() { 1469 return NS_ERROR_NOT_SAME_THREAD; 1470 } 1471 if delta.is_null() || filename.is_null() || callback.is_null() { 1472 return NS_ERROR_NULL_POINTER; 1473 } 1474 let delta_owned = (*delta).to_vec(); 1475 let filename_owned = (*filename).to_string(); 1476 let task = Box::new(try_ns!(SecurityStateTask::new( 1477 &*callback, 1478 &self.security_state, 1479 move |ss| ss.add_crlite_delta(delta_owned, filename_owned), 1480 ))); 1481 let runnable = try_ns!(TaskRunnable::new("AddCRLiteDelta", task)); 1482 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1483 NS_OK 1484 } 1485 1486 unsafe fn TestNoteCRLiteUpdateTime( 1487 &self, 1488 callback: *const nsICertStorageCallback, 1489 ) -> nserror::nsresult { 1490 if !is_main_thread() { 1491 return NS_ERROR_NOT_SAME_THREAD; 1492 } 1493 let task = Box::new(try_ns!(SecurityStateTask::new( 1494 &*callback, 1495 &self.security_state, 1496 move |ss| ss.note_crlite_update_time(), 1497 ))); 1498 let runnable = try_ns!(TaskRunnable::new("TestNoteCRLiteUpdateTime", task)); 1499 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1500 NS_OK 1501 } 1502 1503 unsafe fn GetCRLiteRevocationState( 1504 &self, 1505 issuerSPKI: *const ThinVec<u8>, 1506 serialNumber: *const ThinVec<u8>, 1507 timestamps: *const ThinVec<Option<RefPtr<nsICRLiteTimestamp>>>, 1508 state: *mut i16, 1509 ) -> nserror::nsresult { 1510 // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we 1511 // can't do so until bug 1406854 is fixed. 1512 if issuerSPKI.is_null() || serialNumber.is_null() || state.is_null() || timestamps.is_null() 1513 { 1514 return NS_ERROR_NULL_POINTER; 1515 } 1516 let timestamps = &*timestamps; 1517 let mut timestamp_entries = Vec::with_capacity(timestamps.len()); 1518 for timestamp_entry in timestamps.iter().flatten() { 1519 let mut log_id = ThinVec::with_capacity(32); 1520 try_ns!(timestamp_entry.GetLogID(&mut log_id).to_result(), or continue); 1521 let mut timestamp: u64 = 0; 1522 try_ns!(timestamp_entry.GetTimestamp(&mut timestamp).to_result(), or continue); 1523 timestamp_entries.push(CRLiteTimestamp { log_id, timestamp }); 1524 } 1525 let ss = get_security_state!(self); 1526 *state = ss.get_crlite_revocation_state(&*issuerSPKI, &*serialNumber, ×tamp_entries); 1527 NS_OK 1528 } 1529 1530 unsafe fn AddCerts( 1531 &self, 1532 certs: *const ThinVec<Option<RefPtr<nsICertInfo>>>, 1533 callback: *const nsICertStorageCallback, 1534 ) -> nserror::nsresult { 1535 if !is_main_thread() { 1536 return NS_ERROR_NOT_SAME_THREAD; 1537 } 1538 if certs.is_null() || callback.is_null() { 1539 return NS_ERROR_NULL_POINTER; 1540 } 1541 let certs = &*certs; 1542 let mut cert_entries = Vec::with_capacity(certs.len()); 1543 for cert in certs.iter().flatten() { 1544 let mut der = nsCString::new(); 1545 try_ns!((*cert).GetCert(&mut *der).to_result(), or continue); 1546 let mut subject = nsCString::new(); 1547 try_ns!((*cert).GetSubject(&mut *subject).to_result(), or continue); 1548 let mut trust: i16 = 0; 1549 try_ns!((*cert).GetTrust(&mut trust).to_result(), or continue); 1550 cert_entries.push((der, subject, trust)); 1551 } 1552 let task = Box::new(try_ns!(SecurityStateTask::new( 1553 &*callback, 1554 &self.security_state, 1555 move |ss| ss.add_certs(&cert_entries), 1556 ))); 1557 let runnable = try_ns!(TaskRunnable::new("AddCerts", task)); 1558 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1559 NS_OK 1560 } 1561 1562 // Synchronous helper for testing purposes only 1563 unsafe fn TestHelperAddCert( 1564 &self, 1565 cert: *const nsACString, 1566 subject: *const nsACString, 1567 trust: i16, 1568 ) -> nserror::nsresult { 1569 let cert = nsCString::from(&*cert); 1570 let subject = nsCString::from(&*subject); 1571 let mut ss = self.security_state.write().unwrap(); 1572 ss.open_db().unwrap(); 1573 ss.add_certs(&[(cert, subject, trust)]).unwrap(); 1574 NS_OK 1575 } 1576 1577 unsafe fn RemoveCertsByHashes( 1578 &self, 1579 hashes: *const ThinVec<nsCString>, 1580 callback: *const nsICertStorageCallback, 1581 ) -> nserror::nsresult { 1582 if !is_main_thread() { 1583 return NS_ERROR_NOT_SAME_THREAD; 1584 } 1585 if hashes.is_null() || callback.is_null() { 1586 return NS_ERROR_NULL_POINTER; 1587 } 1588 let hashes = (*hashes).to_vec(); 1589 let task = Box::new(try_ns!(SecurityStateTask::new( 1590 &*callback, 1591 &self.security_state, 1592 move |ss| ss.remove_certs_by_hashes(&hashes), 1593 ))); 1594 let runnable = try_ns!(TaskRunnable::new("RemoveCertsByHashes", task)); 1595 try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); 1596 NS_OK 1597 } 1598 1599 unsafe fn FindCertsBySubject( 1600 &self, 1601 subject: *const ThinVec<u8>, 1602 certs: *mut ThinVec<ThinVec<u8>>, 1603 ) -> nserror::nsresult { 1604 // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we 1605 // can't do so until bug 1406854 is fixed. 1606 if subject.is_null() || certs.is_null() { 1607 return NS_ERROR_NULL_POINTER; 1608 } 1609 let ss = get_security_state!(self); 1610 match ss.find_certs_by_subject(&*subject, &mut *certs) { 1611 Ok(()) => NS_OK, 1612 Err(_) => NS_ERROR_FAILURE, 1613 } 1614 } 1615 1616 unsafe fn HasAllCertsByHash( 1617 &self, 1618 cert_hashes: *const ThinVec<ThinVec<u8>>, 1619 found: *mut bool, 1620 ) -> nserror::nsresult { 1621 if cert_hashes.is_null() { 1622 return NS_ERROR_NULL_POINTER; 1623 } 1624 let ss = get_security_state!(self); 1625 match ss.has_all_certs_by_hash(&*cert_hashes) { 1626 Ok(result) => { 1627 *found = result; 1628 NS_OK 1629 } 1630 Err(err) => { 1631 log::error!("HasAllCertsByHash: {:?}", err); 1632 NS_ERROR_FAILURE 1633 } 1634 } 1635 } 1636 1637 unsafe fn FindCertByHash( 1638 &self, 1639 cert_hash: *const ThinVec<u8>, 1640 cert_bytes: *mut ThinVec<u8>, 1641 ) -> nserror::nsresult { 1642 if cert_hash.is_null() || cert_bytes.is_null() { 1643 return NS_ERROR_NULL_POINTER; 1644 } 1645 let ss = get_security_state!(self); 1646 match ss.find_cert_by_hash(&*cert_hash, Some(&mut *cert_bytes)) { 1647 Ok(true) => NS_OK, 1648 Ok(false) => NS_ERROR_FAILURE, 1649 Err(err) => { 1650 log::error!("FindCertByHash: {:?}", err); 1651 NS_ERROR_FAILURE 1652 } 1653 } 1654 } 1655 } 1656 1657 extern "C" { 1658 fn cert_storage_malloc_size_of(ptr: *const xpcom::reexports::libc::c_void) -> usize; 1659 } 1660 1661 #[xpcom(implement(nsIMemoryReporter), atomic)] 1662 struct MemoryReporter { 1663 security_state: Arc<RwLock<SecurityState>>, 1664 } 1665 1666 #[allow(non_snake_case)] 1667 impl MemoryReporter { 1668 unsafe fn CollectReports( 1669 &self, 1670 callback: *const nsIHandleReportCallback, 1671 data: *const nsISupports, 1672 _anonymize: bool, 1673 ) -> nserror::nsresult { 1674 let ss = try_ns!(self.security_state.read()); 1675 let mut ops = MallocSizeOfOps::new(cert_storage_malloc_size_of, None); 1676 let size = ss.size_of(&mut ops); 1677 let callback = match RefPtr::from_raw(callback) { 1678 Some(ptr) => ptr, 1679 None => return NS_ERROR_UNEXPECTED, 1680 }; 1681 // This does the same as MOZ_COLLECT_REPORT 1682 callback.Callback( 1683 &nsCStr::new() as &nsACString, 1684 &nsCStr::from("explicit/cert-storage/storage") as &nsACString, 1685 nsIMemoryReporter::KIND_HEAP, 1686 nsIMemoryReporter::UNITS_BYTES, 1687 size as i64, 1688 &nsCStr::from("Memory used by certificate storage") as &nsACString, 1689 data, 1690 ); 1691 NS_OK 1692 } 1693 }