usecrate::{RemoteSettingsRecord, Result}; use rc_crypto::contentsignature; use serde_json::{json, Value};
/// Remove `deleted` and `attachment` fields if they are null. fn select_record_fields(value: &Value) -> Value { match value {
Value::Object(map) => Value::Object(
map.iter()
.filter(|(key, v)| !(*key == "deleted" || (*key == "attachment" && v.is_null())))
.map(|(key, v)| (key.clone(), v.clone()))
.collect(),
),
_ => value.clone(), // Return the value as-is if it's not an object
}
}
/// Serialize collection data into canonical JSON. This must match the server implementation. fn serialize_data(timestamp: u64, records: &[RemoteSettingsRecord]) -> Result<Vec<u8>> { letmut sorted_records = records.to_vec();
sorted_records.sort_by_cached_key(|r| r.id.clone()); let serialized = canonical_json::to_string(&json!({ "data": sorted_records.into_iter().map(|r| select_record_fields(&json!(r))).collect::<Vec<Value>>(), "last_modified": timestamp.to_string()
}))?; let data = format!("Content-Signature:\x00{}", serialized);
Ok(data.as_bytes().to_vec())
}
/// Verify that the timestamp and records match the signature in the metadata. pubfn verify_signature(
timestamp: u64,
records: &[RemoteSettingsRecord],
signature: &[u8],
cert_chain_bytes: &[u8],
epoch_seconds: u64,
expected_root_hash: &str,
expected_leaf_cname: &str,
) -> Result<()> { let message = serialize_data(timestamp, records)?; // Check that certificate chain is valid at specific date time, and // that signature matches the input message.
contentsignature::verify(
&message,
signature,
cert_chain_bytes,
epoch_seconds,
expected_root_hash,
expected_leaf_cname,
)?;
Ok(())
}
#[cfg(test)] mod tests { usesuper::serialize_data; usecrate::{Attachment, RemoteSettingsRecord}; use serde_json::json;