Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions interop-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,14 @@ pub async fn generate_repos(
add_target(&mut repo, &keys, 0, consistent_snapshot).await;

// Queue up a series of key rotations
let mut i: u8 = 1;
let rotations = [
Some(Role::Root),
Some(Role::Targets),
Some(Role::Snapshot),
Some(Role::Timestamp),
None,
];
for r in rotations.iter() {
for (i, r) in (1_u8..).zip(rotations.iter()) {
// Initialize new repo and copy the files from the previous step.
let dir_i = Path::new(dir).join(i.to_string());
let mut repo = FileSystemRepositoryBuilder::new(dir_i)
Expand Down Expand Up @@ -306,7 +305,6 @@ pub async fn generate_repos(
)
.await;
add_target(&mut repo, &keys, i, consistent_snapshot).await;
i += 1;
}
Ok(())
}
316 changes: 306 additions & 10 deletions tuf/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,15 +424,13 @@ impl<D: Pouf> Database<D> {
// new timestamp metadata file. If not, discard the new timestamp metadadata file,
// abort the update cycle, and report the failure.

// FIXME(#294): Implement this section.

/////////////////////////////////////////
// FIXME(#297): forgetting the trusted snapshot here is not part of the spec. Do we need to
// do it?

if let Some(trusted_snapshot) = &self.trusted_snapshot {
if trusted_snapshot.version() != new_timestamp.snapshot().version() {
self.trusted_snapshot = None;
if let Some(trusted_timestamp) = &self.trusted_timestamp {
if new_timestamp.snapshot().version() < trusted_timestamp.snapshot().version() {
return Err(Error::AttemptedMetadataRollBack {
role: MetadataPath::snapshot(),
trusted_version: trusted_timestamp.snapshot().version(),
new_version: new_timestamp.snapshot().version(),
});
}
}

Expand Down Expand Up @@ -570,7 +568,24 @@ impl<D: Pouf> Database<D> {
// metadata file. If any of these conditions are not met, discard the new snapshot
// metadadata file, abort the update cycle, and report the failure.

// FIXME(#295): Implement this section.
if let Some(trusted_snapshot) = &self.trusted_snapshot {
for (role, trusted_description) in trusted_snapshot.meta().iter() {
let new_description = new_snapshot.meta().get(role).ok_or_else(|| {
Error::MissingMetadataDescription {
parent_role: MetadataPath::snapshot(),
child_role: role.clone(),
}
})?;

if new_description.version() < trusted_description.version() {
return Err(Error::AttemptedMetadataRollBack {
role: role.clone(),
trusted_version: trusted_description.version(),
new_version: new_description.version(),
});
}
}
}

/////////////////////////////////////////
// TUF-1.0.5 §5.3.4:
Expand Down Expand Up @@ -1363,6 +1378,59 @@ mod test {
assert!(tuf.update_timestamp(&now, &raw_timestamp).is_err())
}

#[test]
fn bad_timestamp_update_rollback_snapshot() {
let now = Utc::now();

let raw_root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[1].public().clone())
.timestamp_key(KEYS[1].public().clone())
.signed::<Pouf1>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();

let mut tuf = Database::from_trusted_root(&raw_root).unwrap();

let snapshot_v2 = SnapshotMetadataBuilder::new()
.version(2)
.signed::<Pouf1>(&KEYS[1])
.unwrap();

let raw_timestamp_v1 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v2, &[HashAlgorithm::Sha256])
.unwrap()
.version(1)
.signed::<Pouf1>(&KEYS[1])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v1).unwrap();

let snapshot_v1 = SnapshotMetadataBuilder::new()
.version(1)
.signed::<Pouf1>(&KEYS[1])
.unwrap();

let raw_timestamp_v2 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v1, &[HashAlgorithm::Sha256])
.unwrap()
.version(2)
.signed::<Pouf1>(&KEYS[1])
.unwrap()
.to_raw()
.unwrap();

assert_matches!(
tuf.update_timestamp(&now, &raw_timestamp_v2),
Err(Error::AttemptedMetadataRollBack { role, trusted_version: 2, new_version: 1 })
if role == MetadataPath::snapshot()
);
}

#[test]
fn good_snapshot_update() {
let now = Utc::now();
Expand Down Expand Up @@ -1474,6 +1542,234 @@ mod test {
assert!(tuf.update_snapshot(&now, &raw_snapshot).is_err());
}

#[test]
fn bad_snapshot_update_rollback_targets() {
let now = Utc::now();

let raw_root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Pouf1>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();

let mut tuf = Database::from_trusted_root(&raw_root).unwrap();

let signed_targets_v2 = TargetsMetadataBuilder::new()
.version(2)
.signed::<Pouf1>(&KEYS[2])
.unwrap();

let snapshot_v1 = SnapshotMetadataBuilder::new()
.version(1)
.insert_metadata(&signed_targets_v2, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Pouf1>(&KEYS[1])
.unwrap();
let raw_snapshot_v1 = snapshot_v1.to_raw().unwrap();

let raw_timestamp_v1 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v1, &[HashAlgorithm::Sha256])
.unwrap()
.version(1)
.signed::<Pouf1>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v1).unwrap();
tuf.update_snapshot(&now, &raw_snapshot_v1).unwrap();

let signed_targets_v1 = TargetsMetadataBuilder::new()
.version(1)
.signed::<Pouf1>(&KEYS[2])
.unwrap();

let snapshot_v2 = SnapshotMetadataBuilder::new()
.version(2)
.insert_metadata(&signed_targets_v1, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Pouf1>(&KEYS[1])
.unwrap();
let raw_snapshot_v2 = snapshot_v2.to_raw().unwrap();

let raw_timestamp_v2 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v2, &[HashAlgorithm::Sha256])
.unwrap()
.version(2)
.signed::<Pouf1>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v2).unwrap();

assert_matches!(
tuf.update_snapshot(&now, &raw_snapshot_v2),
Err(Error::AttemptedMetadataRollBack { role, trusted_version: 2, new_version: 1 })
if role == MetadataPath::targets()
);
}

