blob: 08d61f74a51bad98a6574323976eaae706812c09 [file] [log] [blame]
//! Components needed to verify TUF metadata and targets.
use chrono::offset::Utc;
use log::info;
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
use crate::crypto::KeyId;
use crate::error::Error;
use crate::interchange::DataInterchange;
use crate::metadata::{
Delegations, Metadata, MetadataPath, Role, RootMetadata, SignedMetadata, SnapshotMetadata,
TargetDescription, TargetsMetadata, TimestampMetadata, VirtualTargetPath,
};
use crate::Result;
/// Contains trusted TUF metadata and can be used to verify other metadata and targets.
#[derive(Debug)]
pub struct Tuf<D: DataInterchange> {
root: SignedMetadata<D, RootMetadata>,
snapshot: Option<SignedMetadata<D, SnapshotMetadata>>,
targets: Option<SignedMetadata<D, TargetsMetadata>>,
timestamp: Option<SignedMetadata<D, TimestampMetadata>>,
delegations: HashMap<MetadataPath, SignedMetadata<D, TargetsMetadata>>,
interchange: PhantomData<D>,
}
impl<D: DataInterchange> Tuf<D> {
/// Create a new `TUF` struct from a known set of pinned root keys that are used to verify the
/// signed metadata.
pub fn from_root_pinned<'a, I>(
mut signed_root: SignedMetadata<D, RootMetadata>,
root_key_ids: I,
) -> Result<Self>
where
I: IntoIterator<Item = &'a KeyId>,
{
let root_key_ids = root_key_ids.into_iter().collect::<HashSet<&KeyId>>();
signed_root
.signatures_mut()
.retain(|s| root_key_ids.contains(s.key_id()));
Self::from_root(signed_root)
}
/// Create a new `TUF` struct from a piece of metadata that is assumed to be trusted.
///
/// **WARNING**: This is trust-on-first-use (TOFU) and offers weaker security guarantees than
/// the related method `from_root_pinned`.
pub fn from_root(signed_root: SignedMetadata<D, RootMetadata>) -> Result<Self> {
{
let root = signed_root.as_ref();
signed_root.verify(
root.root().threshold(),
root.keys().iter().filter_map(|(k, v)| {
if root.root().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
}
Ok(Tuf {
root: signed_root,
snapshot: None,
targets: None,
timestamp: None,
delegations: HashMap::new(),
interchange: PhantomData,
})
}
/// An immutable reference to the root metadata.
pub fn root(&self) -> &RootMetadata {
self.root.as_ref()
}
/// An immutable reference to the optional snapshot metadata.
pub fn snapshot(&self) -> Option<&SnapshotMetadata> {
self.snapshot.as_ref().map(|t| t.as_ref())
}
/// An immutable reference to the optional targets metadata.
pub fn targets(&self) -> Option<&TargetsMetadata> {
self.targets.as_ref().map(|t| t.as_ref())
}
/// An immutable reference to the optional timestamp metadata.
pub fn timestamp(&self) -> Option<&TimestampMetadata> {
self.timestamp.as_ref().map(|t| t.as_ref())
}
/// An immutable reference to the delegated metadata.
pub fn delegations(&self) -> &HashMap<MetadataPath, SignedMetadata<D, TargetsMetadata>> {
&self.delegations
}
/// An immutable reference to the root metadata.
pub fn signed_root(&self) -> &SignedMetadata<D, RootMetadata> {
&self.root
}
/// An immutable reference to the optional snapshot metadata.
pub fn signed_snapshot(&self) -> Option<&SignedMetadata<D, SnapshotMetadata>> {
self.snapshot.as_ref()
}
/// An immutable reference to the optional targets metadata.
pub fn signed_targets(&self) -> Option<&SignedMetadata<D, TargetsMetadata>> {
self.targets.as_ref()
}
/// An immutable reference to the optional timestamp metadata.
pub fn signed_timestamp(&self) -> Option<&SignedMetadata<D, TimestampMetadata>> {
self.timestamp.as_ref()
}
fn current_timestamp_version(&self) -> u32 {
self.timestamp
.as_ref()
.map(|t| t.as_ref().version())
.unwrap_or(0)
}
fn current_snapshot_version(&self) -> u32 {
self.snapshot
.as_ref()
.map(|t| t.as_ref().version())
.unwrap_or(0)
}
fn current_targets_version(&self) -> u32 {
self.targets
.as_ref()
.map(|t| t.as_ref().version())
.unwrap_or(0)
}
fn current_delegation_version(&self, role: &MetadataPath) -> u32 {
self.delegations
.get(role)
.map(|t| t.as_ref().version())
.unwrap_or(0)
}
/// Verify and update the root metadata.
pub fn update_root(&mut self, signed_root: SignedMetadata<D, RootMetadata>) -> Result<bool> {
{
let old_root = self.root.as_ref();
let new_root = signed_root.as_ref();
// First, check that the new root was signed by the old root.
signed_root.verify(
old_root.root().threshold(),
old_root.keys().iter().filter_map(|(k, v)| {
if old_root.root().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
// Next, make sure the new root has a higher version than the old root.
if new_root.version() == old_root.version() {
info!(
"Attempted to update root to new metadata with the same version. \
Refusing to update."
);
return Ok(false);
} else if new_root.version() < old_root.version() {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back root metadata at version {} to {}.",
old_root.version(),
new_root.version()
)));
}
// Finally, make sure the new root was signed by the keys in the new root.
signed_root.verify(
new_root.root().threshold(),
new_root.keys().iter().filter_map(|(k, v)| {
if new_root.root().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
}
self.purge_metadata();
self.root = signed_root;
Ok(true)
}
/// Verify and update the timestamp metadata.
pub fn update_timestamp(
&mut self,
signed_timestamp: SignedMetadata<D, TimestampMetadata>,
) -> Result<bool> {
{
let root = self.root.as_ref();
let timestamp = signed_timestamp.as_ref();
// First, make sure the root signed the metadata.
signed_timestamp.verify(
root.timestamp().threshold(),
root.keys().iter().filter_map(|(k, v)| {
if root.timestamp().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
// Next, make sure the timestamp hasn't expired.
if timestamp.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Timestamp));
}
// Next, make sure the new metadata has a higher version than the old metadata.
let current_version = self.current_timestamp_version();
if timestamp.version() < current_version {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back timestamp metadata at version {} to {}.",
current_version,
timestamp.version()
)));
} else if timestamp.version() == current_version {
return Ok(false);
}
if self.current_snapshot_version() != timestamp.snapshot().version() {
self.snapshot = None;
}
}
self.timestamp = Some(signed_timestamp);
Ok(true)
}
/// Verify and update the snapshot metadata.
pub fn update_snapshot(
&mut self,
signed_snapshot: SignedMetadata<D, SnapshotMetadata>,
) -> Result<bool> {
{
let root = self.safe_root_ref()?;
let timestamp = self.safe_timestamp_ref()?;
let current_version = self.current_snapshot_version();
if timestamp.snapshot().version() < current_version {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back snapshot metadata at version {} to {}.",
current_version,
timestamp.snapshot().version()
)));
} else if timestamp.snapshot().version() == current_version {
return Ok(false);
}
signed_snapshot.verify(
root.snapshot().threshold(),
self.root.as_ref().keys().iter().filter_map(|(k, v)| {
if root.snapshot().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
let snapshot = signed_snapshot.as_ref();
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.",
timestamp.snapshot().version(),
snapshot.version()
)));
}
// 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.
};
if self
.targets
.as_ref()
.map(|s| s.as_ref().version())
.unwrap_or(0)
!= signed_snapshot
.as_ref()
.meta()
.get(&MetadataPath::from_role(&Role::Targets))
.map(|m| m.version())
.unwrap_or(0)
{
self.targets = None;
}
self.snapshot = Some(signed_snapshot);
self.purge_delegations();
Ok(true)
}
fn purge_delegations(&mut self) {
let purge = {
let snapshot = match self.snapshot() {
Some(s) => s,
None => return,
};
let mut purge = HashSet::new();
for (role, definition) in snapshot.meta().iter() {
let delegation = match self.delegations.get(role) {
Some(d) => d,
None => continue,
};
if delegation.as_ref().version() > definition.version() {
let _ = purge.insert(role.clone());
continue;
}
}
purge
};
for role in &purge {
let _ = self.delegations.remove(role);
}
}
/// Verify and update the targets metadata.
pub fn update_targets(
&mut self,
signed_targets: SignedMetadata<D, TargetsMetadata>,
) -> Result<bool> {
{
let root = self.safe_root_ref()?;
let snapshot = self.safe_snapshot_ref()?;
let targets_description = snapshot
.meta()
.get(&MetadataPath::from_role(&Role::Targets))
.ok_or_else(|| {
Error::VerificationFailure(
"Snapshot metadata had no description of the targets metadata".into(),
)
})?;
let current_version = self.current_targets_version();
if targets_description.version() < current_version {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back targets metadata at version {} to {}.",
current_version,
targets_description.version()
)));
} else if targets_description.version() == current_version {
return Ok(false);
}
signed_targets.verify(
root.targets().threshold(),
root.keys().iter().filter_map(|(k, v)| {
if root.targets().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
let targets = signed_targets.as_ref();
if targets.version() != targets_description.version() {
return Err(Error::VerificationFailure(format!(
"The timestamp metadata reported that the targets metadata should be at \
version {} but version {} was found instead.",
targets_description.version(),
targets.version()
)));
}
if targets.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Snapshot));
}
}
self.targets = Some(signed_targets);
Ok(true)
}
/// Verify and update a delegation metadata.
pub fn update_delegation(
&mut self,
role: &MetadataPath,
signed_delegation: SignedMetadata<D, TargetsMetadata>,
) -> Result<bool> {
{
let _ = self.safe_root_ref()?;
let snapshot = self.safe_snapshot_ref()?;
let targets = self.safe_targets_ref()?;
let targets_delegations = match targets.delegations() {
Some(d) => d,
None => {
return Err(Error::VerificationFailure(
"Delegations not authorized".into(),
));
}
};
let delegation_description = match snapshot.meta().get(role) {
Some(d) => d,
None => {
return Err(Error::VerificationFailure(format!(
"The degated role {:?} was not present in the snapshot metadata.",
role
)));
}
};
let current_version = self.current_delegation_version(role);
if delegation_description.version() < current_version {
return Err(Error::VerificationFailure(format!(
"Snapshot metadata did listed delegation {:?} version as {} but current\
version is {}",
role,
delegation_description.version(),
current_version
)));
} else if current_version == delegation_description.version() {
return Ok(false);
}
for delegated_targets in self.delegations.values() {
let parent = match delegated_targets.as_ref().delegations() {
Some(d) => d,
None => &targets_delegations,
};
let delegation = match parent.roles().iter().find(|r| r.role() == role) {
Some(d) => d,
None => continue,
};
signed_delegation.verify(
delegation.threshold(),
parent.keys().iter().filter_map(|(k, v)| {
if delegation.key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?;
}
let delegation = signed_delegation.as_ref();
if delegation.version() != delegation_description.version() {
return Err(Error::VerificationFailure(format!(
"The snapshot metadata reported that the delegation {:?} should be at \
version {} but version {} was found instead.",
role,
delegation_description.version(),
delegation.version(),
)));
}
if delegation.expires() <= &Utc::now() {
// TODO this needs to be chagned to accept a MetadataPath and not Role
return Err(Error::ExpiredMetadata(Role::Targets));
}
}
let _ = self.delegations.insert(role.clone(), signed_delegation);
Ok(true)
}
/// Get a reference to the description needed to verify the target defined by the given
/// `VirtualTargetPath`. Returns an `Error` if the target is not defined in the trusted
/// metadata. This may mean the target exists somewhere in the metadata, but the chain of trust
/// to that target may be invalid or incomplete.
pub fn target_description(&self, target_path: &VirtualTargetPath) -> Result<TargetDescription> {
let _ = self.safe_root_ref()?;
let _ = self.safe_snapshot_ref()?;
let targets = self.safe_targets_ref()?;
if let Some(d) = targets.targets().get(target_path) {
return Ok(d.clone());
}
fn lookup<D: DataInterchange>(
tuf: &Tuf<D>,
default_terminate: bool,
current_depth: u32,
target_path: &VirtualTargetPath,
delegations: &Delegations,
parents: &[HashSet<VirtualTargetPath>],
visited: &mut HashSet<MetadataPath>,
) -> (bool, Option<TargetDescription>) {
for delegation in delegations.roles() {
if visited.contains(delegation.role()) {
return (delegation.terminating(), None);
}
let _ = visited.insert(delegation.role().clone());
let mut new_parents = parents.to_owned();
new_parents.push(delegation.paths().clone());
if current_depth > 0 && !target_path.matches_chain(&parents) {
return (delegation.terminating(), None);
}
let targets = match tuf.delegations.get(delegation.role()) {
Some(t) => t.as_ref(),
None => return (delegation.terminating(), None),
};
if targets.expires() <= &Utc::now() {
return (delegation.terminating(), None);
}
if let Some(d) = targets.targets().get(target_path) {
return (delegation.terminating(), Some(d.clone()));
}
if let Some(d) = targets.delegations() {
let mut new_parents = parents.to_vec();
new_parents.push(delegation.paths().clone());
let (term, res) = lookup(
tuf,
delegation.terminating(),
current_depth + 1,
target_path,
d,
&new_parents,
visited,
);
if term {
return (true, res);
} else if res.is_some() {
return (term, res);
}
}
}
(default_terminate, None)
}
match targets.delegations() {
Some(d) => {
let mut visited = HashSet::new();
lookup(self, false, 0, target_path, d, &[], &mut visited)
.1
.ok_or_else(|| Error::TargetUnavailable)
}
None => Err(Error::TargetUnavailable),
}
}
fn purge_metadata(&mut self) {
self.snapshot = None;
self.targets = None;
self.timestamp = None;
self.delegations.clear();
}
fn safe_root_ref(&self) -> Result<&RootMetadata> {
let root = self.root.as_ref();
if root.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Root));
}
Ok(&root)
}
fn safe_snapshot_ref(&self) -> Result<&SnapshotMetadata> {
match self.snapshot {
Some(ref snapshot) => {
let snapshot = snapshot.as_ref();
if snapshot.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Snapshot));
}
Ok(snapshot)
}
None => Err(Error::MissingMetadata(Role::Snapshot)),
}
}
fn safe_targets_ref(&self) -> Result<&TargetsMetadata> {
match self.targets {
Some(ref targets) => {
let targets = targets.as_ref();
if targets.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Targets));
}
Ok(targets)
}
None => Err(Error::MissingMetadata(Role::Targets)),
}
}
fn safe_timestamp_ref(&self) -> Result<&TimestampMetadata> {
match self.timestamp {
Some(ref timestamp) => {
let timestamp = timestamp.as_ref();
if timestamp.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Timestamp));
}
Ok(timestamp)
}
None => Err(Error::MissingMetadata(Role::Timestamp)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::crypto::{HashAlgorithm, PrivateKey, SignatureScheme};
use crate::interchange::Json;
use crate::metadata::{
RootMetadataBuilder, SnapshotMetadataBuilder, TargetsMetadataBuilder,
TimestampMetadataBuilder,
};
use lazy_static::lazy_static;
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, SignatureScheme::Ed25519).unwrap())
.collect()
};
}
#[test]
fn root_pinned_success() {
let root_key = &KEYS[0];
let root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[0].public().clone())
.targets_key(KEYS[0].public().clone())
.timestamp_key(KEYS[0].public().clone())
.signed::<Json>(&root_key)
.unwrap();
assert!(Tuf::from_root_pinned(root, &[root_key.key_id().clone()]).is_ok());
}
#[test]
fn root_pinned_failure() {
let root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[0].public().clone())
.targets_key(KEYS[0].public().clone())
.timestamp_key(KEYS[0].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap();
assert!(Tuf::from_root_pinned(root, &[KEYS[1].key_id().clone()]).is_err());
}
#[test]
fn good_root_rotation() {
let root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[0].public().clone())
.targets_key(KEYS[0].public().clone())
.timestamp_key(KEYS[0].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let mut root = RootMetadataBuilder::new()
.version(2)
.root_key(KEYS[1].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[1].public().clone())
.timestamp_key(KEYS[1].public().clone())
.signed::<Json>(&KEYS[1])
.unwrap();
// add the original key's signature to make it cross signed
root.add_signature(&KEYS[0]).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 = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[0].public().clone())
.targets_key(KEYS[0].public().clone())
.timestamp_key(KEYS[0].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let root = RootMetadataBuilder::new()
.root_key(KEYS[1].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[1].public().clone())
.timestamp_key(KEYS[1].public().clone())
.signed::<Json>(&KEYS[1])
.unwrap();
assert!(tuf.update_root(root).is_err());
}
#[test]
fn good_timestamp_update() {
let 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::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.signed::<Json>(&KEYS[1])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.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 = 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::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.signed::<Json>(&KEYS[1])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
// sign it with the root key
.signed::<Json>(&KEYS[0])
.unwrap();
assert!(tuf.update_timestamp(timestamp).is_err())
}
#[test]
fn good_snapshot_update() {
let root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[2].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let snapshot = SnapshotMetadataBuilder::new().signed(&KEYS[1]).unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap();
tuf.update_timestamp(timestamp).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 = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[2].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.signed::<Json>(&KEYS[2])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
// sign it with the targets key
.signed::<Json>(&KEYS[2])
.unwrap();
tuf.update_timestamp(timestamp).unwrap();
assert!(tuf.update_snapshot(snapshot).is_err());
}
#[test]
fn bad_snapshot_update_wrong_version() {
let root = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.snapshot_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.timestamp_key(KEYS[2].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.version(2)
.signed::<Json>(&KEYS[2])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap();
tuf.update_timestamp(timestamp).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.version(1)
.signed::<Json>(&KEYS[1])
.unwrap();
assert!(tuf.update_snapshot(snapshot).is_err());
}
#[test]
fn good_targets_update() {
let 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::<Json>(&KEYS[0])
.unwrap();
let targets = TargetsMetadataBuilder::new()
.signed::<Json>(&KEYS[2])
.unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.insert_metadata(&targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
tuf.update_timestamp(timestamp).unwrap();
tuf.update_snapshot(snapshot).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 = 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::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let targets = TargetsMetadataBuilder::new()
// sign it with the timestamp key
.signed::<Json>(&KEYS[3])
.unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.insert_metadata(&targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap();
tuf.update_timestamp(timestamp).unwrap();
tuf.update_snapshot(snapshot).unwrap();
assert!(tuf.update_targets(targets).is_err());
}
#[test]
fn bad_targets_update_wrong_version() {
let 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::<Json>(&KEYS[0])
.unwrap();
let mut tuf = Tuf::from_root(root).unwrap();
let targets = TargetsMetadataBuilder::new()
.version(2)
.signed::<Json>(&KEYS[2])
.unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.insert_metadata(&targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.unwrap();
let timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap();
tuf.update_timestamp(timestamp).unwrap();
tuf.update_snapshot(snapshot).unwrap();
let targets = TargetsMetadataBuilder::new()
.version(1)
.signed::<Json>(&KEYS[2])
.unwrap();
assert!(tuf.update_targets(targets).is_err());
}
}