| use assert_matches::assert_matches; |
| use chrono::offset::Utc; |
| use futures_executor::block_on; |
| use maplit::hashmap; |
| use tuf::crypto::{Ed25519PrivateKey, HashAlgorithm, PrivateKey}; |
| use tuf::interchange::Json; |
| use tuf::metadata::{ |
| Delegation, Delegations, MetadataDescription, MetadataPath, TargetPath, TargetsMetadataBuilder, |
| }; |
| use tuf::repo_builder::RepoBuilder; |
| use tuf::repository::EphemeralRepository; |
| use tuf::Database; |
| use tuf::Error; |
| |
| const ED25519_1_PK8: &[u8] = include_bytes!("./ed25519/ed25519-1.pk8.der"); |
| const ED25519_2_PK8: &[u8] = include_bytes!("./ed25519/ed25519-2.pk8.der"); |
| const ED25519_3_PK8: &[u8] = include_bytes!("./ed25519/ed25519-3.pk8.der"); |
| const ED25519_4_PK8: &[u8] = include_bytes!("./ed25519/ed25519-4.pk8.der"); |
| const ED25519_5_PK8: &[u8] = include_bytes!("./ed25519/ed25519-5.pk8.der"); |
| const ED25519_6_PK8: &[u8] = include_bytes!("./ed25519/ed25519-6.pk8.der"); |
| |
| #[test] |
| fn simple_delegation() { |
| block_on(async { |
| let now = Utc::now(); |
| |
| let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap(); |
| let snapshot_key = Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap(); |
| let targets_key = Ed25519PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap(); |
| let timestamp_key = Ed25519PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap(); |
| let delegation_key = Ed25519PrivateKey::from_pkcs8(ED25519_5_PK8).unwrap(); |
| |
| let delegations = Delegations::new( |
| hashmap! { delegation_key.public().key_id().clone() => delegation_key.public().clone() }, |
| vec![Delegation::new( |
| MetadataPath::new("delegation").unwrap(), |
| false, |
| 1, |
| vec![delegation_key.public().key_id().clone()] |
| .iter() |
| .cloned() |
| .collect(), |
| vec![TargetPath::new("foo").unwrap()] |
| .iter() |
| .cloned() |
| .collect(), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| let mut repo = EphemeralRepository::new(); |
| let metadata = RepoBuilder::create(&mut repo) |
| .trusted_root_keys(&[&root_key]) |
| .trusted_snapshot_keys(&[&snapshot_key]) |
| .trusted_targets_keys(&[&targets_key]) |
| .trusted_timestamp_keys(&[×tamp_key]) |
| .stage_root() |
| .unwrap() |
| .stage_targets_with_builder(|builder| builder.delegations(delegations)) |
| .unwrap() |
| .stage_snapshot_with_builder(|builder| { |
| builder.insert_metadata_description( |
| MetadataPath::new("delegation").unwrap(), |
| MetadataDescription::from_slice(&[0u8], 1, &[HashAlgorithm::Sha256]).unwrap(), |
| ) |
| }) |
| .unwrap() |
| .commit() |
| .await |
| .unwrap(); |
| |
| let mut tuf = Database::<Json>::from_trusted_metadata(&metadata).unwrap(); |
| |
| //// build the targets //// |
| //// build the delegation //// |
| let target_file: &[u8] = b"bar"; |
| let delegation = TargetsMetadataBuilder::new() |
| .insert_target_from_slice( |
| TargetPath::new("foo").unwrap(), |
| target_file, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap() |
| .signed::<Json>(&delegation_key) |
| .unwrap(); |
| let raw_delegation = delegation.to_raw().unwrap(); |
| |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::targets(), |
| &MetadataPath::new("delegation").unwrap(), |
| &raw_delegation, |
| ) |
| .unwrap(); |
| |
| assert!(tuf |
| .target_description(&TargetPath::new("foo").unwrap()) |
| .is_ok()); |
| }) |
| } |
| |
| #[test] |
| fn nested_delegation() { |
| block_on(async { |
| let now = Utc::now(); |
| |
| let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap(); |
| let snapshot_key = Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap(); |
| let targets_key = Ed25519PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap(); |
| let timestamp_key = Ed25519PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap(); |
| let delegation_a_key = Ed25519PrivateKey::from_pkcs8(ED25519_5_PK8).unwrap(); |
| let delegation_b_key = Ed25519PrivateKey::from_pkcs8(ED25519_6_PK8).unwrap(); |
| |
| let delegations = Delegations::new( |
| hashmap! { |
| delegation_a_key.public().key_id().clone() => delegation_a_key.public().clone(), |
| }, |
| vec![Delegation::new( |
| MetadataPath::new("delegation-a").unwrap(), |
| false, |
| 1, |
| vec![delegation_a_key.public().key_id().clone()] |
| .iter() |
| .cloned() |
| .collect(), |
| vec![TargetPath::new("foo").unwrap()] |
| .iter() |
| .cloned() |
| .collect(), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| let mut repo = EphemeralRepository::new(); |
| let metadata = RepoBuilder::create(&mut repo) |
| .trusted_root_keys(&[&root_key]) |
| .trusted_snapshot_keys(&[&snapshot_key]) |
| .trusted_targets_keys(&[&targets_key]) |
| .trusted_timestamp_keys(&[×tamp_key]) |
| .stage_root() |
| .unwrap() |
| .stage_targets_with_builder(|builder| builder.delegations(delegations)) |
| .unwrap() |
| .stage_snapshot_with_builder(|builder| { |
| builder |
| .insert_metadata_description( |
| MetadataPath::new("delegation-a").unwrap(), |
| MetadataDescription::from_slice(&[0u8], 1, &[HashAlgorithm::Sha256]) |
| .unwrap(), |
| ) |
| .insert_metadata_description( |
| MetadataPath::new("delegation-b").unwrap(), |
| MetadataDescription::from_slice(&[0u8], 1, &[HashAlgorithm::Sha256]) |
| .unwrap(), |
| ) |
| }) |
| .unwrap() |
| .commit() |
| .await |
| .unwrap(); |
| |
| let mut tuf = Database::<Json>::from_trusted_metadata(&metadata).unwrap(); |
| |
| //// build delegation A //// |
| |
| let delegations = Delegations::new( |
| hashmap! { delegation_b_key.public().key_id().clone() => delegation_b_key.public().clone() }, |
| vec![Delegation::new( |
| MetadataPath::new("delegation-b").unwrap(), |
| false, |
| 1, |
| vec![delegation_b_key.public().key_id().clone()].iter().cloned().collect(), |
| vec![TargetPath::new("foo").unwrap()].iter().cloned().collect(), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| let delegation = TargetsMetadataBuilder::new() |
| .delegations(delegations) |
| .signed::<Json>(&delegation_a_key) |
| .unwrap(); |
| let raw_delegation = delegation.to_raw().unwrap(); |
| |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::targets(), |
| &MetadataPath::new("delegation-a").unwrap(), |
| &raw_delegation, |
| ) |
| .unwrap(); |
| |
| //// build delegation B //// |
| |
| let target_file: &[u8] = b"bar"; |
| |
| let delegation = TargetsMetadataBuilder::new() |
| .insert_target_from_slice( |
| TargetPath::new("foo").unwrap(), |
| target_file, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap() |
| .signed::<Json>(&delegation_b_key) |
| .unwrap(); |
| let raw_delegation = delegation.to_raw().unwrap(); |
| |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::new("delegation-a").unwrap(), |
| &MetadataPath::new("delegation-b").unwrap(), |
| &raw_delegation, |
| ) |
| .unwrap(); |
| |
| assert!(tuf |
| .target_description(&TargetPath::new("foo").unwrap()) |
| .is_ok()); |
| }) |
| } |
| |
| #[test] |
| fn rejects_bad_delegation_signatures() { |
| block_on(async { |
| let now = Utc::now(); |
| |
| let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap(); |
| let snapshot_key = Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap(); |
| let targets_key = Ed25519PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap(); |
| let timestamp_key = Ed25519PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap(); |
| let delegation_key = Ed25519PrivateKey::from_pkcs8(ED25519_5_PK8).unwrap(); |
| let bad_delegation_key = Ed25519PrivateKey::from_pkcs8(ED25519_6_PK8).unwrap(); |
| |
| let delegations = Delegations::new( |
| hashmap! { delegation_key.public().key_id().clone() => delegation_key.public().clone() }, |
| vec![Delegation::new( |
| MetadataPath::new("delegation").unwrap(), |
| false, |
| 1, |
| vec![delegation_key.public().key_id().clone()] |
| .iter() |
| .cloned() |
| .collect(), |
| vec![TargetPath::new("foo").unwrap()] |
| .iter() |
| .cloned() |
| .collect(), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| let mut repo = EphemeralRepository::new(); |
| let metadata = RepoBuilder::create(&mut repo) |
| .trusted_root_keys(&[&root_key]) |
| .trusted_snapshot_keys(&[&snapshot_key]) |
| .trusted_targets_keys(&[&targets_key]) |
| .trusted_timestamp_keys(&[×tamp_key]) |
| .stage_root() |
| .unwrap() |
| .stage_targets_with_builder(|builder| builder.delegations(delegations)) |
| .unwrap() |
| .stage_snapshot_with_builder(|builder| { |
| builder.insert_metadata_description( |
| MetadataPath::new("delegation").unwrap(), |
| MetadataDescription::from_slice(&[0u8], 1, &[HashAlgorithm::Sha256]).unwrap(), |
| ) |
| }) |
| .unwrap() |
| .commit() |
| .await |
| .unwrap(); |
| |
| let mut tuf = Database::<Json>::from_trusted_metadata(&metadata).unwrap(); |
| |
| //// build the delegation //// |
| let target_file: &[u8] = b"bar"; |
| let delegation = TargetsMetadataBuilder::new() |
| .insert_target_from_slice( |
| TargetPath::new("foo").unwrap(), |
| target_file, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap() |
| .signed::<Json>(&bad_delegation_key) |
| .unwrap(); |
| let raw_delegation = delegation.to_raw().unwrap(); |
| |
| assert_matches!( |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::targets(), |
| &MetadataPath::new("delegation").unwrap(), |
| &raw_delegation |
| ), |
| Err(Error::VerificationFailure(_)) |
| ); |
| |
| assert_matches!( |
| tuf.target_description(&TargetPath::new("foo").unwrap()), |
| Err(Error::TargetUnavailable) |
| ); |
| }) |
| } |
| |
| #[test] |
| fn diamond_delegation() { |
| block_on(async { |
| let now = Utc::now(); |
| |
| let etc_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap(); |
| let targets_key = Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap(); |
| let delegation_a_key = Ed25519PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap(); |
| let delegation_b_key = Ed25519PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap(); |
| let delegation_c_key = Ed25519PrivateKey::from_pkcs8(ED25519_5_PK8).unwrap(); |
| |
| // Given delegations a, b, and c, targets delegates "foo" to delegation-a and "bar" to |
| // delegation-b. |
| // |
| // targets |
| // / \ |
| // delegation-a delegation-b |
| // \ / |
| // delegation-c |
| // |
| // if delegation-a delegates "foo" to delegation-c, and |
| // delegation-b delegates "bar" to delegation-c, but |
| // delegation-b's signature is invalid, then delegation-c |
| // can contain target "bar" which is unaccessible and target "foo" which is. |
| // |
| // Verify tuf::Database handles this situation correctly. |
| |
| //// build delegation A //// |
| |
| let delegation_a_delegations = Delegations::new( |
| hashmap! { delegation_c_key.public().key_id().clone() => delegation_c_key.public().clone() }, |
| vec![Delegation::new( |
| MetadataPath::new("delegation-c").unwrap(), |
| false, |
| 1, |
| vec![delegation_c_key.public().key_id().clone()].iter().cloned().collect(), |
| vec![TargetPath::new("foo").unwrap()].iter().cloned().collect(), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| let delegation_a = TargetsMetadataBuilder::new() |
| .delegations(delegation_a_delegations) |
| .signed::<Json>(&delegation_a_key) |
| .unwrap(); |
| let raw_delegation_a = delegation_a.to_raw().unwrap(); |
| |
| //// build delegation B //// |
| |
| let delegations_b = Delegations::new( |
| hashmap! { delegation_c_key.public().key_id().clone() => delegation_c_key.public().clone() }, |
| vec![Delegation::new( |
| MetadataPath::new("delegation-c").unwrap(), |
| false, |
| 1, |
| // oops, wrong key. |
| vec![delegation_b_key.public().key_id().clone()].iter().cloned().collect(), |
| vec![TargetPath::new("bar").unwrap()].iter().cloned().collect(), |
| ) |
| .unwrap()], |
| ) |
| .unwrap(); |
| |
| let delegation_b = TargetsMetadataBuilder::new() |
| .delegations(delegations_b) |
| .signed::<Json>(&delegation_b_key) |
| .unwrap(); |
| let raw_delegation_b = delegation_b.to_raw().unwrap(); |
| |
| //// build delegation C //// |
| |
| let foo_target_file: &[u8] = b"foo contents"; |
| let bar_target_file: &[u8] = b"bar contents"; |
| |
| let delegation_c = TargetsMetadataBuilder::new() |
| .insert_target_from_slice( |
| TargetPath::new("foo").unwrap(), |
| foo_target_file, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap() |
| .insert_target_from_slice( |
| TargetPath::new("bar").unwrap(), |
| bar_target_file, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap() |
| .signed::<Json>(&delegation_c_key) |
| .unwrap(); |
| let raw_delegation_c = delegation_c.to_raw().unwrap(); |
| |
| //// construct the database //// |
| |
| let delegations = Delegations::new( |
| hashmap! { |
| delegation_a_key.public().key_id().clone() => delegation_a_key.public().clone(), |
| delegation_b_key.public().key_id().clone() => delegation_b_key.public().clone(), |
| }, |
| vec![ |
| Delegation::new( |
| MetadataPath::new("delegation-a").unwrap(), |
| false, |
| 1, |
| vec![delegation_a_key.public().key_id().clone()] |
| .iter() |
| .cloned() |
| .collect(), |
| vec![TargetPath::new("foo").unwrap()] |
| .iter() |
| .cloned() |
| .collect(), |
| ) |
| .unwrap(), |
| Delegation::new( |
| MetadataPath::new("delegation-b").unwrap(), |
| false, |
| 1, |
| vec![delegation_b_key.public().key_id().clone()] |
| .iter() |
| .cloned() |
| .collect(), |
| vec![TargetPath::new("bar").unwrap()] |
| .iter() |
| .cloned() |
| .collect(), |
| ) |
| .unwrap(), |
| ], |
| ) |
| .unwrap(); |
| |
| let mut repo = EphemeralRepository::new(); |
| let metadata = RepoBuilder::create(&mut repo) |
| .trusted_root_keys(&[&etc_key]) |
| .trusted_snapshot_keys(&[&etc_key]) |
| .trusted_targets_keys(&[&targets_key]) |
| .trusted_timestamp_keys(&[&etc_key]) |
| .stage_root() |
| .unwrap() |
| .stage_targets_with_builder(|builder| builder.delegations(delegations)) |
| .unwrap() |
| .stage_snapshot_with_builder(|builder| { |
| builder |
| .insert_metadata_description( |
| MetadataPath::new("delegation-a").unwrap(), |
| MetadataDescription::from_slice( |
| raw_delegation_a.as_bytes(), |
| 1, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap(), |
| ) |
| .insert_metadata_description( |
| MetadataPath::new("delegation-b").unwrap(), |
| MetadataDescription::from_slice( |
| raw_delegation_b.as_bytes(), |
| 1, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap(), |
| ) |
| .insert_metadata_description( |
| MetadataPath::new("delegation-c").unwrap(), |
| MetadataDescription::from_slice( |
| raw_delegation_c.as_bytes(), |
| 1, |
| &[HashAlgorithm::Sha256], |
| ) |
| .unwrap(), |
| ) |
| }) |
| .unwrap() |
| .commit() |
| .await |
| .unwrap(); |
| |
| let mut tuf = Database::<Json>::from_trusted_metadata(&metadata).unwrap(); |
| |
| //// Verify we can trust delegation-a and delegation-b.. |
| |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::targets(), |
| &MetadataPath::new("delegation-a").unwrap(), |
| &raw_delegation_a, |
| ) |
| .unwrap(); |
| |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::targets(), |
| &MetadataPath::new("delegation-b").unwrap(), |
| &raw_delegation_b, |
| ) |
| .unwrap(); |
| |
| //// Verify delegation-c is valid, but only when updated through delegation-a. |
| |
| assert_matches!( |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::new("delegation-b").unwrap(), |
| &MetadataPath::new("delegation-c").unwrap(), |
| &raw_delegation_c |
| ), |
| Err(Error::VerificationFailure(_)) |
| ); |
| |
| tuf.update_delegated_targets( |
| &now, |
| &MetadataPath::new("delegation-a").unwrap(), |
| &MetadataPath::new("delegation-c").unwrap(), |
| &raw_delegation_c, |
| ) |
| .unwrap(); |
| |
| assert!(tuf |
| .target_description(&TargetPath::new("foo").unwrap()) |
| .is_ok()); |
| |
| assert_matches!( |
| tuf.target_description(&TargetPath::new("bar").unwrap()), |
| Err(Error::TargetUnavailable) |
| ); |
| }) |
| } |