remote_settings/
signatures.rs
1use core::clone::Clone;
6
7use crate::{RemoteSettingsRecord, Result};
8use rc_crypto::contentsignature;
9use serde_json::{json, Value};
10
11fn select_record_fields(value: &Value) -> Value {
13 match value {
14 Value::Object(map) => Value::Object(
15 map.iter()
16 .filter(|(key, v)| !(*key == "deleted" || (*key == "attachment" && v.is_null())))
17 .map(|(key, v)| (key.clone(), v.clone()))
18 .collect(),
19 ),
20 _ => value.clone(), }
22}
23
24fn serialize_data(timestamp: u64, records: &[RemoteSettingsRecord]) -> Result<Vec<u8>> {
26 let mut sorted_records = records.to_vec();
27 sorted_records.sort_by_cached_key(|r| r.id.clone());
28 let serialized = canonical_json::to_string(&json!({
29 "data": sorted_records.into_iter().map(|r| select_record_fields(&json!(r))).collect::<Vec<Value>>(),
30 "last_modified": timestamp.to_string()
31 }))?;
32 let data = format!("Content-Signature:\x00{}", serialized);
33 Ok(data.as_bytes().to_vec())
34}
35
36pub fn verify_signature(
38 timestamp: u64,
39 records: &[RemoteSettingsRecord],
40 signature: &[u8],
41 cert_chain_bytes: &[u8],
42 epoch_seconds: u64,
43 expected_root_hash: &str,
44 expected_leaf_cname: &str,
45) -> Result<()> {
46 let message = serialize_data(timestamp, records)?;
47 contentsignature::verify(
50 &message,
51 signature,
52 cert_chain_bytes,
53 epoch_seconds,
54 expected_root_hash,
55 expected_leaf_cname,
56 )?;
57 Ok(())
58}
59
60#[cfg(test)]
61mod tests {
62 use super::serialize_data;
63 use crate::{Attachment, RemoteSettingsRecord};
64 use serde_json::json;
65
66 #[test]
67 fn test_records_canonicaljson_serialization() {
68 let bytes = serialize_data(
69 1337,
70 &vec![RemoteSettingsRecord {
71 last_modified: 42,
72 id: "bonjour".into(),
73 deleted: false,
74 attachment: None,
75 fields: json!({"foo": "bar"}).as_object().unwrap().clone(),
76 }],
77 )
78 .unwrap();
79 let s = String::from_utf8(bytes).unwrap();
80 assert_eq!(s, "Content-Signature:\u{0}{\"data\":[{\"id\":\"bonjour\",\"last_modified\":42,\"foo\":\"bar\"}],\"last_modified\":\"1337\"}");
81 }
82
83 #[test]
84 fn test_records_canonicaljson_serialization_with_attachment() {
85 let bytes = serialize_data(
86 1337,
87 &vec![RemoteSettingsRecord {
88 last_modified: 42,
89 id: "bonjour".into(),
90 deleted: true,
91 attachment: Some(Attachment {
92 filename: "pix.jpg".into(),
93 mimetype: "image/jpeg".into(),
94 location: "folder/file.jpg".into(),
95 hash: "aabbcc".into(),
96 size: 1234567,
97 }),
98 fields: json!({}).as_object().unwrap().clone(),
99 }],
100 )
101 .unwrap();
102 let s = String::from_utf8(bytes).unwrap();
103 assert_eq!(s, "Content-Signature:\0{\"data\":[{\"id\":\"bonjour\",\"last_modified\":42,\"attachment\":{\"filename\":\"pix.jpg\",\"mimetype\":\"image/jpeg\",\"location\":\"folder/file.jpg\",\"hash\":\"aabbcc\",\"size\":1234567}}],\"last_modified\":\"1337\"}");
104 }
105}