Bug 1750787 - get CRLite enrollment list from cert-revocations. r=keeler

Differential Revision: https://phabricator.services.mozilla.com/D139728
This commit is contained in:
John Schanck 2022-03-02 18:19:25 +00:00
Родитель 3c5d0d725e
Коммит 47c887153f
27 изменённых файлов: 267 добавлений и 507 удалений

Просмотреть файл

@ -80,17 +80,6 @@ function bytesToString(bytes) {
return String.fromCharCode.apply(null, bytes); return String.fromCharCode.apply(null, bytes);
} }
class CRLiteState {
constructor(subject, spkiHash, state) {
this.subject = subject;
this.spkiHash = spkiHash;
this.state = state;
}
}
CRLiteState.prototype.QueryInterface = ChromeUtils.generateQI([
"nsICRLiteState",
]);
class CRLiteCoverage { class CRLiteCoverage {
constructor(b64LogID, minTimestamp, maxTimestamp) { constructor(b64LogID, minTimestamp, maxTimestamp) {
this.b64LogID = b64LogID; this.b64LogID = b64LogID;
@ -437,40 +426,6 @@ class IntermediatePreloads {
log.debug(`Removing ${deleted.length} Intermediate certificates`); log.debug(`Removing ${deleted.length} Intermediate certificates`);
await this.removeCerts(deleted); await this.removeCerts(deleted);
let hasPriorCRLiteData = await hasPriorData(
Ci.nsICertStorage.DATA_TYPE_CRLITE
);
if (!hasPriorCRLiteData) {
deleted = [];
updated = [];
created = current;
}
const toAdd = created.concat(updated.map(u => u.new));
let entries = [];
for (let entry of deleted) {
entries.push(
new CRLiteState(
entry.subjectDN,
entry.pubKeyHash,
Ci.nsICertStorage.STATE_UNSET
)
);
}
for (let entry of toAdd) {
entries.push(
new CRLiteState(
entry.subjectDN,
entry.pubKeyHash,
entry.crlite_enrolled
? Ci.nsICertStorage.STATE_ENFORCE
: Ci.nsICertStorage.STATE_UNSET
)
);
}
let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(
Ci.nsICertStorage
);
await new Promise(resolve => certStorage.setCRLiteState(entries, resolve));
} }
/** /**
@ -687,9 +642,10 @@ class CRLiteFilters {
); );
} }
} }
let enrollment = filter.enrolledIssuers ? filter.enrolledIssuers : [];
await new Promise(resolve => { await new Promise(resolve => {
certList.setFullCRLiteFilter(filter.bytes, coverage, rv => { certList.setFullCRLiteFilter(filter.bytes, enrollment, coverage, rv => {
log.debug(`setFullCRLiteFilter: ${rv}`); log.debug(`setFullCRLiteFilter: ${rv}`);
resolve(); resolve();
}); });

Просмотреть файл

@ -59,21 +59,24 @@ use std::time::{Duration, SystemTime};
use storage_variant::VariantType; use storage_variant::VariantType;
use thin_vec::ThinVec; use thin_vec::ThinVec;
use xpcom::interfaces::{ use xpcom::interfaces::{
nsICRLiteCoverage, nsICRLiteState, nsICRLiteTimestamp, nsICertInfo, nsICertStorage, nsICRLiteCoverage, nsICRLiteTimestamp, nsICertInfo, nsICertStorage, nsICertStorageCallback,
nsICertStorageCallback, nsIFile, nsIHandleReportCallback, nsIIssuerAndSerialRevocationState, nsIFile, nsIHandleReportCallback, nsIIssuerAndSerialRevocationState, nsIMemoryReporter,
nsIMemoryReporter, nsIMemoryReporterManager, nsIObserver, nsIPrefBranch, nsIRevocationState, nsIMemoryReporterManager, nsIObserver, nsIPrefBranch, nsIRevocationState, nsISerialEventTarget,
nsISerialEventTarget, nsISubjectAndPubKeyRevocationState, nsISupports, nsISubjectAndPubKeyRevocationState, nsISupports,
}; };
use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom}; use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom};
const PREFIX_REV_IS: &str = "is"; const PREFIX_REV_IS: &str = "is";
const PREFIX_REV_SPK: &str = "spk"; const PREFIX_REV_SPK: &str = "spk";
const PREFIX_CRLITE: &str = "crlite";
const PREFIX_SUBJECT: &str = "subject"; const PREFIX_SUBJECT: &str = "subject";
const PREFIX_CERT: &str = "cert"; const PREFIX_CERT: &str = "cert";
const PREFIX_DATA_TYPE: &str = "datatype"; const PREFIX_DATA_TYPE: &str = "datatype";
const COVERAGE_SERIALIZATION_VERSION: u8 = 1; const COVERAGE_SERIALIZATION_VERSION: u8 = 1;
const COVERAGE_V1_ENTRY_BYTES: usize = 48;
const ENROLLMENT_SERIALIZATION_VERSION: u8 = 1;
const ENROLLMENT_V1_ENTRY_BYTES: usize = 32;
type Rkv = rkv::Rkv<SafeModeEnvironment>; type Rkv = rkv::Rkv<SafeModeEnvironment>;
type SingleStore = rkv::SingleStore<SafeModeDatabase>; type SingleStore = rkv::SingleStore<SafeModeDatabase>;
@ -157,6 +160,8 @@ struct SecurityState {
crlite_stash: Option<HashMap<Vec<u8>, HashSet<Vec<u8>>>>, crlite_stash: Option<HashMap<Vec<u8>, HashSet<Vec<u8>>>>,
/// Maps an RFC 6962 LogID to a pair of 64 bit unix timestamps /// Maps an RFC 6962 LogID to a pair of 64 bit unix timestamps
crlite_coverage: Option<HashMap<Vec<u8>, (u64, u64)>>, crlite_coverage: Option<HashMap<Vec<u8>, (u64, u64)>>,
/// Set of `SHA256(subject || spki)` values for enrolled issuers
crlite_enrollment: Option<HashSet<Vec<u8>>>,
/// Tracks the number of asynchronous operations which have been dispatched but not completed. /// Tracks the number of asynchronous operations which have been dispatched but not completed.
remaining_ops: i32, remaining_ops: i32,
} }
@ -172,6 +177,7 @@ impl SecurityState {
crlite_filter: None, crlite_filter: None,
crlite_stash: None, crlite_stash: None,
crlite_coverage: None, crlite_coverage: None,
crlite_enrollment: None,
remaining_ops: 0, remaining_ops: 0,
}) })
} }
@ -302,7 +308,9 @@ impl SecurityState {
pub fn get_has_prior_data(&self, data_type: u8) -> Result<bool, SecurityStateError> { pub fn get_has_prior_data(&self, data_type: u8) -> Result<bool, SecurityStateError> {
if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_FULL { if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_FULL {
return Ok(self.crlite_filter.is_some() && self.crlite_coverage.is_some()); return Ok(self.crlite_filter.is_some()
&& self.crlite_coverage.is_some()
&& self.crlite_enrollment.is_some());
} }
if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_INCREMENTAL { if data_type == nsICertStorage::DATA_TYPE_CRLITE_FILTER_INCREMENTAL {
return Ok(self.crlite_stash.is_some()); return Ok(self.crlite_stash.is_some());
@ -401,24 +409,18 @@ impl SecurityState {
} }
} }
pub fn get_crlite_state( fn issuer_is_enrolled(&self, subject: &[u8], pub_key: &[u8]) -> bool {
&self, if let Some(crlite_enrollment) = self.crlite_enrollment.as_ref() {
subject: &[u8], let mut digest = Sha256::default();
pub_key: &[u8], digest.input(subject);
) -> Result<i16, SecurityStateError> { digest.input(pub_key);
let mut digest = Sha256::default(); let issuer_id = digest.result();
digest.input(pub_key); return crlite_enrollment.contains(&issuer_id.to_vec());
let pub_key_hash = digest.result();
let subject_pubkey = make_key!(PREFIX_CRLITE, subject, &pub_key_hash);
match self.read_entry(&subject_pubkey) {
Ok(Some(value)) => Ok(value),
Ok(None) => Ok(nsICertStorage::STATE_UNSET),
Err(_) => Err(SecurityStateError::from("problem reading crlite state")),
} }
return false;
} }
pub fn filter_covers_some_timestamp(&self, timestamps: &[CRLiteTimestamp]) -> bool { fn filter_covers_some_timestamp(&self, timestamps: &[CRLiteTimestamp]) -> bool {
if let Some(crlite_coverage) = self.crlite_coverage.as_ref() { if let Some(crlite_coverage) = self.crlite_coverage.as_ref() {
for entry in timestamps { for entry in timestamps {
if let Some(&(low, high)) = crlite_coverage.get(entry.log_id.as_ref()) { if let Some(&(low, high)) = crlite_coverage.get(entry.log_id.as_ref()) {
@ -434,6 +436,7 @@ impl SecurityState {
pub fn set_full_crlite_filter( pub fn set_full_crlite_filter(
&mut self, &mut self,
filter: Vec<u8>, filter: Vec<u8>,
enrolled_issuers: Vec<nsCString>,
coverage_entries: &[(nsCString, u64, u64)], coverage_entries: &[(nsCString, u64, u64)],
) -> Result<(), SecurityStateError> { ) -> Result<(), SecurityStateError> {
// First drop any existing crlite filter and clear the accumulated stash. // First drop any existing crlite filter and clear the accumulated stash.
@ -441,6 +444,7 @@ impl SecurityState {
let _ = self.crlite_filter.take(); let _ = self.crlite_filter.take();
let _ = self.crlite_stash.take(); let _ = self.crlite_stash.take();
let _ = self.crlite_coverage.take(); let _ = self.crlite_coverage.take();
let _ = self.crlite_enrollment.take();
let mut path = get_store_path(&self.profile_path)?; let mut path = get_store_path(&self.profile_path)?;
path.push("crlite.stash"); path.push("crlite.stash");
// Truncate the stash file if it exists. // Truncate the stash file if it exists.
@ -461,9 +465,8 @@ impl SecurityState {
// Serialize the coverage metadata as a 1 byte version number followed by any number of 48 // Serialize the coverage metadata as a 1 byte version number followed by any number of 48
// byte entries. Each entry is a 32 byte (opaque) log id, followed by two 8 byte // byte entries. Each entry is a 32 byte (opaque) log id, followed by two 8 byte
// timestamps. Each timestamp is an 8 byte unsigned integer in little endian. // timestamps. Each timestamp is an 8 byte unsigned integer in little endian.
let mut coverage_bytes = Vec::with_capacity( let mut coverage_bytes =
size_of::<u8>() + coverage_entries.len() * (32 + size_of::<u64>() + size_of::<u64>()), Vec::with_capacity(size_of::<u8>() + coverage_entries.len() * COVERAGE_V1_ENTRY_BYTES);
);
coverage_bytes.push(COVERAGE_SERIALIZATION_VERSION); coverage_bytes.push(COVERAGE_SERIALIZATION_VERSION);
for (b64_log_id, min_t, max_t) in coverage_entries { for (b64_log_id, min_t, max_t) in coverage_entries {
let log_id = match base64::decode(&b64_log_id) { let log_id = match base64::decode(&b64_log_id) {
@ -484,8 +487,32 @@ impl SecurityState {
let mut coverage_file = File::create(&path)?; let mut coverage_file = File::create(&path)?;
coverage_file.write_all(&coverage_bytes)?; coverage_file.write_all(&coverage_bytes)?;
} }
self.load_crlite_filter()?;
// Serialize the enrollment list as a 1 byte version number followed by:
// Version 1: any number of 32 byte values of the form `SHA256(subject || spki)`.
let mut enrollment_bytes = Vec::with_capacity(
size_of::<u8>() + enrolled_issuers.len() * ENROLLMENT_V1_ENTRY_BYTES,
);
enrollment_bytes.push(ENROLLMENT_SERIALIZATION_VERSION);
for b64_issuer_id in enrolled_issuers {
let issuer_id = match base64::decode(&b64_issuer_id) {
Ok(issuer_id) if issuer_id.len() == 32 => issuer_id,
_ => {
warn!("malformed issuer ID - skipping: {}", b64_issuer_id);
continue;
}
};
enrollment_bytes.extend_from_slice(&issuer_id);
}
// Write the enrollment file for the new filter
let mut path = get_store_path(&self.profile_path)?;
path.push("crlite.enrollment");
{
let mut enrollment_file = File::create(&path)?;
enrollment_file.write_all(&enrollment_bytes)?;
}
self.load_crlite_filter()?;
Ok(()) Ok(())
} }
@ -521,53 +548,78 @@ impl SecurityState {
// Deserialize the coverage metadata. // Deserialize the coverage metadata.
// The format is described in `set_full_crlite_filter`. // The format is described in `set_full_crlite_filter`.
let coverage_file = File::open(path)?; let coverage_file = File::open(path)?;
let coverage_file_len = coverage_file.metadata()?.len() as usize;
let mut coverage_reader = BufReader::new(coverage_file); let mut coverage_reader = BufReader::new(coverage_file);
match coverage_reader.read_u8() { match coverage_reader.read_u8() {
Ok(COVERAGE_SERIALIZATION_VERSION) => (), Ok(COVERAGE_SERIALIZATION_VERSION) => (),
_ => { _ => return Err(SecurityStateError::from("unknown CRLite coverage version")),
return Err(SecurityStateError::from(
"unable to initialize CRLite coverage",
))
}
} }
if (coverage_file_len - 1) % COVERAGE_V1_ENTRY_BYTES != 0 {
return Err(SecurityStateError::from("truncated CRLite coverage file"));
}
let coverage_count = (coverage_file_len - 1) / COVERAGE_V1_ENTRY_BYTES;
let mut crlite_coverage: HashMap<Vec<u8>, (u64, u64)> = HashMap::new(); let mut crlite_coverage: HashMap<Vec<u8>, (u64, u64)> = HashMap::new();
loop { for _ in 0..coverage_count {
let mut coverage_entry = [0u8; 48]; let mut coverage_entry = [0u8; COVERAGE_V1_ENTRY_BYTES];
match coverage_reader.read(&mut coverage_entry) { match coverage_reader.read_exact(&mut coverage_entry) {
Ok(48) => (), Ok(()) => (),
Ok(0) => break, // end of file _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
_ => {
return Err(SecurityStateError::from(
"unable to initialize CRLite coverage",
))
}
}; };
let log_id = &coverage_entry[0..32]; let log_id = &coverage_entry[0..32];
let min_timestamp: u64; let min_timestamp: u64;
let max_timestamp: u64; let max_timestamp: u64;
match (&coverage_entry[32..40]).read_u64::<LittleEndian>() { match (&coverage_entry[32..40]).read_u64::<LittleEndian>() {
Ok(value) => min_timestamp = value, Ok(value) => min_timestamp = value,
_ => { _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
return Err(SecurityStateError::from(
"unable to initialize CRLite coverage",
))
}
} }
match (&coverage_entry[40..48]).read_u64::<LittleEndian>() { match (&coverage_entry[40..48]).read_u64::<LittleEndian>() {
Ok(value) => max_timestamp = value, Ok(value) => max_timestamp = value,
_ => { _ => return Err(SecurityStateError::from("truncated CRLite coverage file")),
return Err(SecurityStateError::from(
"unable to initialize CRLite coverage",
))
}
} }
crlite_coverage.insert(log_id.to_vec(), (min_timestamp, max_timestamp)); crlite_coverage.insert(log_id.to_vec(), (min_timestamp, max_timestamp));
} }
let mut path = get_store_path(&self.profile_path)?;
path.push("crlite.enrollment");
if !path.exists() {
return Ok(());
}
// Deserialize the enrollment metadata.
// The format is described in `set_full_crlite_filter`.
let enrollment_file = File::open(path)?;
let enrollment_file_len = enrollment_file.metadata()?.len() as usize;
let mut enrollment_reader = BufReader::new(enrollment_file);
match enrollment_reader.read_u8() {
Ok(ENROLLMENT_SERIALIZATION_VERSION) => (),
_ => {
return Err(SecurityStateError::from(
"unknown CRLite enrollment version",
))
}
}
if (enrollment_file_len - 1) % ENROLLMENT_V1_ENTRY_BYTES != 0 {
return Err(SecurityStateError::from("truncated CRLite enrollment file"));
}
let enrollment_count = (enrollment_file_len - 1) / ENROLLMENT_V1_ENTRY_BYTES;
let mut crlite_enrollment: HashSet<Vec<u8>> = HashSet::new();
for _ in 0..enrollment_count {
let mut enrollment_entry = [0u8; ENROLLMENT_V1_ENTRY_BYTES];
match enrollment_reader.read_exact(&mut enrollment_entry) {
Ok(()) => (),
_ => return Err(SecurityStateError::from("truncated CRLite enrollment file")),
};
let issuer_id = &enrollment_entry[..];
crlite_enrollment.insert(issuer_id.to_vec());
}
let old_crlite_filter_should_be_none = self.crlite_filter.replace(crlite_filter); let old_crlite_filter_should_be_none = self.crlite_filter.replace(crlite_filter);
assert!(old_crlite_filter_should_be_none.is_none()); assert!(old_crlite_filter_should_be_none.is_none());
let old_crlite_coverage_should_be_none = self.crlite_coverage.replace(crlite_coverage); let old_crlite_coverage_should_be_none = self.crlite_coverage.replace(crlite_coverage);
assert!(old_crlite_coverage_should_be_none.is_none()); assert!(old_crlite_coverage_should_be_none.is_none());
let old_crlite_enrollment_should_be_none =
self.crlite_enrollment.replace(crlite_enrollment);
assert!(old_crlite_enrollment_should_be_none.is_none());
Ok(()) Ok(())
} }
@ -607,13 +659,12 @@ impl SecurityState {
issuer_spki: &[u8], issuer_spki: &[u8],
serial_number: &[u8], serial_number: &[u8],
timestamps: &[CRLiteTimestamp], timestamps: &[CRLiteTimestamp],
) -> Result<i16, SecurityStateError> { ) -> i16 {
let enrollment_state = self.get_crlite_state(issuer, issuer_spki)?; if !self.issuer_is_enrolled(issuer, issuer_spki) {
if enrollment_state != nsICertStorage::STATE_ENFORCE { return nsICertStorage::STATE_NOT_ENROLLED;
return Ok(nsICertStorage::STATE_NOT_ENROLLED);
} }
if !self.filter_covers_some_timestamp(timestamps) { if !self.filter_covers_some_timestamp(timestamps) {
return Ok(nsICertStorage::STATE_NOT_COVERED); return nsICertStorage::STATE_NOT_COVERED;
} }
let mut digest = Sha256::default(); let mut digest = Sha256::default();
digest.input(issuer_spki); digest.input(issuer_spki);
@ -624,11 +675,11 @@ impl SecurityState {
Some(crlite_filter) => crlite_filter.rent(|filter| filter.has(&lookup_key)), Some(crlite_filter) => crlite_filter.rent(|filter| filter.has(&lookup_key)),
// This can only happen if the backing file was deleted or if it or our database has // This can only happen if the backing file was deleted or if it or our database has
// become corrupted. In any case, we have no information. // become corrupted. In any case, we have no information.
None => return Ok(nsICertStorage::STATE_NOT_COVERED), None => return nsICertStorage::STATE_NOT_COVERED,
}; };
match result { match result {
true => Ok(nsICertStorage::STATE_ENFORCE), true => nsICertStorage::STATE_ENFORCE,
false => Ok(nsICertStorage::STATE_UNSET), false => nsICertStorage::STATE_UNSET,
} }
} }
@ -1649,87 +1700,26 @@ impl CertStorage {
NS_OK NS_OK
} }
unsafe fn SetCRLiteState(
&self,
crlite_state: *const ThinVec<RefPtr<nsICRLiteState>>,
callback: *const nsICertStorageCallback,
) -> nserror::nsresult {
if !is_main_thread() {
return NS_ERROR_NOT_SAME_THREAD;
}
if crlite_state.is_null() || callback.is_null() {
return NS_ERROR_NULL_POINTER;
}
let crlite_state = &*crlite_state;
let mut crlite_entries = Vec::with_capacity(crlite_state.len());
// By continuing when an nsICRLiteState attribute value is invalid, we prevent errors
// relating to individual entries from causing sync to fail.
for crlite_entry in crlite_state {
let mut state: i16 = 0;
try_ns!(crlite_entry.GetState(&mut state).to_result(), or continue);
let mut subject = nsCString::new();
try_ns!(crlite_entry.GetSubject(&mut *subject).to_result(), or continue);
let mut pub_key_hash = nsCString::new();
try_ns!(crlite_entry.GetSpkiHash(&mut *pub_key_hash).to_result(), or continue);
crlite_entries.push(EncodedSecurityState::new(
PREFIX_CRLITE,
subject,
pub_key_hash,
state,
));
}
let task = Box::new(try_ns!(SecurityStateTask::new(
&*callback,
&self.security_state,
move |ss| ss.set_batch_state(&crlite_entries, nsICertStorage::DATA_TYPE_CRLITE),
)));
let runnable = try_ns!(TaskRunnable::new("SetCRLiteState", task));
try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
NS_OK
}
unsafe fn GetCRLiteState(
&self,
subject: *const ThinVec<u8>,
pub_key: *const ThinVec<u8>,
state: *mut i16,
) -> nserror::nsresult {
// TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
// can't do so until bug 1406854 is fixed.
if subject.is_null() || pub_key.is_null() {
return NS_ERROR_NULL_POINTER;
}
*state = nsICertStorage::STATE_UNSET;
let ss = get_security_state!(self);
match ss.get_crlite_state(&*subject, &*pub_key) {
Ok(st) => {
*state = st;
NS_OK
}
_ => NS_ERROR_FAILURE,
}
}
unsafe fn SetFullCRLiteFilter( unsafe fn SetFullCRLiteFilter(
&self, &self,
filter: *const ThinVec<u8>, filter: *const ThinVec<u8>,
enrolled_issuers: *const ThinVec<nsCString>,
coverage: *const ThinVec<RefPtr<nsICRLiteCoverage>>, coverage: *const ThinVec<RefPtr<nsICRLiteCoverage>>,
callback: *const nsICertStorageCallback, callback: *const nsICertStorageCallback,
) -> nserror::nsresult { ) -> nserror::nsresult {
if !is_main_thread() { if !is_main_thread() {
return NS_ERROR_NOT_SAME_THREAD; return NS_ERROR_NOT_SAME_THREAD;
} }
if filter.is_null() || coverage.is_null() || callback.is_null() { if filter.is_null()
|| coverage.is_null()
|| callback.is_null()
|| enrolled_issuers.is_null()
{
return NS_ERROR_NULL_POINTER; return NS_ERROR_NULL_POINTER;
} }
let filter_owned = (*filter).to_vec(); let filter_owned = (*filter).to_vec();
let enrolled_issuers_owned = (*enrolled_issuers).to_vec();
let coverage = &*coverage; let coverage = &*coverage;
let mut coverage_entries = Vec::with_capacity(coverage.len()); let mut coverage_entries = Vec::with_capacity(coverage.len());
@ -1746,7 +1736,11 @@ impl CertStorage {
let task = Box::new(try_ns!(SecurityStateTask::new( let task = Box::new(try_ns!(SecurityStateTask::new(
&*callback, &*callback,
&self.security_state, &self.security_state,
move |ss| ss.set_full_crlite_filter(filter_owned, &coverage_entries), move |ss| ss.set_full_crlite_filter(
filter_owned,
enrolled_issuers_owned,
&coverage_entries
),
))); )));
let runnable = try_ns!(TaskRunnable::new("SetFullCRLiteFilter", task)); let runnable = try_ns!(TaskRunnable::new("SetFullCRLiteFilter", task));
try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce())); try_ns!(TaskRunnable::dispatch(runnable, self.queue.coerce()));
@ -1819,20 +1813,14 @@ impl CertStorage {
try_ns!(timestamp_entry.GetTimestamp(&mut timestamp).to_result(), or continue); try_ns!(timestamp_entry.GetTimestamp(&mut timestamp).to_result(), or continue);
timestamp_entries.push(CRLiteTimestamp { log_id, timestamp }); timestamp_entries.push(CRLiteTimestamp { log_id, timestamp });
} }
*state = nsICertStorage::STATE_UNSET;
let ss = get_security_state!(self); let ss = get_security_state!(self);
match ss.get_crlite_revocation_state( *state = ss.get_crlite_revocation_state(
&*issuer, &*issuer,
&*issuerSPKI, &*issuerSPKI,
&*serialNumber, &*serialNumber,
&timestamp_entries, &timestamp_entries,
) { );
Ok(st) => { NS_OK
*state = st;
NS_OK
}
_ => NS_ERROR_FAILURE,
}
} }
unsafe fn AddCerts( unsafe fn AddCerts(

Просмотреть файл

@ -55,21 +55,6 @@ interface nsISubjectAndPubKeyRevocationState : nsIRevocationState {
readonly attribute ACString pubKey; readonly attribute ACString pubKey;
}; };
/**
* An interface representing the CRLite enrollment state of a certificate
* identified by its subject and subject public key info hash.
* subject is a base 64-encoded DER subject distinguished name.
* spkiHash is a base 64-encoded SHA-256 hash of a DER subject public key info.
* state is nsICertStorage.STATE_ENFORCE or STATE_UNSET, meaning the certificate
* is or is not enrolled in CRLite, respectively.
*/
[scriptable, uuid(5d0d22be-185f-4cf0-b73b-c5a911273e77)]
interface nsICRLiteState : nsISupports {
readonly attribute ACString subject;
readonly attribute ACString spkiHash;
readonly attribute short state;
};
/** /**
* An interface representing a set of certificates that are covered by a CRLite * An interface representing a set of certificates that are covered by a CRLite
* filter. The set is represented by a certificate transparency log ID and a * filter. The set is represented by a certificate transparency log ID and a
@ -174,30 +159,15 @@ interface nsICertStorage : nsISupports {
boolean isBlocklistFresh(); boolean isBlocklistFresh();
/** /**
* Asynchronously set a batch of CRLite enrollment state. See the * Given the contents of a new CRLite filter, a list containing
* documentation for nsICRLiteState. * `base64(sha256(subject DN || subject SPKI))` for each enrolled issuer, and
* Must only be called from the main thread. * the filter's timestamp coverage, replaces any existing filter with the new
*/ * one. Also clears any previously-set incremental revocation updates
[must_use] * ("stashes").
void setCRLiteState(in Array<nsICRLiteState> crliteState,
in nsICertStorageCallback callback);
/**
* Get the CRLite enrollment state of a certificate identified by the given
* subject distinguished name and subject public key info (both as DER bytes).
* STATE_ENFORCE indicates the certificate is enrolled, whereas STATE_UNSET
* indicates it is not.
*/
[must_use]
short getCRLiteState(in Array<octet> subject, in Array<octet> spki);
/**
* Given the contents of a new CRLite filter and a description of the new
* filter's coverage, replaces any existing filter with the new one. Also
* clears any previously-set incremental revocation updates ("stashes").
*/ */
[must_use] [must_use]
void setFullCRLiteFilter(in Array<octet> filter, void setFullCRLiteFilter(in Array<octet> filter,
in Array<ACString> enrolledIssuers,
in Array<nsICRLiteCoverage> coverage, in Array<nsICRLiteCoverage> coverage,
in nsICertStorageCallback callback); in nsICertStorageCallback callback);

Просмотреть файл

@ -8,7 +8,7 @@
// //
// Usage: // Usage:
// Define nsILocalFile variables for the `crlite.filter`, `crlite.coverage`, // Define nsILocalFile variables for the `crlite.filter`, `crlite.coverage`,
// and `data.safe.bin` files that should be copied to the new profile, and // and `crlite.enrollment` files that should be copied to the new profile, and
// then load this file. The variables should be called `filter`, `coverage`, // then load this file. The variables should be called `filter`, `coverage`,
// and `enrollment`, respectively. To omit a file, leave the corresponding // and `enrollment`, respectively. To omit a file, leave the corresponding
// variable `undefined`. // variable `undefined`.
@ -16,7 +16,7 @@
// Example: // Example:
// let filter = do_get_file("some_test_dir/crlite.filter"); // let filter = do_get_file("some_test_dir/crlite.filter");
// let coverage = undefined; // let coverage = undefined;
// let enrollment = do_get_file("some_test_dir/data.safe.bin"); // let enrollment = do_get_file("some_test_dir/crlite.enrollment");
// load("./corrupted_crlite_helper.js"); // load("./corrupted_crlite_helper.js");
// //
// Note: // Note:
@ -40,7 +40,7 @@ add_task(async function test_crlite_corrupted() {
coverage.copyTo(securityStateDirectory, "crlite.coverage"); coverage.copyTo(securityStateDirectory, "crlite.coverage");
} }
if (enrollment != undefined) { if (enrollment != undefined) {
enrollment.copyTo(securityStateDirectory, "data.safe.bin"); enrollment.copyTo(securityStateDirectory, "crlite.enrollment");
} }
if (filter != undefined) { if (filter != undefined) {
filter.copyTo(securityStateDirectory, "crlite.filter"); filter.copyTo(securityStateDirectory, "crlite.filter");
@ -55,7 +55,7 @@ add_task(async function test_crlite_corrupted() {
); );
// This certificate is revoked according to `test_crlite_filters/20201017-0-filter`. // This certificate is revoked according to `test_crlite_filters/20201017-0-filter`.
// Its issuer is enrolled according to `test_crlite_preexisting/data.safe.bin`, // Its issuer is enrolled according to `test_crlite_preexisting/crlite.enrollment`,
// and it is covered according to `test_crlite_preexisting/crlite.coverage`. // and it is covered according to `test_crlite_preexisting/crlite.coverage`.
let revokedCert = constructCertFromFile("test_crlite_filters/revoked.pem"); let revokedCert = constructCertFromFile("test_crlite_filters/revoked.pem");
@ -78,20 +78,7 @@ add_task(async function test_crlite_corrupted() {
Ci.nsIX509CertDB.FLAG_LOCAL_ONLY Ci.nsIX509CertDB.FLAG_LOCAL_ONLY
); );
// The attempted revocation check should have at least partially initialized // We should not have a filter or a stash.
// CRLite. We should have a database (an empty one is created if
// `data.safe.bin` is corrupted). But we should not have a filter or a stash.
let hasDB = await new Promise(resolve => {
certStorage.hasPriorData(
Ci.nsICertStorage.DATA_TYPE_CRLITE,
(rv, result) => {
Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed");
resolve(result);
}
);
});
Assert.equal(hasDB, true, "CRLite should have a database");
let hasFilter = await new Promise(resolve => { let hasFilter = await new Promise(resolve => {
certStorage.hasPriorData( certStorage.hasPriorData(
Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_FULL, Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_FULL,

Просмотреть файл

@ -0,0 +1,33 @@
#!/usr/bin/python
# Given a PEM encoded X.509 certificate, outputs
# base64(SHA256(subject || spki))
# where `subject` is the RFC 5280 RDNSequence encoding
# the certificate's subject, and `spki` is the RFC 5280
# SubjectPublicKeyInfo field encoding the certificate's
# public key.
import sys
import base64
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <path to pem cert>")
sys.exit(1)
with open(sys.argv[1], "r") as f:
cert = x509.load_pem_x509_certificate(f.read().encode("utf-8"), backend=None)
subj = cert.subject.public_bytes()
spki = cert.public_key().public_bytes(
format=serialization.PublicFormat.SubjectPublicKeyInfo,
encoding=serialization.Encoding.DER,
)
digest = hashes.Hash(hashes.SHA256(), backend=None)
digest.update(subj)
digest.update(spki)
print(base64.b64encode(digest.finalize()).decode("utf-8"))

Просмотреть файл

@ -41,18 +41,6 @@ async function check_has_prior_cert_data(certStorage, expectedResult) {
); );
} }
async function check_has_prior_crlite_data(certStorage, expectedResult) {
let hasPriorCRLiteData = await call_has_prior_data(
certStorage,
Ci.nsICertStorage.DATA_TYPE_CRLITE
);
Assert.equal(
hasPriorCRLiteData,
expectedResult,
`should ${expectedResult ? "have" : "not have"} prior CRLite data`
);
}
add_task(async function() { add_task(async function() {
// Create an invalid database. // Create an invalid database.
let fileToCopy = do_get_file("test_cert_storage_broken_db.js"); let fileToCopy = do_get_file("test_cert_storage_broken_db.js");
@ -65,7 +53,6 @@ add_task(async function() {
); );
check_has_prior_revocation_data(certStorage, false); check_has_prior_revocation_data(certStorage, false);
check_has_prior_cert_data(certStorage, false); check_has_prior_cert_data(certStorage, false);
check_has_prior_crlite_data(certStorage, false);
let result = await new Promise(resolve => { let result = await new Promise(resolve => {
certStorage.setRevocations([], resolve); certStorage.setRevocations([], resolve);
@ -74,7 +61,6 @@ add_task(async function() {
check_has_prior_revocation_data(certStorage, true); check_has_prior_revocation_data(certStorage, true);
check_has_prior_cert_data(certStorage, false); check_has_prior_cert_data(certStorage, false);
check_has_prior_crlite_data(certStorage, false);
result = await new Promise(resolve => { result = await new Promise(resolve => {
certStorage.addCerts([], resolve); certStorage.addCerts([], resolve);
@ -83,13 +69,4 @@ add_task(async function() {
check_has_prior_revocation_data(certStorage, true); check_has_prior_revocation_data(certStorage, true);
check_has_prior_cert_data(certStorage, true); check_has_prior_cert_data(certStorage, true);
check_has_prior_crlite_data(certStorage, false);
result = await new Promise(resolve => {
certStorage.setCRLiteState([], resolve);
});
Assert.equal(result, Cr.NS_OK, "setCRLiteState should succeed");
check_has_prior_revocation_data(certStorage, true);
check_has_prior_cert_data(certStorage, true);
check_has_prior_crlite_data(certStorage, true);
}); });

Просмотреть файл

@ -242,17 +242,6 @@ add_task(async function test_batched_removal() {
Assert.equal(storedCerts.length, 0, "shouldn't have any certificates now"); Assert.equal(storedCerts.length, 0, "shouldn't have any certificates now");
}); });
class CRLiteState {
constructor(subject, spkiHash, state) {
this.subject = btoa(subject);
this.spkiHash = spkiHash;
this.state = state;
}
}
CRLiteState.prototype.QueryInterface = ChromeUtils.generateQI([
"nsICRLiteState",
]);
class CRLiteCoverage { class CRLiteCoverage {
constructor(ctLogID, minTimestamp, maxTimestamp) { constructor(ctLogID, minTimestamp, maxTimestamp) {
this.b64LogID = ctLogID; this.b64LogID = ctLogID;
@ -264,133 +253,6 @@ CRLiteCoverage.prototype.QueryInterface = ChromeUtils.generateQI([
"nsICRLiteCoverage", "nsICRLiteCoverage",
]); ]);
async function addCRLiteState(state) {
let result = await new Promise(resolve => {
certStorage.setCRLiteState(state, resolve);
});
Assert.equal(result, Cr.NS_OK, "setCRLiteState should succeed");
}
add_task(async function test_crlite_state() {
// echo -n "some spki 1" | sha256sum | xxd -r -p | base64
let crliteState1 = new CRLiteState(
"some subject 1",
"bDlKlhR5ptlvuxclnZ3RQHznG8/3pgIybrRJ/Zvn9L8=",
Ci.nsICertStorage.STATE_ENFORCE
);
// echo -n "some spki 2" | sha256sum | xxd -r -p | base64
let crliteState2 = new CRLiteState(
"some subject 2",
"ZlXvlHhtdx4yKwkhZqg7Opv5T1ofwzorlsCoLf0wnlY=",
Ci.nsICertStorage.STATE_UNSET
);
// echo -n "some spki 3" | sha256sum | xxd -r -p | base64
let crliteState3 = new CRLiteState(
"some subject 3",
"pp1SRn6njaHX/c+b2uf82JPeBkWhPfTBp/Mxb3xkjRM=",
Ci.nsICertStorage.STATE_ENFORCE
);
await addCRLiteState([crliteState1, crliteState2, crliteState3]);
let state1 = certStorage.getCRLiteState(
stringToArray("some subject 1"),
stringToArray("some spki 1")
);
Assert.equal(state1, Ci.nsICertStorage.STATE_ENFORCE);
let state2 = certStorage.getCRLiteState(
stringToArray("some subject 2"),
stringToArray("some spki 2")
);
Assert.equal(state2, Ci.nsICertStorage.STATE_UNSET);
let state3 = certStorage.getCRLiteState(
stringToArray("some subject 3"),
stringToArray("some spki 3")
);
Assert.equal(state3, Ci.nsICertStorage.STATE_ENFORCE);
// Check that if we never set the state of a particular subject/spki pair, we get "unset" when we
// look it up.
let stateNeverSet = certStorage.getCRLiteState(
stringToArray("some unknown subject"),
stringToArray("some unknown spki")
);
Assert.equal(stateNeverSet, Ci.nsICertStorage.STATE_UNSET);
// "some subject 1"/"some spki 1" and "some subject 3"/"some spki 3" both have their CRLite state
// set. However, the combination of "some subject 3"/"some spki1" should not.
let stateDifferentSubjectSPKI = certStorage.getCRLiteState(
stringToArray("some subject 3"),
stringToArray("some spki 1")
);
Assert.equal(stateDifferentSubjectSPKI, Ci.nsICertStorage.STATE_UNSET);
let anotherStateDifferentSubjectSPKI = certStorage.getCRLiteState(
stringToArray("some subject 1"),
stringToArray("some spki 2")
);
Assert.equal(anotherStateDifferentSubjectSPKI, Ci.nsICertStorage.STATE_UNSET);
let yetAnotherStateDifferentSubjectSPKI = certStorage.getCRLiteState(
stringToArray("some subject 2"),
stringToArray("some spki 1")
);
Assert.equal(
yetAnotherStateDifferentSubjectSPKI,
Ci.nsICertStorage.STATE_UNSET
);
crliteState3 = new CRLiteState(
"some subject 3",
"pp1SRn6njaHX/c+b2uf82JPeBkWhPfTBp/Mxb3xkjRM=",
Ci.nsICertStorage.STATE_UNSET
);
await addCRLiteState([crliteState3]);
state3 = certStorage.getCRLiteState(
stringToArray("some subject 3"),
stringToArray("some spki 3")
);
Assert.equal(state3, Ci.nsICertStorage.STATE_UNSET);
crliteState2 = new CRLiteState(
"some subject 2",
"ZlXvlHhtdx4yKwkhZqg7Opv5T1ofwzorlsCoLf0wnlY=",
Ci.nsICertStorage.STATE_ENFORCE
);
await addCRLiteState([crliteState2]);
state2 = certStorage.getCRLiteState(
stringToArray("some subject 2"),
stringToArray("some spki 2")
);
Assert.equal(state2, Ci.nsICertStorage.STATE_ENFORCE);
// Inserting a subject/spki pair with a state value outside of our expected
// values will succeed. However, since our data type is a signed 16-bit value,
// values outside that range will be truncated. The least significant 16 bits
// of 2013003773 are FFFD, which when interpreted as a signed 16-bit integer
// comes out to -3.
// echo -n "some spki 4" | sha256sum | xxd -r -p | base64
let bogusValueState = new CRLiteState(
"some subject 4",
"1eA0++hCqzt8vpzREYSqHAqpEOLchZca1Gx8viCVYzc=",
2013003773
);
await addCRLiteState([bogusValueState]);
let bogusValueStateValue = certStorage.getCRLiteState(
stringToArray("some subject 4"),
stringToArray("some spki 4")
);
Assert.equal(bogusValueStateValue, -3);
});
async function enrollCertForCRLite(nsCert) {
let { subjectString, spkiHashString } = getSubjectAndSPKIHash(nsCert);
let crliteState = new CRLiteState(
subjectString,
spkiHashString,
Ci.nsICertStorage.STATE_ENFORCE
);
await addCRLiteState([crliteState]);
}
add_task(async function test_crlite_filter() { add_task(async function test_crlite_filter() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB Ci.nsIX509CertDB
@ -398,29 +260,27 @@ add_task(async function test_crlite_filter() {
let validCertIssuer = constructCertFromFile( let validCertIssuer = constructCertFromFile(
"test_cert_storage_direct/valid-cert-issuer.pem" "test_cert_storage_direct/valid-cert-issuer.pem"
); );
await enrollCertForCRLite(validCertIssuer);
let validCert = constructCertFromFile( let validCert = constructCertFromFile(
"test_cert_storage_direct/valid-cert.pem" "test_cert_storage_direct/valid-cert.pem"
); );
let revokedCertIssuer = constructCertFromFile( let revokedCertIssuer = constructCertFromFile(
"test_cert_storage_direct/revoked-cert-issuer.pem" "test_cert_storage_direct/revoked-cert-issuer.pem"
); );
await enrollCertForCRLite(revokedCertIssuer);
let revokedCert = constructCertFromFile( let revokedCert = constructCertFromFile(
"test_cert_storage_direct/revoked-cert.pem" "test_cert_storage_direct/revoked-cert.pem"
); );
let filterFile = do_get_file( let filterFile = do_get_file(
"test_cert_storage_direct/test-filter.crlite", "test_cert_storage_direct/test-filter.crlite",
false false
); );
ok(filterFile.exists(), "test filter file should exist"); ok(filterFile.exists(), "test filter file should exist");
let enrollment = [];
let coverage = []; let coverage = [];
let filterBytes = stringToArray(readFile(filterFile)); let filterBytes = stringToArray(readFile(filterFile));
// First simualte a filter that does not cover any certificates. With CRLite // First simualte a filter that does not cover any certificates. With CRLite
// enabled, none of the certificates should appear to be revoked. // enabled, none of the certificates should appear to be revoked.
let setFullCRLiteFilterResult = await new Promise(resolve => { let setFullCRLiteFilterResult = await new Promise(resolve => {
certStorage.setFullCRLiteFilter(filterBytes, coverage, resolve); certStorage.setFullCRLiteFilter(filterBytes, enrollment, coverage, resolve);
}); });
Assert.equal( Assert.equal(
setFullCRLiteFilterResult, setFullCRLiteFilterResult,
@ -463,8 +323,15 @@ add_task(async function test_crlite_filter() {
) )
); );
// crlite_enrollment_id.py test_crlite_filters/issuer.pem
enrollment.push("UbH9/ZAnjuqf79Xhah1mFOWo6ZvgQCgsdheWfjvVUM8=");
// crlite_enrollment_id.py test_crlite_filters/no-sct-issuer.pem
enrollment.push("Myn7EasO1QikOtNmo/UZdh6snCAw0BOY6wgU8OsUeeY=");
// crlite_enrollment_id.py test_cert_storage_direct/revoked-cert-issuer.pem
enrollment.push("HTvSp2263dqBYtgYA2fldKAoTYcEVLPVTlRia9XaoCQ=");
setFullCRLiteFilterResult = await new Promise(resolve => { setFullCRLiteFilterResult = await new Promise(resolve => {
certStorage.setFullCRLiteFilter(filterBytes, coverage, resolve); certStorage.setFullCRLiteFilter(filterBytes, enrollment, coverage, resolve);
}); });
Assert.equal( Assert.equal(
setFullCRLiteFilterResult, setFullCRLiteFilterResult,

Просмотреть файл

@ -45,15 +45,4 @@ add_task(async function() {
); );
}); });
Assert.equal(hasPriorCertData, true, "should have prior cert data"); Assert.equal(hasPriorCertData, true, "should have prior cert data");
let hasPriorCRLiteData = await new Promise(resolve => {
certStorage.hasPriorData(
Ci.nsICertStorage.DATA_TYPE_CRLITE,
(rv, hasPriorData) => {
Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed");
resolve(hasPriorData);
}
);
});
Assert.equal(hasPriorCRLiteData, true, "should have prior cert data");
}); });

Просмотреть файл

@ -19,10 +19,6 @@ add_task(async function() {
let dbDirectory = do_get_profile(); let dbDirectory = do_get_profile();
dbDirectory.append("security_state"); dbDirectory.append("security_state");
let dbFile = do_get_file(
"test_cert_storage_preexisting_crlite/data.safe.bin"
);
dbFile.copyTo(dbDirectory, "data.safe.bin");
let crliteFile = do_get_file( let crliteFile = do_get_file(
"test_cert_storage_preexisting_crlite/crlite.filter" "test_cert_storage_preexisting_crlite/crlite.filter"
); );
@ -31,6 +27,10 @@ add_task(async function() {
"test_cert_storage_preexisting_crlite/crlite.coverage" "test_cert_storage_preexisting_crlite/crlite.coverage"
); );
coverageFile.copyTo(dbDirectory, "crlite.coverage"); coverageFile.copyTo(dbDirectory, "crlite.coverage");
let enrollmentFile = do_get_file(
"test_cert_storage_preexisting_crlite/crlite.enrollment"
);
enrollmentFile.copyTo(dbDirectory, "crlite.enrollment");
let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService( let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(
Ci.nsICertStorage Ci.nsICertStorage

Просмотреть файл

@ -0,0 +1 @@
3)ű«Ő¤:ÓfŁőv¬ś 0Đ<13>ëđëyćQ±ýý<C3BD>'ŽęźďŐájfĺ¨é›ŕ@(,v–~;ŐPĎ;ҧmşÝÚ<C39D>gĺt (M‡TłŐNTbkŐÚ $

Просмотреть файл

@ -0,0 +1,2 @@



Двоичный файл не отображается.

Просмотреть файл

@ -11,7 +11,7 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
let coverage = undefined; let coverage = undefined;
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter"); let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js"); load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -11,7 +11,7 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
let coverage = do_get_file("test_crlite_corrupted/trunc-log-id.coverage"); let coverage = do_get_file("test_crlite_corrupted/trunc-log-id.coverage");
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter"); let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js"); load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -13,7 +13,7 @@
let coverage = do_get_file( let coverage = do_get_file(
"test_crlite_corrupted/trunc-min-timestamp.coverage" "test_crlite_corrupted/trunc-min-timestamp.coverage"
); );
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter"); let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js"); load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -13,7 +13,7 @@
let coverage = do_get_file( let coverage = do_get_file(
"test_crlite_corrupted/trunc-max-timestamp.coverage" "test_crlite_corrupted/trunc-max-timestamp.coverage"
); );
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter"); let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js"); load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -11,7 +11,7 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
let coverage = do_get_file("test_crlite_corrupted/version-0.coverage"); let coverage = do_get_file("test_crlite_corrupted/version-0.coverage");
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter"); let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js"); load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -0,0 +1,19 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Tests that CRLite is left in the uninitialized state when the profile
// contains a corrupted enrollment file. Specifically, this handles the case
// where the enrollment file is truncated in an issuer ID field.
"use strict";
/* eslint-disable no-unused-vars */
let coverage = do_get_file("test_crlite_preexisting/crlite.coverage");
let enrollment = do_get_file(
"test_crlite_corrupted/trunc-issuer-id.enrollment"
);
let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -0,0 +1,17 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Tests that CRLite is left in the uninitialized state when the profile
// contains a corrupted enrollment file. Specifically, this handles the case
// where the enrollment file's version is not recognized.
"use strict";
/* eslint-disable no-unused-vars */
let coverage = do_get_file("test_crlite_preexisting/crlite.coverage");
let enrollment = do_get_file("test_crlite_corrupted/version-0.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter");
load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -15,7 +15,7 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
let coverage = do_get_file("test_crlite_preexisting/crlite.coverage"); let coverage = do_get_file("test_crlite_preexisting/crlite.coverage");
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
let filter = do_get_file("test_crlite_corrupted/hash-alg-0.filter"); let filter = do_get_file("test_crlite_corrupted/hash-alg-0.filter");
load("./corrupted_crlite_helper.js"); load("./corrupted_crlite_helper.js");

Просмотреть файл

@ -15,10 +15,7 @@ const { TestUtils } = ChromeUtils.import(
"resource://testing-common/TestUtils.jsm" "resource://testing-common/TestUtils.jsm"
); );
const { const { CRLiteFiltersClient } = RemoteSecuritySettings.init();
CRLiteFiltersClient,
IntermediatePreloadsClient,
} = RemoteSecuritySettings.init();
const CRLITE_FILTERS_ENABLED_PREF = const CRLITE_FILTERS_ENABLED_PREF =
"security.remote_settings.crlite_filters.enabled"; "security.remote_settings.crlite_filters.enabled";
@ -27,6 +24,11 @@ const INTERMEDIATES_ENABLED_PREF =
const INTERMEDIATES_DL_PER_POLL_PREF = const INTERMEDIATES_DL_PER_POLL_PREF =
"security.remote_settings.intermediates.downloads_per_poll"; "security.remote_settings.intermediates.downloads_per_poll";
// crlite_enrollment_id.py test_crlite_filters/issuer.pem
const ISSUER_PEM_UID = "UbH9/ZAnjuqf79Xhah1mFOWo6ZvgQCgsdheWfjvVUM8=";
// crlite_enrollment_id.py test_crlite_filters/no-sct-issuer.pem
const NO_SCT_ISSUER_PEM_UID = "Myn7EasO1QikOtNmo/UZdh6snCAw0BOY6wgU8OsUeeY=";
function getHashCommon(aStr, useBase64) { function getHashCommon(aStr, useBase64) {
let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
Ci.nsICryptoHash Ci.nsICryptoHash
@ -98,6 +100,8 @@ async function syncAndDownload(filters, clear = true) {
parent: filter.type == "diff" ? filter.parent : undefined, parent: filter.type == "diff" ? filter.parent : undefined,
id: filter.id, id: filter.id,
coverage: filter.type == "full" ? filter.coverage : undefined, coverage: filter.type == "full" ? filter.coverage : undefined,
enrolledIssuers:
filter.type == "full" ? filter.enrolledIssuers : undefined,
}; };
await localDB.create(record); await localDB.create(record);
@ -357,15 +361,6 @@ add_task(async function test_crlite_filters_multiple_days() {
); );
}); });
function getCRLiteEnrollmentRecordFor(nsCert) {
let { subjectString, spkiHashString } = getSubjectAndSPKIHash(nsCert);
return {
subjectDN: btoa(subjectString),
pubKeyHash: spkiHashString,
crlite_enrolled: true,
};
}
add_task(async function test_crlite_confirm_revocations_mode() { add_task(async function test_crlite_confirm_revocations_mode() {
Services.prefs.setBoolPref(CRLITE_FILTERS_ENABLED_PREF, true); Services.prefs.setBoolPref(CRLITE_FILTERS_ENABLED_PREF, true);
Services.prefs.setIntPref( Services.prefs.setIntPref(
@ -382,20 +377,6 @@ add_task(async function test_crlite_confirm_revocations_mode() {
"test_crlite_filters/no-sct-issuer.pem" "test_crlite_filters/no-sct-issuer.pem"
); );
let crliteEnrollmentRecords = [
getCRLiteEnrollmentRecordFor(issuerCert),
getCRLiteEnrollmentRecordFor(noSCTCertIssuer),
];
await IntermediatePreloadsClient.onSync({
data: {
current: crliteEnrollmentRecords,
created: crliteEnrollmentRecords,
updated: [],
deleted: [],
},
});
let result = await syncAndDownload([ let result = await syncAndDownload([
{ {
timestamp: "2020-10-17T00:00:00Z", timestamp: "2020-10-17T00:00:00Z",
@ -413,6 +394,7 @@ add_task(async function test_crlite_confirm_revocations_mode() {
maxTimestamp: 9999999999999, maxTimestamp: 9999999999999,
}, },
], ],
enrolledIssuers: [ISSUER_PEM_UID, NO_SCT_ISSUER_PEM_UID],
}, },
]); ]);
equal( equal(
@ -484,20 +466,6 @@ add_task(async function test_crlite_filters_and_check_revocation() {
"test_crlite_filters/no-sct-issuer.pem" "test_crlite_filters/no-sct-issuer.pem"
); );
let crliteEnrollmentRecords = [
getCRLiteEnrollmentRecordFor(issuerCert),
getCRLiteEnrollmentRecordFor(noSCTCertIssuer),
];
await IntermediatePreloadsClient.onSync({
data: {
current: crliteEnrollmentRecords,
created: crliteEnrollmentRecords,
updated: [],
deleted: [],
},
});
let result = await syncAndDownload([ let result = await syncAndDownload([
{ {
timestamp: "2020-10-17T00:00:00Z", timestamp: "2020-10-17T00:00:00Z",
@ -515,6 +483,7 @@ add_task(async function test_crlite_filters_and_check_revocation() {
maxTimestamp: 9999999999999, maxTimestamp: 9999999999999,
}, },
], ],
enrolledIssuers: [ISSUER_PEM_UID, NO_SCT_ISSUER_PEM_UID],
}, },
]); ]);
equal( equal(
@ -723,6 +692,7 @@ add_task(async function test_crlite_filters_and_check_revocation() {
maxTimestamp: 9999999999999, maxTimestamp: 9999999999999,
}, },
], ],
enrolledIssuers: [ISSUER_PEM_UID, NO_SCT_ISSUER_PEM_UID],
}, },
]); ]);
equal( equal(
@ -757,6 +727,7 @@ add_task(async function test_crlite_filters_avoid_reprocessing_filters() {
maxTimestamp: 9999999999999, maxTimestamp: 9999999999999,
}, },
], ],
enrolledIssuers: [ISSUER_PEM_UID, NO_SCT_ISSUER_PEM_UID],
}, },
{ {
timestamp: "2019-01-01T06:00:00Z", timestamp: "2019-01-01T06:00:00Z",

Просмотреть файл

@ -67,10 +67,13 @@ add_task(async function test_preexisting_crlite_data() {
Ci.nsICertStorage Ci.nsICertStorage
); );
await new Promise(resolve => { await new Promise(resolve => {
certStorage.hasPriorData(Ci.nsICertStorage.DATA_TYPE_CRLITE, (rv, _) => { certStorage.hasPriorData(
Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed"); Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_INCREMENTAL,
resolve(); (rv, _) => {
}); Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed");
resolve();
}
);
}); });
await checkCertErrorGenericAtTime( await checkCertErrorGenericAtTime(
certdb, certdb,
@ -153,8 +156,12 @@ function run_test() {
stashFile.copyTo(securityStateDirectory, "crlite.stash"); stashFile.copyTo(securityStateDirectory, "crlite.stash");
let coverageFile = do_get_file("test_crlite_preexisting/crlite.coverage"); let coverageFile = do_get_file("test_crlite_preexisting/crlite.coverage");
coverageFile.copyTo(securityStateDirectory, "crlite.coverage"); coverageFile.copyTo(securityStateDirectory, "crlite.coverage");
let certStorageFile = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollmentFile = do_get_file("test_crlite_preexisting/crlite.enrollment");
certStorageFile.copyTo(securityStateDirectory, "data.safe.bin"); enrollmentFile.copyTo(securityStateDirectory, "crlite.enrollment");
let certStorageFile = do_get_file(
"test_crlite_preexisting/crlite.enrollment"
);
certStorageFile.copyTo(securityStateDirectory, "crlite.enrollment");
run_next_test(); run_next_test();
} }

Просмотреть файл

@ -0,0 +1 @@
3)ű«Ő¤:ÓfŁőv¬ś 0Đ<13>ëđëyćQ±ýý<C3BD>'ŽęźďŐájfĺ¨é›ŕ@(,v–~;ŐPĎ

Двоичный файл не отображается.

Просмотреть файл

@ -20,8 +20,8 @@ add_task(async function test_crlite_stash_corrupted() {
let coverage = do_get_file("test_crlite_preexisting/crlite.coverage"); let coverage = do_get_file("test_crlite_preexisting/crlite.coverage");
coverage.copyTo(securityStateDirectory, "crlite.coverage"); coverage.copyTo(securityStateDirectory, "crlite.coverage");
let enrollment = do_get_file("test_crlite_preexisting/data.safe.bin"); let enrollment = do_get_file("test_crlite_preexisting/crlite.enrollment");
enrollment.copyTo(securityStateDirectory, "data.safe.bin"); enrollment.copyTo(securityStateDirectory, "crlite.enrollment");
let filter = do_get_file("test_crlite_filters/20201017-0-filter"); let filter = do_get_file("test_crlite_filters/20201017-0-filter");
filter.copyTo(securityStateDirectory, "crlite.filter"); filter.copyTo(securityStateDirectory, "crlite.filter");
@ -39,14 +39,17 @@ add_task(async function test_crlite_stash_corrupted() {
// Await a task that ensures the stash loading task has completed. // Await a task that ensures the stash loading task has completed.
await new Promise(resolve => { await new Promise(resolve => {
certStorage.hasPriorData(Ci.nsICertStorage.DATA_TYPE_CRLITE, (rv, _) => { certStorage.hasPriorData(
Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed"); Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_INCREMENTAL,
resolve(); (rv, _) => {
}); Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed");
resolve();
}
);
}); });
// This certificate is revoked according to `test_crlite_filters/20201017-0-filter`. // This certificate is revoked according to `test_crlite_filters/20201017-0-filter`.
// Its issuer is enrolled according to `test_crlite_preexisting/data.safe.bin`, // Its issuer is enrolled according to `test_crlite_preexisting/crlite.enrollment`,
// and it is covered according to `test_crlite_preexisting/crlite.coverage`. // and it is covered according to `test_crlite_preexisting/crlite.coverage`.
let revokedCert = constructCertFromFile("test_crlite_filters/revoked.pem"); let revokedCert = constructCertFromFile("test_crlite_filters/revoked.pem");
@ -67,17 +70,6 @@ add_task(async function test_crlite_stash_corrupted() {
0 0
); );
let hasDB = await new Promise(resolve => {
certStorage.hasPriorData(
Ci.nsICertStorage.DATA_TYPE_CRLITE,
(rv, result) => {
Assert.equal(rv, Cr.NS_OK, "hasPriorData should succeed");
resolve(result);
}
);
});
Assert.equal(hasDB, true, "CRLite should have a database");
let hasFilter = await new Promise(resolve => { let hasFilter = await new Promise(resolve => {
certStorage.hasPriorData( certStorage.hasPriorData(
Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_FULL, Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_FULL,

Просмотреть файл

@ -299,15 +299,6 @@ add_task(async function test_preload_basic() {
let intermediateDERBytes = atob(pemToBase64(intermediateBytes)); let intermediateDERBytes = atob(pemToBase64(intermediateBytes));
let intermediateCert = new X509.Certificate(); let intermediateCert = new X509.Certificate();
intermediateCert.parse(stringToArray(intermediateDERBytes)); intermediateCert.parse(stringToArray(intermediateDERBytes));
let crliteStateBefore = certStorage.getCRLiteState(
intermediateCert.tbsCertificate.subject._der._bytes,
intermediateCert.tbsCertificate.subjectPublicKeyInfo._der._bytes
);
equal(
crliteStateBefore,
Ci.nsICertStorage.STATE_UNSET,
"crlite state should be unset before"
);
const result = await syncAndDownload(["int.pem", "int2.pem"]); const result = await syncAndDownload(["int.pem", "int2.pem"]);
equal(result, "success", "Preloading update should have run"); equal(result, "success", "Preloading update should have run");
@ -366,16 +357,6 @@ add_task(async function test_preload_basic() {
}, },
}); });
let crliteStateAfter = certStorage.getCRLiteState(
intermediateCert.tbsCertificate.subject._der._bytes,
intermediateCert.tbsCertificate.subjectPublicKeyInfo._der._bytes
);
equal(
crliteStateAfter,
Ci.nsICertStorage.STATE_ENFORCE,
"crlite state should be set after"
);
// check that ee cert 2 does not verify - since we don't know the issuer of // check that ee cert 2 does not verify - since we don't know the issuer of
// this certificate // this certificate
await checkCertErrorGeneric( await checkCertErrorGeneric(

Просмотреть файл

@ -106,6 +106,8 @@ tags = remote-settings psm
[test_crlite_preexisting.js] [test_crlite_preexisting.js]
[test_crlite_filter_corrupted.js] [test_crlite_filter_corrupted.js]
[test_crlite_stash_corrupted.js] [test_crlite_stash_corrupted.js]
[test_crlite_enrollment_version.js]
[test_crlite_enrollment_trunc1.js]
[test_crlite_coverage_version.js] [test_crlite_coverage_version.js]
[test_crlite_coverage_trunc1.js] [test_crlite_coverage_trunc1.js]
[test_crlite_coverage_trunc2.js] [test_crlite_coverage_trunc2.js]