Merge branch 'tests' into develop
diff --git a/Cargo.toml b/Cargo.toml
index ef2a49e..473df2e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -47,5 +47,6 @@
uuid = { version = "0.5", features = [ "v4" ] }
[dev-dependencies]
+lazy_static = "0.2.8"
maplit = "0.1.4"
tempdir = "0.3"
diff --git a/src/client.rs b/src/client.rs
index 0d2c22d..da617e5 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -49,16 +49,8 @@
///
/// Returns `true` if an update occurred and `false` otherwise.
pub fn update_local(&mut self) -> Result<bool> {
- let r = Self::update_root(
- &mut self.tuf,
- &mut self.local,
- &self.config,
- )?;
- let ts = match Self::update_timestamp(
- &mut self.tuf,
- &mut self.local,
- &self.config,
- ) {
+ let r = Self::update_root(&mut self.tuf, &mut self.local, &self.config)?;
+ let ts = match Self::update_timestamp(&mut self.tuf, &mut self.local, &self.config) {
Ok(b) => b,
Err(e) => {
warn!(
@@ -68,11 +60,7 @@
false
}
};
- let sn = match Self::update_snapshot(
- &mut self.tuf,
- &mut self.local,
- &self.config,
- ) {
+ let sn = match Self::update_snapshot(&mut self.tuf, &mut self.local, &self.config) {
Ok(b) => b,
Err(e) => {
warn!(
@@ -82,11 +70,7 @@
false
}
};
- let ta = match Self::update_targets(
- &mut self.tuf,
- &mut self.local,
- &self.config,
- ) {
+ let ta = match Self::update_targets(&mut self.tuf, &mut self.local, &self.config) {
Ok(b) => b,
Err(e) => {
warn!(
@@ -104,36 +88,16 @@
///
/// Returns `true` if an update occurred and `false` otherwise.
pub fn update_remote(&mut self) -> Result<bool> {
- let r = Self::update_root(
- &mut self.tuf,
- &mut self.remote,
- &self.config,
- )?;
- let ts = Self::update_timestamp(
- &mut self.tuf,
- &mut self.remote,
- &self.config,
- )?;
- let sn = Self::update_snapshot(
- &mut self.tuf,
- &mut self.remote,
- &self.config,
- )?;
- let ta = Self::update_targets(
- &mut self.tuf,
- &mut self.remote,
- &self.config,
- )?;
+ let r = Self::update_root(&mut self.tuf, &mut self.remote, &self.config)?;
+ let ts = Self::update_timestamp(&mut self.tuf, &mut self.remote, &self.config)?;
+ let sn = Self::update_snapshot(&mut self.tuf, &mut self.remote, &self.config)?;
+ let ta = Self::update_targets(&mut self.tuf, &mut self.remote, &self.config)?;
Ok(r || ts || sn || ta)
}
/// Returns `true` if an update occurred and `false` otherwise.
- fn update_root<T>(
- tuf: &mut Tuf<D>,
- repo: &mut T,
- config: &Config,
- ) -> Result<bool>
+ fn update_root<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
where
T: Repository<D>,
{
@@ -184,11 +148,7 @@
}
/// Returns `true` if an update occurred and `false` otherwise.
- fn update_timestamp<T>(
- tuf: &mut Tuf<D>,
- repo: &mut T,
- config: &Config,
- ) -> Result<bool>
+ fn update_timestamp<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
where
T: Repository<D>,
{
@@ -209,16 +169,7 @@
T: Repository<D>,
{
let snapshot_description = match tuf.timestamp() {
- Some(ts) => {
- match ts.meta().get(&MetadataPath::from_role(&Role::Snapshot)) {
- Some(d) => Ok(d),
- None => Err(Error::VerificationFailure(
- "Timestamp metadata did not contain a description of the \
- current snapshot metadata."
- .into(),
- )),
- }
- }
+ Some(ts) => Ok(ts.snapshot()),
None => Err(Error::MissingMetadata(Role::Timestamp)),
}?
.clone();
@@ -228,7 +179,7 @@
}
let (alg, value) = crypto::hash_preference(snapshot_description.hashes())?;
-
+
let version = if tuf.root().consistent_snapshot() {
MetadataVersion::Hash(value.clone())
} else {
@@ -431,7 +382,13 @@
&signed_meta,
) {
Ok(_) => (),
- Err(e) => warn!("Error storing metadata {:?} locally: {:?}", delegation.role(), e),
+ Err(e) => {
+ warn!(
+ "Error storing metadata {:?} locally: {:?}",
+ delegation.role(),
+ e
+ )
+ }
}
let meta = tuf.delegations().get(delegation.role()).unwrap().clone();
@@ -590,3 +547,114 @@
}
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use chrono::prelude::*;
+ use crypto::{PrivateKey, SignatureScheme};
+ use interchange::JsonDataInterchange;
+ use metadata::{RootMetadata, SignedMetadata, RoleDefinition, MetadataPath, MetadataVersion};
+ use repository::EphemeralRepository;
+
+ lazy_static! {
+ static ref KEYS: Vec<PrivateKey> = {
+ let keys: &[&[u8]] = &[
+ include_bytes!("../tests/ed25519/ed25519-1.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-2.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-3.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-4.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-5.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-6.pk8.der"),
+ ];
+ keys.iter().map(|b| PrivateKey::from_pkcs8(b).unwrap()).collect()
+ };
+ }
+
+ #[test]
+ fn root_chain_update() {
+ let mut repo = EphemeralRepository::new();
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ repo.store_metadata(
+ &Role::Root,
+ &MetadataPath::from_role(&Role::Root),
+ &MetadataVersion::Number(1),
+ &root,
+ ).unwrap();
+
+ let tuf = Tuf::from_root(root).unwrap();
+
+ let root = RootMetadata::new(
+ 2,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[1].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ ).unwrap();
+ let mut root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ root.add_signature(&KEYS[0], SignatureScheme::Ed25519)
+ .unwrap();
+
+ repo.store_metadata(
+ &Role::Root,
+ &MetadataPath::from_role(&Role::Root),
+ &MetadataVersion::Number(2),
+ &root,
+ ).unwrap();
+
+ let root = RootMetadata::new(
+ 3,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[2].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ ).unwrap();
+ let mut root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ root.add_signature(&KEYS[1], SignatureScheme::Ed25519)
+ .unwrap();
+
+ repo.store_metadata(
+ &Role::Root,
+ &MetadataPath::from_role(&Role::Root),
+ &MetadataVersion::Number(3),
+ &root,
+ ).unwrap();
+ repo.store_metadata(
+ &Role::Root,
+ &MetadataPath::from_role(&Role::Root),
+ &MetadataVersion::None,
+ &root,
+ ).unwrap();
+
+ let mut client = Client::new(
+ tuf,
+ Config::build().finish().unwrap(),
+ repo,
+ EphemeralRepository::new(),
+ ).unwrap();
+ assert_eq!(client.update_local(), Ok(true));
+ assert_eq!(client.tuf.root().version(), 3);
+ }
+}
diff --git a/src/interchange/mod.rs b/src/interchange/mod.rs
index 3b01242..7635dba 100644
--- a/src/interchange/mod.rs
+++ b/src/interchange/mod.rs
@@ -14,7 +14,7 @@
/// The format used for data interchange, serialization, and deserialization.
pub trait DataInterchange: Debug + PartialEq + Clone {
/// The type of data that is contained in the `signed` portion of metadata.
- type RawData: Serialize + DeserializeOwned + Clone;
+ type RawData: Serialize + DeserializeOwned + Clone + PartialEq;
/// The data interchange's extension.
fn extension() -> &'static str;
diff --git a/src/lib.rs b/src/lib.rs
index 6243c25..cd23a47 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -70,6 +70,9 @@
extern crate derp;
extern crate hyper;
extern crate itoa;
+#[cfg(test)]
+#[macro_use]
+extern crate lazy_static;
#[macro_use]
extern crate log;
#[cfg(test)]
diff --git a/src/metadata.rs b/src/metadata.rs
index b2b4d0b..fef50a6 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -258,6 +258,11 @@
/// Append a signature to this signed metadata. Will overwrite signature by keys with the same
/// ID.
+ ///
+ /// **WARNING**: You should never have multiple TUF private keys on the same machine, so if
+ /// you're using this to append several signatures are once, you are doing something wrong. The
+ /// preferred method is to generate your copy of the metadata locally and use `merge_signatures`
+ /// to perform the "append" operations.
pub fn add_signature(
&mut self,
private_key: &PrivateKey,
@@ -273,6 +278,32 @@
Ok(())
}
+ /// Merge the singatures from `other` into `self` if and only if
+ /// `self.signed() == other.signed()`. If `self` and `other` contain signatures from the same
+ /// key ID, then the signatures from `self` will replace the signatures from `other`.
+ pub fn merge_signatures(&mut self, other: &Self) -> Result<()> {
+ if self.signed() != other.signed() {
+ return Err(Error::IllegalArgument(
+ "Attempted to merge unequal metadata".into(),
+ ));
+ }
+
+ let key_ids = self.signatures
+ .iter()
+ .map(|s| s.key_id().clone())
+ .collect::<HashSet<KeyId>>();
+
+ self.signatures.extend(
+ other
+ .signatures
+ .iter()
+ .filter(|s| !key_ids.contains(s.key_id()))
+ .cloned(),
+ );
+
+ Ok(())
+ }
+
/// An immutable reference to the signatures.
pub fn signatures(&self) -> &[Signature] {
&self.signatures
@@ -314,7 +345,7 @@
for sig in self.signatures.iter() {
if !authorized_key_ids.contains(sig.key_id()) {
warn!(
- "Key ID {:?} is not authorized to sign root metadata.",
+ "Key ID {:?} is not authorized to sign metadata.",
sig.key_id()
);
continue;
@@ -357,7 +388,7 @@
}
/// Metadata for the root role.
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
pub struct RootMetadata {
version: u32,
expires: DateTime<Utc>,
@@ -633,11 +664,11 @@
}
/// Metadata for the timestamp role.
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
pub struct TimestampMetadata {
version: u32,
expires: DateTime<Utc>,
- meta: HashMap<MetadataPath, MetadataDescription>,
+ snapshot: MetadataDescription,
}
impl TimestampMetadata {
@@ -645,7 +676,7 @@
pub fn new(
version: u32,
expires: DateTime<Utc>,
- meta: HashMap<MetadataPath, MetadataDescription>,
+ snapshot: MetadataDescription,
) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(format!(
@@ -657,7 +688,7 @@
Ok(TimestampMetadata {
version: version,
expires: expires,
- meta: meta,
+ snapshot: snapshot,
})
}
@@ -671,9 +702,9 @@
&self.expires
}
- /// An immutable reference to the metadata paths and descriptions.
- pub fn meta(&self) -> &HashMap<MetadataPath, MetadataDescription> {
- &self.meta
+ /// An immutable reference to the snapshot description.
+ pub fn snapshot(&self) -> &MetadataDescription {
+ &self.snapshot
}
}
@@ -1224,6 +1255,12 @@
return Err(Error::IllegalArgument("Cannot have threshold < 1".into()));
}
+ if (key_ids.len() as u64) < (threshold as u64) {
+ return Err(Error::IllegalArgument(
+ "Cannot have threshold less than number of keys".into(),
+ ));
+ }
+
Ok(Delegation {
role: role,
terminating: terminating,
@@ -1281,6 +1318,7 @@
mod test {
use super::*;
use chrono::prelude::*;
+ use data_encoding::BASE64URL;
use json;
use interchange::JsonDataInterchange;
@@ -1501,27 +1539,22 @@
let timestamp = TimestampMetadata::new(
1,
Utc.ymd(2017, 1, 1).and_hms(0, 0, 0),
- hashmap!{
- MetadataPath::new("foo".into()).unwrap() =>
- MetadataDescription::new(
- 1,
- 100,
- hashmap! { HashAlgorithm::Sha256 => HashValue::new(vec![]) }
- ).unwrap(),
- },
+ MetadataDescription::new(
+ 1,
+ 100,
+ hashmap! { HashAlgorithm::Sha256 => HashValue::new(vec![]) },
+ ).unwrap(),
).unwrap();
let jsn = json!({
"type": "timestamp",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
- "meta": {
- "foo": {
- "version": 1,
- "size": 100,
- "hashes": {
- "sha256": "",
- },
+ "snapshot": {
+ "version": 1,
+ "size": 100,
+ "hashes": {
+ "sha256": "",
},
},
});
@@ -1709,4 +1742,426 @@
json::from_value(encoded).unwrap();
assert_eq!(decoded, signed);
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // Here there be test cases about what metadata is allowed to be parsed wherein we do all sorts
+ // of naughty things and make sure the parsers puke appropriately.
+ // ______________
+ // ,===:'., `-._
+ // `:.`---.__ `-._
+ // `:. `--. `.
+ // \. `. `.
+ // (,,(, \. `. ____,-`.,
+ // (,' `/ \. ,--.___`.'
+ // , ,' ,--. `, \.;' `
+ // `{o, { \ : \;
+ // |,,' / / //
+ // j;; / ,' ,-//. ,---. ,
+ // \;' / ,' / _ \ / _ \ ,'/
+ // \ `' / \ `' / \ `.' /
+ // `.___,' `.__,' `.__,'
+ //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ fn make_root() -> json::Value {
+ let root_def = RoleDefinition::new(
+ 1,
+ hashset!(PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap().key_id().clone()),
+ ).unwrap();
+
+ let snapshot_def = RoleDefinition::new(
+ 1,
+ hashset!(PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap().key_id().clone()),
+ ).unwrap();
+
+ let targets_def = RoleDefinition::new(
+ 1,
+ hashset!(PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap().key_id().clone()),
+ ).unwrap();
+
+ let timestamp_def = RoleDefinition::new(
+ 1,
+ hashset!(PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap().key_id().clone()),
+ ).unwrap();
+
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec!(
+ PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap().public().clone(),
+ PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap().public().clone(),
+ PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap().public().clone(),
+ PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap().public().clone(),
+ ),
+ root_def,
+ snapshot_def,
+ targets_def,
+ timestamp_def,
+ ).unwrap();
+
+ json::to_value(&root).unwrap()
+ }
+
+ fn make_snapshot() -> json::Value {
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!())
+ .unwrap();
+
+ json::to_value(&snapshot).unwrap()
+ }
+
+ fn make_timestamp() -> json::Value {
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+
+ json::to_value(×tamp).unwrap()
+ }
+
+ fn make_targets() -> json::Value {
+ let targets =
+ TargetsMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!(), None)
+ .unwrap();
+
+ json::to_value(&targets).unwrap()
+ }
+
+ fn make_delegations() -> json::Value {
+ let key = PrivateKey::from_pkcs8(ED25519_1_PK8)
+ .unwrap()
+ .public()
+ .clone();
+ let delegations = Delegations::new(
+ vec![key.clone()],
+ vec![Delegation::new(
+ MetadataPath::new("foo".into()).unwrap(),
+ false,
+ 1,
+ hashset!(key.key_id().clone()),
+ hashset!(TargetPath::new("bar".into()).unwrap()),
+ ).unwrap()],
+ ).unwrap();
+
+ json::to_value(&delegations).unwrap()
+ }
+
+ fn make_delegation() -> json::Value {
+ let key = PrivateKey::from_pkcs8(ED25519_1_PK8)
+ .unwrap()
+ .public()
+ .clone();
+ let delegation = Delegation::new(
+ MetadataPath::new("foo".into()).unwrap(),
+ false,
+ 1,
+ hashset!(key.key_id().clone()),
+ hashset!(TargetPath::new("bar".into()).unwrap()),
+ ).unwrap();
+
+ json::to_value(&delegation).unwrap()
+ }
+
+ fn set_version(value: &mut json::Value, version: i64) {
+ match value.as_object_mut() {
+ Some(obj) => {
+ let _ = obj.insert("version".into(), json!(version));
+ }
+ None => panic!(),
+ }
+ }
+
+ // Refuse to deserialize root metadata if the version is not > 0
+ #[test]
+ fn deserialize_json_root_illegal_version() {
+ let mut root_json = make_root();
+ set_version(&mut root_json, 0);
+ assert!(json::from_value::<RootMetadata>(root_json.clone()).is_err());
+
+ let mut root_json = make_root();
+ set_version(&mut root_json, -1);
+ assert!(json::from_value::<RootMetadata>(root_json).is_err());
+ }
+
+ // Refuse to deserialize root metadata if any of the defined keys don't match their key ID
+ #[test]
+ fn deserialize_json_root_bad_key_ids() {
+ let mut root_json = make_root();
+ match root_json.as_object_mut() {
+ Some(obj) => {
+ match obj.get_mut("keys").unwrap().as_object_mut() {
+ Some(keys) => {
+ let key_id = keys.keys().next().unwrap().clone();
+ let key = keys.get(&key_id).unwrap().clone();
+ let mut bytes = BASE64URL.decode(key_id.as_bytes()).unwrap();
+ bytes[0] ^= 0x01;
+ let key_id = BASE64URL.encode(&bytes);
+ let _ = keys.insert(key_id, key);
+ }
+ None => panic!(),
+ }
+ }
+ None => panic!(),
+ }
+
+ assert!(json::from_value::<RootMetadata>(root_json).is_err());
+ }
+
+ fn set_threshold(value: &mut json::Value, threshold: i32) {
+ match value.as_object_mut() {
+ Some(obj) => {
+ let _ = obj.insert("threshold".into(), json!(threshold));
+ }
+ None => panic!(),
+ }
+ }
+
+ // Refuse to deserialize role definitions with illegal thresholds
+ #[test]
+ fn deserialize_json_role_definition_illegal_threshold() {
+ let role_def = RoleDefinition::new(
+ 1,
+ hashset!(PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap().key_id().clone()),
+ ).unwrap();
+
+ let mut jsn = json::to_value(&role_def).unwrap();
+ set_threshold(&mut jsn, 0);
+ assert!(json::from_value::<RoleDefinition>(jsn).is_err());
+
+ let mut jsn = json::to_value(&role_def).unwrap();
+ set_threshold(&mut jsn, -1);
+ assert!(json::from_value::<RoleDefinition>(jsn).is_err());
+
+ let role_def = RoleDefinition::new(
+ 2,
+ hashset!(
+ PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap().key_id().clone(),
+ PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap().key_id().clone(),
+ ),
+ ).unwrap();
+
+ let mut jsn = json::to_value(&role_def).unwrap();
+ set_threshold(&mut jsn, 3);
+ assert!(json::from_value::<RoleDefinition>(jsn).is_err());
+ }
+
+ // Refuse to deserialilze root metadata with wrong type field
+ #[test]
+ fn deserialize_json_root_bad_type() {
+ let mut root = make_root();
+ let _ = root.as_object_mut().unwrap().insert(
+ "type".into(),
+ json!("snapshot"),
+ );
+ assert!(json::from_value::<RootMetadata>(root).is_err());
+ }
+
+ // Refuse to deserialize role definitions with duplicated key ids
+ #[test]
+ fn deserialize_json_role_definition_duplicate_key_ids() {
+ let key_id = PrivateKey::from_pkcs8(ED25519_1_PK8)
+ .unwrap()
+ .key_id()
+ .clone();
+ let role_def = RoleDefinition::new(1, hashset!(key_id.clone())).unwrap();
+ let mut jsn = json::to_value(&role_def).unwrap();
+
+ match jsn.as_object_mut() {
+ Some(obj) => {
+ match obj.get_mut("key_ids").unwrap().as_array_mut() {
+ Some(arr) => arr.push(json!(key_id)),
+ None => panic!(),
+ }
+ }
+ None => panic!(),
+ }
+
+ assert!(json::from_value::<RoleDefinition>(jsn).is_err());
+ }
+
+ // Refuse to deserialize snapshot metadata with illegal versions
+ #[test]
+ fn deserialize_json_snapshot_illegal_version() {
+ let mut snapshot = make_snapshot();
+ set_version(&mut snapshot, 0);
+ assert!(json::from_value::<SnapshotMetadata>(snapshot).is_err());
+
+ let mut snapshot = make_snapshot();
+ set_version(&mut snapshot, -1);
+ assert!(json::from_value::<SnapshotMetadata>(snapshot).is_err());
+ }
+
+ // Refuse to deserialilze snapshot metadata with wrong type field
+ #[test]
+ fn deserialize_json_snapshot_bad_type() {
+ let mut snapshot = make_snapshot();
+ let _ = snapshot.as_object_mut().unwrap().insert(
+ "type".into(),
+ json!("root"),
+ );
+ assert!(json::from_value::<SnapshotMetadata>(snapshot).is_err());
+ }
+
+ // Refuse to deserialize timestamp metadata with illegal versions
+ #[test]
+ fn deserialize_json_timestamp_illegal_version() {
+ let mut timestamp = make_timestamp();
+ set_version(&mut timestamp, 0);
+ assert!(json::from_value::<TimestampMetadata>(timestamp).is_err());
+
+ let mut timestamp = make_timestamp();
+ set_version(&mut timestamp, -1);
+ assert!(json::from_value::<TimestampMetadata>(timestamp).is_err());
+ }
+
+ // Refuse to deserialilze timestamp metadata with wrong type field
+ #[test]
+ fn deserialize_json_timestamp_bad_type() {
+ let mut timestamp = make_timestamp();
+ let _ = timestamp.as_object_mut().unwrap().insert(
+ "type".into(),
+ json!("root"),
+ );
+ assert!(json::from_value::<TimestampMetadata>(timestamp).is_err());
+ }
+
+ // Refuse to deserialize targets metadata with illegal versions
+ #[test]
+ fn deserialize_json_targets_illegal_version() {
+ let mut targets = make_targets();
+ set_version(&mut targets, 0);
+ assert!(json::from_value::<TargetsMetadata>(targets).is_err());
+
+ let mut targets = make_targets();
+ set_version(&mut targets, -1);
+ assert!(json::from_value::<TargetsMetadata>(targets).is_err());
+ }
+
+ // Refuse to deserialilze targets metadata with wrong type field
+ #[test]
+ fn deserialize_json_targets_bad_type() {
+ let mut targets = make_targets();
+ let _ = targets.as_object_mut().unwrap().insert(
+ "type".into(),
+ json!("root"),
+ );
+ assert!(json::from_value::<TargetsMetadata>(targets).is_err());
+ }
+
+ // Refuse to deserialize delegations with no keys
+ #[test]
+ fn deserialize_json_delegations_no_keys() {
+ let mut delegations = make_delegations();
+ delegations
+ .as_object_mut()
+ .unwrap()
+ .get_mut("keys".into())
+ .unwrap()
+ .as_object_mut()
+ .unwrap()
+ .clear();
+ assert!(json::from_value::<Delegations>(delegations).is_err());
+ }
+
+ // Refuse to deserialize delegations with no roles
+ #[test]
+ fn deserialize_json_delegations_no_roles() {
+ let mut delegations = make_delegations();
+ delegations
+ .as_object_mut()
+ .unwrap()
+ .get_mut("roles".into())
+ .unwrap()
+ .as_array_mut()
+ .unwrap()
+ .clear();
+ assert!(json::from_value::<Delegations>(delegations).is_err());
+ }
+
+ // Refuse to deserialize delegations with duplicated roles
+ #[test]
+ fn deserialize_json_delegations_duplicated_roles() {
+ let mut delegations = make_delegations();
+ let dupe = delegations
+ .as_object()
+ .unwrap()
+ .get("roles".into())
+ .unwrap()
+ .as_array()
+ .unwrap()
+ [0]
+ .clone();
+ delegations
+ .as_object_mut()
+ .unwrap()
+ .get_mut("roles".into())
+ .unwrap()
+ .as_array_mut()
+ .unwrap()
+ .push(dupe);
+ assert!(json::from_value::<Delegations>(delegations).is_err());
+ }
+
+ // Refuse to deserialize a delegation with insufficient threshold
+ #[test]
+ fn deserialize_json_delegation_bad_threshold() {
+ let mut delegation = make_delegation();
+ set_threshold(&mut delegation, 0);
+ assert!(json::from_value::<Delegation>(delegation).is_err());
+
+ let mut delegation = make_delegation();
+ set_threshold(&mut delegation, 2);
+ assert!(json::from_value::<Delegation>(delegation).is_err());
+ }
+
+ // Refuse to deserialize a delegation with duplicate key IDs
+ #[test]
+ fn deserialize_json_delegation_duplicate_key_ids() {
+ let mut delegation = make_delegation();
+ let dupe = delegation
+ .as_object()
+ .unwrap()
+ .get("key_ids".into())
+ .unwrap()
+ .as_array()
+ .unwrap()
+ [0]
+ .clone();
+ delegation
+ .as_object_mut()
+ .unwrap()
+ .get_mut("key_ids".into())
+ .unwrap()
+ .as_array_mut()
+ .unwrap()
+ .push(dupe);
+ assert!(json::from_value::<Delegation>(delegation).is_err());
+ }
+
+ // Refuse to deserialize a delegation with duplicate paths
+ #[test]
+ fn deserialize_json_delegation_duplicate_paths() {
+ let mut delegation = make_delegation();
+ let dupe = delegation
+ .as_object()
+ .unwrap()
+ .get("paths".into())
+ .unwrap()
+ .as_array()
+ .unwrap()
+ [0]
+ .clone();
+ delegation
+ .as_object_mut()
+ .unwrap()
+ .get_mut("paths".into())
+ .unwrap()
+ .as_array_mut()
+ .unwrap()
+ .push(dupe);
+ assert!(json::from_value::<Delegation>(delegation).is_err());
+ }
}
diff --git a/src/shims.rs b/src/shims.rs
index 7f8d2cc..6a9541d 100644
--- a/src/shims.rs
+++ b/src/shims.rs
@@ -48,12 +48,12 @@
let mut keys = Vec::new();
for (key_id, value) in self.keys.drain() {
if &key_id != value.key_id() {
- warn!(
+ return Err(Error::Encoding(format!(
"Received key with ID {:?} but calculated it's value as {:?}. \
Refusing to add it to the set of trusted keys.",
key_id,
value.key_id()
- );
+ )));
} else {
debug!(
"Found key with good ID {:?}. Adding it to the set of trusted keys.",
@@ -140,7 +140,7 @@
typ: metadata::Role,
version: u32,
expires: DateTime<Utc>,
- meta: HashMap<metadata::MetadataPath, metadata::MetadataDescription>,
+ snapshot: metadata::MetadataDescription,
}
impl TimestampMetadata {
@@ -149,7 +149,7 @@
typ: metadata::Role::Timestamp,
version: metadata.version(),
expires: metadata.expires().clone(),
- meta: metadata.meta().clone(),
+ snapshot: metadata.snapshot().clone(),
})
}
@@ -161,7 +161,7 @@
)));
}
- metadata::TimestampMetadata::new(self.version, self.expires, self.meta)
+ metadata::TimestampMetadata::new(self.version, self.expires, self.snapshot)
}
}
diff --git a/src/tuf.rs b/src/tuf.rs
index 36793e9..f3af563 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -161,24 +161,15 @@
let snapshot = {
let root = self.safe_root_ref()?;
let timestamp = self.safe_timestamp_ref()?;
- let snapshot_description = timestamp
- .meta()
- .get(&MetadataPath::from_role(&Role::Snapshot))
- .ok_or_else(|| {
- Error::VerificationFailure(
- "Timestamp metadata had no description of the snapshot metadata".into(),
- )
- })?;
-
let current_version = self.snapshot.as_ref().map(|t| t.version()).unwrap_or(0);
- if snapshot_description.version() < current_version {
+ if timestamp.snapshot().version() < current_version {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back snapshot metadata at version {} to {}.",
current_version,
- snapshot_description.version()
+ timestamp.snapshot().version()
)));
- } else if snapshot_description.version() == current_version {
+ } else if timestamp.snapshot().version() == current_version {
return Ok(false);
}
@@ -190,18 +181,17 @@
let snapshot: SnapshotMetadata = D::deserialize(&signed_snapshot.signed())?;
- if snapshot.version() != snapshot_description.version() {
+ if snapshot.version() != timestamp.snapshot().version() {
return Err(Error::VerificationFailure(format!(
"The timestamp metadata reported that the snapshot metadata should be at \
version {} but version {} was found instead.",
- snapshot_description.version(),
+ timestamp.snapshot().version(),
snapshot.version()
)));
}
- if snapshot.expires() <= &Utc::now() {
- return Err(Error::ExpiredMetadata(Role::Snapshot));
- }
+ // Note: this doesn't check the expiration because we need to be able to update it
+ // regardless so we can prevent rollback attacks againsts targets/delegations.
snapshot
};
@@ -509,3 +499,494 @@
}
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use chrono::prelude::*;
+ use crypto::{PrivateKey, SignatureScheme, HashAlgorithm};
+ use interchange::JsonDataInterchange;
+ use metadata::{RoleDefinition, MetadataDescription};
+
+ lazy_static! {
+ static ref KEYS: Vec<PrivateKey> = {
+ let keys: &[&[u8]] = &[
+ include_bytes!("../tests/ed25519/ed25519-1.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-2.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-3.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-4.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-5.pk8.der"),
+ include_bytes!("../tests/ed25519/ed25519-6.pk8.der"),
+ ];
+ keys.iter().map(|b| PrivateKey::from_pkcs8(b).unwrap()).collect()
+ };
+ }
+
+ #[test]
+ fn root_pinned_success() {
+ let root_key = &KEYS[0];
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone()],
+ RoleDefinition::new(1, hashset!(root_key.key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &root_key, SignatureScheme::Ed25519).unwrap();
+
+ assert!(Tuf::from_root_pinned(root, &[root_key.key_id().clone()]).is_ok());
+ }
+
+ #[test]
+ fn root_pinned_failure() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ assert!(Tuf::from_root_pinned(root, &[KEYS[1].key_id().clone()]).is_err());
+ }
+
+ #[test]
+ fn good_root_rotation() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let root = RootMetadata::new(
+ 2,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[1].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ ).unwrap();
+ let mut root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ // add the original key's signature to make it cross signed
+ root.add_signature(&KEYS[0], SignatureScheme::Ed25519)
+ .unwrap();
+
+ assert_eq!(tuf.update_root(root.clone()), Ok(true));
+
+ // second update should do nothing
+ assert_eq!(tuf.update_root(root), Ok(false));
+ }
+
+ #[test]
+ fn no_cross_sign_root_rotation() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let root = RootMetadata::new(
+ 2,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ // include the old key to prevent short circuiting the verify logic
+ vec![KEYS[0].public().clone(), KEYS[1].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ assert!(tuf.update_root(root).is_err());
+ }
+
+ #[test]
+ fn good_timestamp_update() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone(), KEYS[1].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ assert_eq!(tuf.update_timestamp(timestamp.clone()), Ok(true));
+
+ // second update should do nothing
+ assert_eq!(tuf.update_timestamp(timestamp), Ok(false))
+ }
+
+ #[test]
+ fn bad_timestamp_update_wrong_key() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![KEYS[0].public().clone(), KEYS[1].public().clone()],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+
+ // sign it with the root key
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ assert!(tuf.update_timestamp(timestamp).is_err())
+ }
+
+ #[test]
+ fn good_snapshot_update() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![
+ KEYS[0].public().clone(),
+ KEYS[1].public().clone(),
+ KEYS[2].public().clone(),
+ ],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_timestamp(timestamp).unwrap();
+
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!())
+ .unwrap();
+ let snapshot: SignedMetadata<JsonDataInterchange, SnapshotMetadata> =
+ SignedMetadata::new(&snapshot, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ assert_eq!(tuf.update_snapshot(snapshot.clone()), Ok(true));
+
+ // second update should do nothing
+ assert_eq!(tuf.update_snapshot(snapshot), Ok(false));
+ }
+
+ #[test]
+ fn bad_snapshot_update_wrong_key() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![
+ KEYS[0].public().clone(),
+ KEYS[1].public().clone(),
+ KEYS[2].public().clone(),
+ ],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_timestamp(timestamp).unwrap();
+
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!())
+ .unwrap();
+ let snapshot: SignedMetadata<JsonDataInterchange, SnapshotMetadata> =
+ SignedMetadata::new(&snapshot, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ assert!(tuf.update_snapshot(snapshot.clone()).is_err());
+ }
+
+ #[test]
+ fn bad_snapshot_update_wrong_version() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![
+ KEYS[0].public().clone(),
+ KEYS[1].public().clone(),
+ KEYS[2].public().clone(),
+ ],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 2, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_timestamp(timestamp).unwrap();
+
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!())
+ .unwrap();
+ let snapshot: SignedMetadata<JsonDataInterchange, SnapshotMetadata> =
+ SignedMetadata::new(&snapshot, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ assert!(tuf.update_snapshot(snapshot).is_err());
+ }
+
+ #[test]
+ fn good_targets_update() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![
+ KEYS[0].public().clone(),
+ KEYS[1].public().clone(),
+ KEYS[2].public().clone(),
+ KEYS[3].public().clone(),
+ ],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[3].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[3], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_timestamp(timestamp).unwrap();
+
+ let meta_map = hashmap!(
+ MetadataPath::from_role(&Role::Targets) =>
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ );
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
+ .unwrap();
+ let snapshot: SignedMetadata<JsonDataInterchange, SnapshotMetadata> =
+ SignedMetadata::new(&snapshot, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_snapshot(snapshot).unwrap();
+
+ let targets = TargetsMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ hashmap!(),
+ None,
+ ).unwrap();
+ let targets: SignedMetadata<JsonDataInterchange, TargetsMetadata> =
+ SignedMetadata::new(&targets, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ assert_eq!(tuf.update_targets(targets.clone()), Ok(true));
+
+ // second update should do nothing
+ assert_eq!(tuf.update_targets(targets), Ok(false));
+ }
+
+ #[test]
+ fn bad_targets_update_wrong_key() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![
+ KEYS[0].public().clone(),
+ KEYS[1].public().clone(),
+ KEYS[2].public().clone(),
+ KEYS[3].public().clone(),
+ ],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[3].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[3], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_timestamp(timestamp).unwrap();
+
+ let meta_map = hashmap!(
+ MetadataPath::from_role(&Role::Targets) =>
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ );
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
+ .unwrap();
+ let snapshot: SignedMetadata<JsonDataInterchange, SnapshotMetadata> =
+ SignedMetadata::new(&snapshot, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_snapshot(snapshot).unwrap();
+
+ let targets = TargetsMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ hashmap!(),
+ None,
+ ).unwrap();
+ let targets: SignedMetadata<JsonDataInterchange, TargetsMetadata> =
+ SignedMetadata::new(&targets, &KEYS[3], SignatureScheme::Ed25519).unwrap();
+
+ assert!(tuf.update_targets(targets).is_err());
+ }
+
+ #[test]
+ fn bad_targets_update_wrong_version() {
+ let root = RootMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ false,
+ vec![
+ KEYS[0].public().clone(),
+ KEYS[1].public().clone(),
+ KEYS[2].public().clone(),
+ KEYS[3].public().clone(),
+ ],
+ RoleDefinition::new(1, hashset!(KEYS[0].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[1].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[2].key_id().clone())).unwrap(),
+ RoleDefinition::new(1, hashset!(KEYS[3].key_id().clone())).unwrap(),
+ ).unwrap();
+ let root: SignedMetadata<JsonDataInterchange, RootMetadata> =
+ SignedMetadata::new(&root, &KEYS[0], SignatureScheme::Ed25519).unwrap();
+
+ let mut tuf = Tuf::from_root(root).unwrap();
+
+ let timestamp = TimestampMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ MetadataDescription::from_reader(&*vec![], 1, &[HashAlgorithm::Sha256])
+ .unwrap(),
+ ).unwrap();
+ let timestamp: SignedMetadata<JsonDataInterchange, TimestampMetadata> =
+ SignedMetadata::new(×tamp, &KEYS[3], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_timestamp(timestamp).unwrap();
+
+ let meta_map = hashmap!(
+ MetadataPath::from_role(&Role::Targets) =>
+ MetadataDescription::from_reader(&*vec![], 2, &[HashAlgorithm::Sha256]).unwrap(),
+ );
+ let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
+ .unwrap();
+ let snapshot: SignedMetadata<JsonDataInterchange, SnapshotMetadata> =
+ SignedMetadata::new(&snapshot, &KEYS[1], SignatureScheme::Ed25519).unwrap();
+
+ tuf.update_snapshot(snapshot).unwrap();
+
+ let targets = TargetsMetadata::new(
+ 1,
+ Utc.ymd(2038, 1, 1).and_hms(0, 0, 0),
+ hashmap!(),
+ None,
+ ).unwrap();
+ let targets: SignedMetadata<JsonDataInterchange, TargetsMetadata> =
+ SignedMetadata::new(&targets, &KEYS[2], SignatureScheme::Ed25519).unwrap();
+
+ assert!(tuf.update_targets(targets).is_err());
+ }
+}
diff --git a/tests/integration.rs b/tests/integration.rs
index b7ae23d..1871162 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -1,9 +1,11 @@
extern crate chrono;
+#[macro_use]
+extern crate maplit;
extern crate tuf;
use chrono::prelude::*;
use chrono::offset::Utc;
-use std::collections::{HashSet, HashMap};
+use std::collections::HashMap;
use tuf::Tuf;
use tuf::crypto::{PrivateKey, SignatureScheme, HashAlgorithm};
use tuf::interchange::JsonDataInterchange;
@@ -34,21 +36,10 @@
timestamp_key.public().clone(),
];
- let mut key_ids = HashSet::new();
- key_ids.insert(root_key.key_id().clone());
- let root_def = RoleDefinition::new(1, key_ids).unwrap();
-
- let mut key_ids = HashSet::new();
- key_ids.insert(snapshot_key.key_id().clone());
- let snapshot_def = RoleDefinition::new(1, key_ids).unwrap();
-
- let mut key_ids = HashSet::new();
- key_ids.insert(targets_key.key_id().clone());
- let targets_def = RoleDefinition::new(1, key_ids).unwrap();
-
- let mut key_ids = HashSet::new();
- key_ids.insert(timestamp_key.key_id().clone());
- let timestamp_def = RoleDefinition::new(1, key_ids).unwrap();
+ let root_def = RoleDefinition::new(1, hashset!(root_key.key_id().clone())).unwrap();
+ let snapshot_def = RoleDefinition::new(1, hashset!(snapshot_key.key_id().clone())).unwrap();
+ let targets_def = RoleDefinition::new(1, hashset!(targets_key.key_id().clone())).unwrap();
+ let timestamp_def = RoleDefinition::new(1, hashset!(timestamp_key.key_id().clone())).unwrap();
let root = RootMetadata::new(
1,
@@ -71,12 +62,8 @@
Tuf::<JsonDataInterchange>::from_root_pinned(signed, &[root_key.key_id().clone()]).unwrap();
//// build the timestamp ////
- let mut meta_map = HashMap::new();
- let path = MetadataPath::new("snapshot".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
- let timestamp = TimestampMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
- .unwrap();
+ let snap = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
+ let timestamp = TimestampMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), snap).unwrap();
let signed = SignedMetadata::<JsonDataInterchange, TimestampMetadata>::new(
×tamp,
@@ -87,13 +74,13 @@
tuf.update_timestamp(signed).unwrap();
//// build the snapshot ////
- let mut meta_map = HashMap::new();
- let path = MetadataPath::new("targets".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
- let path = MetadataPath::new("delegation".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
+ let meta_map =
+ hashmap! {
+ MetadataPath::new("targets".into()).unwrap() =>
+ MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ MetadataPath::new("delegation".into()).unwrap() =>
+ MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ };
let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
.unwrap();
@@ -141,12 +128,11 @@
//// build the delegation ////
let target_file: &[u8] = b"bar";
- let target_path = TargetPath::new("foo".into()).unwrap();
- let target_description = TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256])
- .unwrap();
-
- let mut target_map = HashMap::new();
- let _ = target_map.insert(target_path, target_description);
+ let target_map =
+ hashmap! {
+ TargetPath::new("foo".into()).unwrap() =>
+ TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256]).unwrap(),
+ };
let delegation =
TargetsMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), target_map, None).unwrap();
@@ -182,21 +168,10 @@
timestamp_key.public().clone(),
];
- let mut key_ids = HashSet::new();
- key_ids.insert(root_key.key_id().clone());
- let root_def = RoleDefinition::new(1, key_ids).unwrap();
-
- let mut key_ids = HashSet::new();
- key_ids.insert(snapshot_key.key_id().clone());
- let snapshot_def = RoleDefinition::new(1, key_ids).unwrap();
-
- let mut key_ids = HashSet::new();
- key_ids.insert(targets_key.key_id().clone());
- let targets_def = RoleDefinition::new(1, key_ids).unwrap();
-
- let mut key_ids = HashSet::new();
- key_ids.insert(timestamp_key.key_id().clone());
- let timestamp_def = RoleDefinition::new(1, key_ids).unwrap();
+ let root_def = RoleDefinition::new(1, hashset!(root_key.key_id().clone())).unwrap();
+ let snapshot_def = RoleDefinition::new(1, hashset!(snapshot_key.key_id().clone())).unwrap();
+ let targets_def = RoleDefinition::new(1, hashset!(targets_key.key_id().clone())).unwrap();
+ let timestamp_def = RoleDefinition::new(1, hashset!(timestamp_key.key_id().clone())).unwrap();
let root = RootMetadata::new(
1,
@@ -219,12 +194,8 @@
Tuf::<JsonDataInterchange>::from_root_pinned(signed, &[root_key.key_id().clone()]).unwrap();
//// build the timestamp ////
- let mut meta_map = HashMap::new();
- let path = MetadataPath::new("snapshot".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
- let timestamp = TimestampMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
- .unwrap();
+ let snap = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
+ let timestamp = TimestampMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), snap).unwrap();
let signed = SignedMetadata::<JsonDataInterchange, TimestampMetadata>::new(
×tamp,
@@ -235,16 +206,15 @@
tuf.update_timestamp(signed).unwrap();
//// build the snapshot ////
- let mut meta_map = HashMap::new();
- let path = MetadataPath::new("targets".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
- let path = MetadataPath::new("delegation-a".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
- let path = MetadataPath::new("delegation-b".into()).unwrap();
- let desc = MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap();
- let _ = meta_map.insert(path, desc);
+ let meta_map =
+ hashmap! {
+ MetadataPath::new("targets".into()).unwrap() =>
+ MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ MetadataPath::new("delegation-a".into()).unwrap() =>
+ MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ MetadataPath::new("delegation-b".into()).unwrap() =>
+ MetadataDescription::from_reader(&*vec![0u8], 1, &[HashAlgorithm::Sha256]).unwrap(),
+ };
let snapshot = SnapshotMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)
.unwrap();
@@ -327,12 +297,11 @@
//// build delegation B ////
let target_file: &[u8] = b"bar";
- let target_path = TargetPath::new("foo".into()).unwrap();
- let target_description = TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256])
- .unwrap();
-
- let mut target_map = HashMap::new();
- let _ = target_map.insert(target_path, target_description);
+ let target_map =
+ hashmap! {
+ TargetPath::new("foo".into()).unwrap() =>
+ TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256]).unwrap(),
+ };
let delegation =
TargetsMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), target_map, None).unwrap();
diff --git a/tests/simple_example.rs b/tests/simple_example.rs
index b9cac7b..0dd3172 100644
--- a/tests/simple_example.rs
+++ b/tests/simple_example.rs
@@ -167,12 +167,8 @@
JsonDataInterchange::canonicalize(&JsonDataInterchange::serialize(&signed)?)?;
//// build the timestamp ////
- let meta_map =
- hashmap! {
- MetadataPath::new("snapshot".into())? =>
- MetadataDescription::from_reader(&*snapshot_bytes, 1, &[HashAlgorithm::Sha256])?,
- };
- let timestamp = TimestampMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), meta_map)?;
+ let snap = MetadataDescription::from_reader(&*snapshot_bytes, 1, &[HashAlgorithm::Sha256])?;
+ let timestamp = TimestampMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), snap)?;
let signed = SignedMetadata::<JsonDataInterchange, TimestampMetadata>::new(
×tamp,