From 3d8cb88d00ecaecff0c3c7368fdb03abb715a349 Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 11:43:29 -0500 Subject: [PATCH 1/9] adjust spec_version to proper semver format Signed-off-by: Luke Steensen --- tuf/src/interchange/cjson/shims.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tuf/src/interchange/cjson/shims.rs b/tuf/src/interchange/cjson/shims.rs index 9b125e9..d6fa7e6 100644 --- a/tuf/src/interchange/cjson/shims.rs +++ b/tuf/src/interchange/cjson/shims.rs @@ -8,7 +8,15 @@ use crate::error::Error; use crate::metadata::{self, Metadata}; use crate::Result; -const SPEC_VERSION: &str = "1.0"; +const SPEC_VERSION: &str = "1.0.0"; + +// Ensure the given spec version matches our spec version. +// +// We also need to handle the literal "1.0" here, despite that fact that it is not a valid version +// according to the SemVer spec, because it is already baked into some of the old roots. +fn valid_spec_version(other: &str) -> bool { + other == SPEC_VERSION || other == "1.0" +} fn parse_datetime(ts: &str) -> Result> { Utc.datetime_from_str(ts, "%FT%TZ") @@ -70,7 +78,7 @@ impl RootMetadata { ))); } - if self.spec_version != SPEC_VERSION { + if !valid_spec_version(&self.spec_version) { return Err(Error::Encoding(format!( "Unknown spec version {}", self.spec_version @@ -184,7 +192,7 @@ impl TimestampMetadata { ))); } - if self.spec_version != SPEC_VERSION { + if !valid_spec_version(&self.spec_version) { return Err(Error::Encoding(format!( "Unknown spec version {}", self.spec_version @@ -233,7 +241,7 @@ impl SnapshotMetadata { ))); } - if self.spec_version != SPEC_VERSION { + if !valid_spec_version(&self.spec_version) { return Err(Error::Encoding(format!( "Unknown spec version {}", self.spec_version @@ -299,7 +307,7 @@ impl TargetsMetadata { ))); } - if self.spec_version != SPEC_VERSION { + if !valid_spec_version(&self.spec_version) { return Err(Error::Encoding(format!( "Unknown spec version {}", self.spec_version From 4b48acf7612fdc15cac1787958b72aa9837f2a41 Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 11:45:03 -0500 Subject: [PATCH 2/9] use RFC3339 functions for datetime handling Signed-off-by: Luke Steensen --- tuf/src/interchange/cjson/shims.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tuf/src/interchange/cjson/shims.rs b/tuf/src/interchange/cjson/shims.rs index d6fa7e6..8327f45 100644 --- a/tuf/src/interchange/cjson/shims.rs +++ b/tuf/src/interchange/cjson/shims.rs @@ -19,20 +19,13 @@ fn valid_spec_version(other: &str) -> bool { } fn parse_datetime(ts: &str) -> Result> { - Utc.datetime_from_str(ts, "%FT%TZ") + DateTime::parse_from_rfc3339(ts) + .map(|ts| ts.with_timezone(&Utc)) .map_err(|e| Error::Encoding(format!("Can't parse DateTime: {:?}", e))) } fn format_datetime(ts: &DateTime) -> String { - format!( - "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", - ts.year(), - ts.month(), - ts.day(), - ts.hour(), - ts.minute(), - ts.second() - ) + ts.to_rfc3339() } #[derive(Debug, Serialize, Deserialize)] From 70283a78ef6b62e9e72eb0e33ef03cc5c9f7451c Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 11:47:58 -0500 Subject: [PATCH 3/9] add Send bound to repository types Signed-off-by: Luke Steensen --- tuf/src/interchange/mod.rs | 2 +- tuf/src/repository.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tuf/src/interchange/mod.rs b/tuf/src/interchange/mod.rs index e295461..f69b0e5 100644 --- a/tuf/src/interchange/mod.rs +++ b/tuf/src/interchange/mod.rs @@ -10,7 +10,7 @@ use std::fmt::Debug; use crate::Result; /// The format used for data interchange, serialization, and deserialization. -pub trait DataInterchange: Debug + PartialEq + Clone { +pub trait DataInterchange: Debug + PartialEq + Clone + Send { /// The type of data that is contained in the `signed` portion of metadata. type RawData: Serialize + DeserializeOwned + Clone + PartialEq; diff --git a/tuf/src/repository.rs b/tuf/src/repository.rs index a23f1bc..3b314a3 100644 --- a/tuf/src/repository.rs +++ b/tuf/src/repository.rs @@ -108,7 +108,7 @@ where /// A writable TUF repository. Most implementors of this trait should also implement /// `RepositoryProvider`. -pub trait RepositoryStorage +pub trait RepositoryStorage: Send where D: DataInterchange + Sync, { From c38603b7a22e6ac203e87c76d648cb5f8e97d11d Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 11:48:35 -0500 Subject: [PATCH 4/9] don't consider asterisks illegal in paths Signed-off-by: Luke Steensen --- tuf/src/metadata.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tuf/src/metadata.rs b/tuf/src/metadata.rs index d8aa7e3..d8550fb 100644 --- a/tuf/src/metadata.rs +++ b/tuf/src/metadata.rs @@ -66,7 +66,6 @@ static PATH_ILLEGAL_STRINGS: &[&str] = &[ "\"", "|", "?", - "*", // control characters, all illegal in FAT "\u{000}", "\u{001}", From c1313e133aca9229542b3005cdba16c0b67b65e7 Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 15:30:48 -0500 Subject: [PATCH 5/9] back out datetime formatting change since it doesn't actually match spec Signed-off-by: Luke Steensen --- tuf/src/interchange/cjson/shims.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tuf/src/interchange/cjson/shims.rs b/tuf/src/interchange/cjson/shims.rs index 8327f45..63e6f14 100644 --- a/tuf/src/interchange/cjson/shims.rs +++ b/tuf/src/interchange/cjson/shims.rs @@ -25,7 +25,15 @@ fn parse_datetime(ts: &str) -> Result> { } fn format_datetime(ts: &DateTime) -> String { - ts.to_rfc3339() + format!( + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", + ts.year(), + ts.month(), + ts.day(), + ts.hour(), + ts.minute(), + ts.second() + ) } #[derive(Debug, Serialize, Deserialize)] From 4dfc36e2a31cfa35d8701945a9fb04839281919a Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 15:31:28 -0500 Subject: [PATCH 6/9] add tests for datetime parsing, spec_version, and path asterisks Signed-off-by: Luke Steensen --- tuf/src/interchange/cjson/shims.rs | 48 ++++++++++++++++++++++++++++++ tuf/src/metadata.rs | 18 +++++++++++ 2 files changed, 66 insertions(+) diff --git a/tuf/src/interchange/cjson/shims.rs b/tuf/src/interchange/cjson/shims.rs index 63e6f14..f50d3fb 100644 --- a/tuf/src/interchange/cjson/shims.rs +++ b/tuf/src/interchange/cjson/shims.rs @@ -579,3 +579,51 @@ mod deserialize_reject_duplicates { }) } } + +#[cfg(test)] +mod test { + use super::{parse_datetime, valid_spec_version}; + + #[test] + fn spec_version_validation() { + let valid_spec_versions = ["1.0.0", "1.0"]; + + for version in valid_spec_versions { + assert!(valid_spec_version(version), "{:?} should be valid", version); + } + + let invalid_spec_versions = ["1.0.1", "1.1.0", "2.0.0", "3.0"]; + + for version in invalid_spec_versions { + assert!( + !valid_spec_version(version), + "{:?} should be invalid", + version + ); + } + } + + #[test] + fn datetime_formats() { + // The TUF spec says datetimes should be in ISO8601 format, specifically + // "YYYY-MM-DDTHH:MM:SSZ". Since not all TUF clients adhere strictly to that, we choose to + // be more lenient here. The following represent the intersection of valid ISO8601 + // and RFC3339 datetime formats (source: https://ijmacd.github.io/rfc3339-iso8601/). + let valid_formats = [ + "2022-08-30T19:53:55Z", + "2022-08-30T19:53:55.7Z", + "2022-08-30T19:53:55.77Z", + "2022-08-30T19:53:55.775Z", + "2022-08-30T19:53:55+00:00", + "2022-08-30T19:53:55.7+00:00", + "2022-08-30T14:53:55-05:00", + "2022-08-30T14:53:55.7-05:00", + "2022-08-30T14:53:55.77-05:00", + "2022-08-30T14:53:55.775-05:00", + ]; + + for format in valid_formats { + assert!(parse_datetime(format).is_ok(), "should parse {:?}", format); + } + } +} diff --git a/tuf/src/metadata.rs b/tuf/src/metadata.rs index d8550fb..2f510c8 100644 --- a/tuf/src/metadata.rs +++ b/tuf/src/metadata.rs @@ -2313,6 +2313,24 @@ mod test { } } + #[test] + fn allow_asterisk_in_target_path() { + let good_paths = &[ + "*", + "*/some/path", + "*/some/path/", + "some/*/path", + "some/*/path/*", + ]; + + for path in good_paths.iter() { + assert!(safe_path(path).is_ok()); + assert!(TargetPath::new(path.to_string()).is_ok()); + assert!(MetadataPath::new(path.to_string()).is_ok()); + assert!(TargetPath::new(path.to_string()).is_ok()); + } + } + #[test] fn path_matches_chain() { let test_cases: &[(bool, &str, &[&[&str]])] = &[ From 94ccc7ff72e6f69ce62ac9e9f7a450d9c5619d67 Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Tue, 30 Aug 2022 15:33:47 -0500 Subject: [PATCH 7/9] adjust existing tests for new spec_version Signed-off-by: Luke Steensen --- tuf/src/metadata.rs | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tuf/src/metadata.rs b/tuf/src/metadata.rs index 2f510c8..d403629 100644 --- a/tuf/src/metadata.rs +++ b/tuf/src/metadata.rs @@ -2479,7 +2479,7 @@ mod test { let jsn = json!({ "_type": "root", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "consistent_snapshot": true, @@ -2546,7 +2546,7 @@ mod test { fn jsn_root_metadata_without_keyid_hash_algos() -> serde_json::Value { json!({ "_type": "root", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "consistent_snapshot": false, @@ -2651,7 +2651,7 @@ mod test { let jsn = json!({ "signatures": [{ "keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a", - "sig": "c4ba838e0d3f783716393a4d691f568f840733ff488bb79ac68287e97e0b31d63fcef392dbc978e878c2103ba231905af634cc651d6f0e63a35782d051ac6e00" + "sig": "1f944e022d0b30c5a9ddc9c210026f396e18a17cc9a4ee92c339a8ee63357608dba8121847a825c3a5c84c1081435436bd784c8086c3103cdd1489e79cff2802" }], "signed": jsn_root_metadata_without_keyid_hash_algos() }); @@ -2676,11 +2676,11 @@ mod test { let jsn = json!({ "signatures": [{ "keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a", - "sig": "c4ba838e0d3f783716393a4d691f568f840733ff488bb79ac68287e97e0b31d63fcef392dbc978e878c2103ba231905af634cc651d6f0e63a35782d051ac6e00" + "sig": "1f944e022d0b30c5a9ddc9c210026f396e18a17cc9a4ee92c339a8ee63357608dba8121847a825c3a5c84c1081435436bd784c8086c3103cdd1489e79cff2802" }, { "keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a", - "sig": "c4ba838e0d3f783716393a4d691f568f840733ff488bb79ac68287e97e0b31d63fcef392dbc978e878c2103ba231905af634cc651d6f0e63a35782d051ac6e00" + "sig": "1f944e022d0b30c5a9ddc9c210026f396e18a17cc9a4ee92c339a8ee63357608dba8121847a825c3a5c84c1081435436bd784c8086c3103cdd1489e79cff2802" }], "signed": jsn_root_metadata_without_keyid_hash_algos() }); @@ -2688,6 +2688,7 @@ mod test { let decoded: SignedMetadata = serde_json::from_value(jsn).unwrap(); let raw_root = decoded.to_raw().unwrap(); + assert_matches!( verify_signatures(&MetadataPath::root(), &raw_root, 2, &[root_key.public().clone()]), Err(Error::MetadataMissingSignatures { @@ -2825,7 +2826,7 @@ mod test { let jsn = json!({ "_type": "timestamp", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -2857,7 +2858,7 @@ mod test { let jsn = json!({ "_type": "timestamp", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -2877,7 +2878,7 @@ mod test { fn serde_timestamp_metadata_missing_snapshot() { let jsn = json!({ "_type": "timestamp", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": {} @@ -2893,7 +2894,7 @@ mod test { fn serde_timestamp_metadata_extra_metadata() { let jsn = json!({ "_type": "timestamp", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -2939,7 +2940,7 @@ mod test { let jsn = json!({ "_type": "snapshot", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -2973,7 +2974,7 @@ mod test { let jsn = json!({ "_type": "snapshot", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -3034,7 +3035,7 @@ mod test { let jsn = json!({ "_type": "targets", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "targets": { @@ -3104,7 +3105,7 @@ mod test { let jsn = json!({ "_type": "targets", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "targets": {}, @@ -3162,14 +3163,14 @@ mod test { "signatures": [ { "keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a", - "sig": "ea48ddc7b3ea614b394e508eb8722100f94ff1a4e3aac3af09d\ - a0dada4f878431e8ac26160833405ec239924dfe62edf605fee8294\ - c49b4acade55c76e817602", + "sig": "a9b97b2439cd41e9a8c62e4d2f8f73b25a06095e0a994e8631a0\ + 88977271909af2cc829c68637af98b07ebffeea308cc1a1c83d18fa2\ + 9ec401493973b3dfa90e", } ], "signed": { "_type": "snapshot", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -3329,7 +3330,7 @@ mod test { fn deserialize_json_root_duplicate_keys() { let root_json = r#"{ "_type": "root", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "consistent_snapshot": false, @@ -3515,7 +3516,7 @@ mod test { fn deserialize_json_snapshot_duplicate_metadata() { let snapshot_json = r#"{ "_type": "snapshot", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { @@ -3580,7 +3581,7 @@ mod test { fn deserialize_json_timestamp_duplicate_metadata() { let timestamp_json = r#"{ "_type": "timestamp", - "spec_version": "1.0", + "spec_version": "1.0.0", "version": 1, "expires": "2017-01-01T00:00:00Z", "meta": { From f941bab771dbd0ae0fb2873bd32bac580a96f581 Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Thu, 1 Sep 2022 16:21:40 -0500 Subject: [PATCH 8/9] collect additional fields on TargetsMetadata Signed-off-by: Luke Steensen --- tuf/src/interchange/cjson/shims.rs | 8 ++++++++ tuf/src/metadata.rs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/tuf/src/interchange/cjson/shims.rs b/tuf/src/interchange/cjson/shims.rs index f50d3fb..71780b9 100644 --- a/tuf/src/interchange/cjson/shims.rs +++ b/tuf/src/interchange/cjson/shims.rs @@ -282,6 +282,8 @@ pub struct TargetsMetadata { targets: BTreeMap, #[serde(default, skip_serializing_if = "metadata::Delegations::is_empty")] delegations: metadata::Delegations, + #[serde(flatten)] + additional_fields: BTreeMap, } impl TargetsMetadata { @@ -297,6 +299,11 @@ impl TargetsMetadata { .map(|(p, d)| (p.clone(), d.clone())) .collect(), delegations: metadata.delegations().clone(), + additional_fields: metadata + .additional_fields() + .iter() + .map(|(p, d)| (p.clone(), d.clone())) + .collect(), }) } @@ -320,6 +327,7 @@ impl TargetsMetadata { parse_datetime(&self.expires)?, self.targets.into_iter().collect(), self.delegations, + self.additional_fields.into_iter().collect(), ) } } diff --git a/tuf/src/metadata.rs b/tuf/src/metadata.rs index d403629..3d835e5 100644 --- a/tuf/src/metadata.rs +++ b/tuf/src/metadata.rs @@ -1841,6 +1841,7 @@ pub struct TargetsMetadata { expires: DateTime, targets: HashMap, delegations: Delegations, + additional_fields: HashMap, } impl TargetsMetadata { @@ -1850,6 +1851,7 @@ impl TargetsMetadata { expires: DateTime, targets: HashMap, delegations: Delegations, + additional_fields: HashMap, ) -> Result { if version < 1 { return Err(Error::IllegalArgument(format!( @@ -1863,6 +1865,7 @@ impl TargetsMetadata { expires, targets, delegations, + additional_fields, }) } @@ -1875,6 +1878,11 @@ impl TargetsMetadata { pub fn delegations(&self) -> &Delegations { &self.delegations } + + /// An immutable reference to any additional fields on the metadata. + pub fn additional_fields(&self) -> &HashMap { + &self.additional_fields + } } impl Metadata for TargetsMetadata { @@ -1991,6 +1999,7 @@ impl TargetsMetadataBuilder { self.expires, self.targets, self.delegations.unwrap_or_default(), + Default::default(), ) } @@ -3260,6 +3269,7 @@ mod test { Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!(), Delegations::default(), + Default::default(), ) .unwrap(); From f0d6ca38002ba3e112427b13e4d72387fb49e6c4 Mon Sep 17 00:00:00 2001 From: Luke Steensen Date: Fri, 2 Sep 2022 13:54:24 -0500 Subject: [PATCH 9/9] add additional_fields handling to root, timestamp, and snapshot metas Signed-off-by: Luke Steensen --- tuf/src/interchange/cjson/shims.rs | 12 +++++++++++ tuf/src/metadata.rs | 34 ++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/tuf/src/interchange/cjson/shims.rs b/tuf/src/interchange/cjson/shims.rs index 71780b9..f9a502d 100644 --- a/tuf/src/interchange/cjson/shims.rs +++ b/tuf/src/interchange/cjson/shims.rs @@ -47,6 +47,8 @@ pub struct RootMetadata { #[serde(deserialize_with = "deserialize_reject_duplicates::deserialize")] keys: BTreeMap, roles: RoleDefinitions, + #[serde(flatten)] + additional_fields: BTreeMap, } impl RootMetadata { @@ -68,6 +70,7 @@ impl RootMetadata { targets: meta.targets().clone(), timestamp: meta.timestamp().clone(), }, + additional_fields: meta.additional_fields().clone().into_iter().collect(), }) } @@ -104,6 +107,7 @@ impl RootMetadata { self.roles.snapshot, self.roles.targets, self.roles.timestamp, + self.additional_fields.into_iter().collect(), ) } } @@ -163,6 +167,8 @@ pub struct TimestampMetadata { version: u32, expires: String, meta: TimestampMeta, + #[serde(flatten)] + additional_fields: BTreeMap, } #[derive(Serialize, Deserialize)] @@ -182,6 +188,7 @@ impl TimestampMetadata { meta: TimestampMeta { snapshot: metadata.snapshot().clone(), }, + additional_fields: metadata.additional_fields().clone().into_iter().collect(), }) } @@ -204,6 +211,7 @@ impl TimestampMetadata { self.version, parse_datetime(&self.expires)?, self.meta.snapshot, + self.additional_fields.into_iter().collect(), ) } } @@ -217,6 +225,8 @@ pub struct SnapshotMetadata { expires: String, #[serde(deserialize_with = "deserialize_reject_duplicates::deserialize")] meta: BTreeMap, + #[serde(flatten)] + additional_fields: BTreeMap, } impl SnapshotMetadata { @@ -231,6 +241,7 @@ impl SnapshotMetadata { .iter() .map(|(p, d)| (format!("{}.json", p), d.clone())) .collect(), + additional_fields: metadata.additional_fields().clone().into_iter().collect(), }) } @@ -268,6 +279,7 @@ impl SnapshotMetadata { Ok((p, d)) }) .collect::>()?, + self.additional_fields.into_iter().collect(), ) } } diff --git a/tuf/src/metadata.rs b/tuf/src/metadata.rs index 3d835e5..df82a2e 100644 --- a/tuf/src/metadata.rs +++ b/tuf/src/metadata.rs @@ -718,6 +718,7 @@ impl RootMetadataBuilder { RoleDefinition::new(self.snapshot_threshold, self.snapshot_key_ids)?, RoleDefinition::new(self.targets_threshold, self.targets_key_ids)?, RoleDefinition::new(self.timestamp_threshold, self.timestamp_key_ids)?, + Default::default(), ) } @@ -766,6 +767,7 @@ pub struct RootMetadata { snapshot: RoleDefinition, targets: RoleDefinition, timestamp: RoleDefinition, + additional_fields: HashMap, } impl RootMetadata { @@ -779,6 +781,7 @@ impl RootMetadata { snapshot: RoleDefinition, targets: RoleDefinition, timestamp: RoleDefinition, + additional_fields: HashMap, ) -> Result { if version < 1 { return Err(Error::IllegalArgument(format!( @@ -796,6 +799,7 @@ impl RootMetadata { snapshot, targets, timestamp, + additional_fields, }) } @@ -861,6 +865,11 @@ impl RootMetadata { pub fn timestamp(&self) -> &RoleDefinition { &self.timestamp } + + /// An immutable reference to any additional fields on the metadata. + pub fn additional_fields(&self) -> &HashMap { + &self.additional_fields + } } impl Metadata for RootMetadata { @@ -1127,7 +1136,12 @@ impl TimestampMetadataBuilder { /// Construct a new `TimestampMetadata`. pub fn build(self) -> Result { - TimestampMetadata::new(self.version, self.expires, self.snapshot) + TimestampMetadata::new( + self.version, + self.expires, + self.snapshot, + Default::default(), + ) } /// Construct a new `SignedMetadata`. @@ -1148,6 +1162,7 @@ pub struct TimestampMetadata { version: u32, expires: DateTime, snapshot: MetadataDescription, + additional_fields: HashMap, } impl TimestampMetadata { @@ -1156,6 +1171,7 @@ impl TimestampMetadata { version: u32, expires: DateTime, snapshot: MetadataDescription, + additional_fields: HashMap, ) -> Result { if version < 1 { return Err(Error::IllegalArgument(format!( @@ -1168,6 +1184,7 @@ impl TimestampMetadata { version, expires, snapshot, + additional_fields, }) } @@ -1175,6 +1192,11 @@ impl TimestampMetadata { pub fn snapshot(&self) -> &MetadataDescription { &self.snapshot } + + /// An immutable reference to any additional fields on the metadata. + pub fn additional_fields(&self) -> &HashMap { + &self.additional_fields + } } impl Metadata for TimestampMetadata { @@ -1379,7 +1401,7 @@ impl SnapshotMetadataBuilder { /// Construct a new `SnapshotMetadata`. pub fn build(self) -> Result { - SnapshotMetadata::new(self.version, self.expires, self.meta) + SnapshotMetadata::new(self.version, self.expires, self.meta, Default::default()) } /// Construct a new `SignedMetadata`. @@ -1416,6 +1438,7 @@ pub struct SnapshotMetadata { version: u32, expires: DateTime, meta: HashMap, + additional_fields: HashMap, } impl SnapshotMetadata { @@ -1424,6 +1447,7 @@ impl SnapshotMetadata { version: u32, expires: DateTime, meta: HashMap, + additional_fields: HashMap, ) -> Result { if version < 1 { return Err(Error::IllegalArgument(format!( @@ -1436,6 +1460,7 @@ impl SnapshotMetadata { version, expires, meta, + additional_fields, }) } @@ -1443,6 +1468,11 @@ impl SnapshotMetadata { pub fn meta(&self) -> &HashMap { &self.meta } + + /// An immutable reference to any additional fields on the metadata. + pub fn additional_fields(&self) -> &HashMap { + &self.additional_fields + } } impl Metadata for SnapshotMetadata {