Skip to content

Commit

Permalink
Serialize records as expected by signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
leplatrem committed Dec 19, 2024
1 parent f8ead0e commit a9635cb
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 1 deletion.
16 changes: 15 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions components/remote_settings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ regex = "1.9"
anyhow = "1.0"
firefox-versioning = { path = "../support/firefox-versioning" }
sha2 = "^0.10"
canonical_json = "0.5"

[build-dependencies]
uniffi = { version = "0.28.2", features = ["build"] }
Expand Down
10 changes: 10 additions & 0 deletions components/remote_settings/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
)
}

fn verify_signature(&self) -> Result<()> {
let mut inner = self.inner.lock();
let collection_url = inner.api_client.collection_url();
let timestamp = inner.storage.get_last_modified_timestamp(&collection_url)?;
let records = inner.storage.get_records(&collection_url)?;
let metadata = inner.storage.get_collection_metadata(&collection_url)?;

Ok(())
}

/// Downloads an attachment from [attachment_location]. NOTE: there are no guarantees about a
/// maximum size, so use care when fetching potentially large attachments.
pub fn get_attachment(&self, record: RemoteSettingsRecord) -> Result<Vec<u8>> {
Expand Down
2 changes: 2 additions & 0 deletions components/remote_settings/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum Error {
DatabaseError(#[from] rusqlite::Error),
#[error("No attachment in given record: {0}")]
RecordAttachmentMismatchError(String),
#[error("data could not be serialized: {0}")]
SerializationError(#[from] canonical_json::CanonicalJSONError),
}

// Define how our internal errors are handled and converted to external errors
Expand Down
1 change: 1 addition & 0 deletions components/remote_settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod client;
pub mod config;
pub mod error;
pub mod service;
pub mod signatures;
pub mod storage;

#[cfg(feature = "jexl")]
Expand Down
66 changes: 66 additions & 0 deletions components/remote_settings/src/signatures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

use core::clone::Clone;

use crate::{
RemoteSettingsRecord,
Result,
};
use canonical_json;
use serde_json::{json, Value};


fn select_record_fields(value: &Value) -> Value {
if let Value::Object(map) = value {
let new_map = map
.iter()
.filter_map(|(key, v)| {
if key == "deleted" || key == "attachment" && v.is_null() {
None
} else {
Some((key.clone(), v.clone()))
}
})
.collect();
Value::Object(new_map)
} else {
value.clone() // Return the value as-is if it's not an object
}
}

fn serialize_data(timestamp: u64, records: Vec<RemoteSettingsRecord>) -> Result<Vec<u8>> {
let mut 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())
}

#[cfg(test)]
mod tests {
use crate::{RemoteSettingsRecord, Attachment};
use serde_json::json;
use super::serialize_data;

#[test]
fn test_records_canonicaljson_serialization() {
let bytes = serialize_data(1337, vec![RemoteSettingsRecord{last_modified: 42, id: "bonjour".into(), deleted: false, attachment: None, fields: json!({"foo": "bar"}).as_object().unwrap().clone()}]).unwrap();
let s = String::from_utf8(bytes).unwrap();
assert_eq!(s, "Content-Signature:\u{0}{\"data\":[{\"id\":\"bonjour\",\"last_modified\":42,\"foo\":\"bar\"}],\"last_modified\":\"1337\"}");
}

#[test]
fn test_records_canonicaljson_serialization_with_attachment() {
let bytes = serialize_data(1337, vec![RemoteSettingsRecord{last_modified: 42, id: "bonjour".into(), deleted: true, attachment: Some(Attachment {
filename: "pix.jpg".into(),
mimetype: "image/jpeg".into(),
location: "folder/file.jpg".into(),
hash: "aabbcc".into(),
size: 1234567,
}), fields: json!({}).as_object().unwrap().clone()}]).unwrap();
let s = String::from_utf8(bytes).unwrap();
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\"}");
}
}

0 comments on commit a9635cb

Please sign in to comment.