#[test]
fn bad_snapshot_update_missing_targets() {
let now = Utc::now();

let raw_root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Pouf1>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();

let mut tuf = Database::from_trusted_root(&raw_root).unwrap();

let signed_targets = TargetsMetadataBuilder::new()
.version(1)
.signed::<Pouf1>(&KEYS[2])
.unwrap();

let snapshot_v1 = SnapshotMetadataBuilder::new()
.version(1)
.insert_metadata(&signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Pouf1>(&KEYS[1])
.unwrap();
let raw_snapshot_v1 = snapshot_v1.to_raw().unwrap();

let raw_timestamp_v1 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v1, &[HashAlgorithm::Sha256])
.unwrap()
.version(1)
.signed::<Pouf1>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v1).unwrap();
tuf.update_snapshot(&now, &raw_snapshot_v1).unwrap();

let snapshot_v2 = SnapshotMetadataBuilder::new()
.version(2)
.signed::<Pouf1>(&KEYS[1])
.unwrap();
let raw_snapshot_v2 = snapshot_v2.to_raw().unwrap();

let raw_timestamp_v2 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v2, &[HashAlgorithm::Sha256])
.unwrap()
.version(2)
.signed::<Pouf1>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v2).unwrap();

assert_matches!(
tuf.update_snapshot(&now, &raw_snapshot_v2),
Err(Error::MissingMetadataDescription { parent_role, child_role })
if parent_role == MetadataPath::snapshot() && child_role == MetadataPath::targets()
);
}

#[test]
fn good_snapshot_update_remove_role_after_root_update() {
let now = Utc::now();

let raw_root_v1 = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Pouf1>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();

let mut tuf = Database::from_trusted_root(&raw_root_v1).unwrap();

let signed_targets = TargetsMetadataBuilder::new()
.version(1)
.signed::<Pouf1>(&KEYS[2])
.unwrap();

let snapshot_v1 = SnapshotMetadataBuilder::new()
.version(1)
.insert_metadata(&signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.insert_metadata_with_path("delegation", &signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Pouf1>(&KEYS[1])
.unwrap();
let raw_snapshot_v1 = snapshot_v1.to_raw().unwrap();

let raw_timestamp_v1 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v1, &[HashAlgorithm::Sha256])
.unwrap()
.version(1)
.signed::<Pouf1>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v1).unwrap();
tuf.update_snapshot(&now, &raw_snapshot_v1).unwrap();

assert!(
tuf.trusted_snapshot()
.unwrap()
.meta()
.contains_key(&MetadataPath::new("delegation").unwrap())
);

let root_v2 = RootMetadataBuilder::new()
.version(2)
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[0].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Pouf1>(&KEYS[0])
.unwrap();
let raw_root_v2 = root_v2.to_raw().unwrap();

tuf.update_root(&raw_root_v2).unwrap();

let snapshot_v2 = SnapshotMetadataBuilder::new()
.version(2)
.insert_metadata(&signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Pouf1>(&KEYS[0])
.unwrap();
let raw_snapshot_v2 = snapshot_v2.to_raw().unwrap();

let raw_timestamp_v2 =
TimestampMetadataBuilder::from_snapshot(&snapshot_v2, &[HashAlgorithm::Sha256])
.unwrap()
.version(2)
.signed::<Pouf1>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();

tuf.update_timestamp(&now, &raw_timestamp_v2).unwrap();

assert_matches!(tuf.update_snapshot(&now, &raw_snapshot_v2), Ok(true));

assert!(
!tuf.trusted_snapshot()
.unwrap()
.meta()
.contains_key(&MetadataPath::new("delegation").unwrap())
);
}

#[test]
fn good_targets_update() {
let now = Utc::now();
Expand Down
Loading