use chrono::{offset::Utc, DateTime};
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
use crate::crypto::PublicKey;
use crate::error::Error;
use crate::interchange::DataInterchange;
use crate::metadata::{
Delegations, Metadata, MetadataPath, MetadataVersion, RawSignedMetadata, RawSignedMetadataSet,
RootMetadata, SnapshotMetadata, TargetDescription, TargetPath, TargetsMetadata,
TimestampMetadata,
};
use crate::verify::{self, Verified};
use crate::Result;
#[derive(Clone, Debug)]
pub struct Database<D: DataInterchange> {
trusted_root: Verified<RootMetadata>,
trusted_snapshot: Option<Verified<SnapshotMetadata>>,
trusted_targets: Option<Verified<TargetsMetadata>>,
trusted_timestamp: Option<Verified<TimestampMetadata>>,
trusted_delegations: HashMap<MetadataPath, Verified<TargetsMetadata>>,
interchange: PhantomData<D>,
}
impl<D: DataInterchange> Database<D> {
pub fn from_root_with_trusted_keys<'a, I>(
raw_root: &RawSignedMetadata<D, RootMetadata>,
root_threshold: u32,
root_keys: I,
) -> Result<Self>
where
I: IntoIterator<Item = &'a PublicKey>,
{
let verified_root = {
let new_root = verify::verify_signatures(
&MetadataPath::root(),
raw_root,
root_threshold,
root_keys,
)?;
verify::verify_signatures(
&MetadataPath::root(),
raw_root,
new_root.root().threshold(),
new_root.keys().iter().filter_map(|(k, v)| {
if new_root.root().key_ids().contains(k) {
Some(v)
} else {
None
}
}),
)?
};
Ok(Database {
trusted_root: verified_root,
trusted_snapshot: None,
trusted_targets: None,
trusted_timestamp: None,
trusted_delegations: HashMap::new(),
interchange: PhantomData,
})
}
pub fn from_trusted_root(raw_root: &RawSignedMetadata<D, RootMetadata>) -> Result<Self> {
let verified_root = {
let unverified_root = raw_root.parse_untrusted()?.assume_valid()?;
verify::verify_signatures(
&MetadataPath::root(),
raw_root,
unverified_root.root().threshold(),
unverified_root.root_keys(),
)?
};
Ok(Database {
trusted_root: verified_root,
trusted_snapshot: None,
trusted_targets: None,
trusted_timestamp: None,
trusted_delegations: HashMap::new(),
interchange: PhantomData,
})
}
pub fn from_metadata_with_trusted_keys<'a, I>(
metadata_set: &RawSignedMetadataSet<D>,
root_threshold: u32,
root_keys: I,
) -> Result<Self>
where
I: IntoIterator<Item = &'a PublicKey>,
{
Self::from_metadata_with_trusted_keys_and_start_time(
&Utc::now(),
metadata_set,
root_threshold,
root_keys,
)
}
pub fn from_metadata_with_trusted_keys_and_start_time<'a, I>(
start_time: &DateTime<Utc>,
metadata_set: &RawSignedMetadataSet<D>,
root_threshold: u32,
root_keys: I,
) -> Result<Self>
where
I: IntoIterator<Item = &'a PublicKey>,
{
let mut db = if let Some(root) = metadata_set.root() {
Database::from_root_with_trusted_keys(root, root_threshold, root_keys)?
} else {
return Err(Error::MetadataNotFound {
path: MetadataPath::root(),
version: MetadataVersion::None,
});
};
db.update_metadata_after_root(start_time, metadata_set)?;
Ok(db)
}
pub fn from_trusted_metadata(metadata_set: &RawSignedMetadataSet<D>) -> Result<Self> {
Self::from_trusted_metadata_with_start_time(metadata_set, &Utc::now())
}
pub fn from_trusted_metadata_with_start_time(
metadata_set: &RawSignedMetadataSet<D>,
start_time: &DateTime<Utc>,
) -> Result<Self> {
let mut db = if let Some(root) = metadata_set.root() {
Database::from_trusted_root(root)?
} else {
return Err(Error::MetadataNotFound {
path: MetadataPath::root(),
version: MetadataVersion::None,
});
};
db.update_metadata_after_root(start_time, metadata_set)?;
Ok(db)
}
pub fn trusted_root(&self) -> &Verified<RootMetadata> {
&self.trusted_root
}
pub fn trusted_targets(&self) -> Option<&Verified<TargetsMetadata>> {
self.trusted_targets.as_ref()
}
pub fn trusted_snapshot(&self) -> Option<&Verified<SnapshotMetadata>> {
self.trusted_snapshot.as_ref()
}
pub fn trusted_timestamp(&self) -> Option<&Verified<TimestampMetadata>> {
self.trusted_timestamp.as_ref()
}
pub fn trusted_delegations(&self) -> &HashMap<MetadataPath, Verified<TargetsMetadata>> {
&self.trusted_delegations
}
pub fn update_metadata(&mut self, metadata: &RawSignedMetadataSet<D>) -> Result<bool> {
self.update_metadata_with_start_time(metadata, &Utc::now())
}
pub fn update_metadata_with_start_time(
&mut self,
metadata: &RawSignedMetadataSet<D>,
start_time: &DateTime<Utc>,
) -> Result<bool> {
let updated = if let Some(root) = metadata.root() {
self.update_root(root)?;
true
} else {
false
};
if self.update_metadata_after_root(start_time, metadata)? {
Ok(true)
} else {
Ok(updated)
}
}
fn update_metadata_after_root(
&mut self,
start_time: &DateTime<Utc>,
metadata_set: &RawSignedMetadataSet<D>,
) -> Result<bool> {
let mut updated = false;
if let Some(timestamp) = metadata_set.timestamp() {
if self.update_timestamp(start_time, timestamp)?.is_some() {
updated = true;
}
}
if let Some(snapshot) = metadata_set.snapshot() {
if self.update_snapshot(start_time, snapshot)? {
updated = true;
}
}
if let Some(targets) = metadata_set.targets() {
if self.update_targets(start_time, targets)? {
updated = true;
}
}
Ok(updated)
}
pub fn update_root(&mut self, raw_root: &RawSignedMetadata<D, RootMetadata>) -> Result<()> {
let verified = {
let trusted_root = &self.trusted_root;
let new_root = verify::verify_signatures(
&MetadataPath::root(),
raw_root,
trusted_root.root().threshold(),
trusted_root.root_keys(),
)?;
let new_root = verify::verify_signatures(
&MetadataPath::root(),
raw_root,
new_root.root().threshold(),
new_root.root_keys(),
)?;
let next_root_version = trusted_root.version().checked_add(1).ok_or_else(|| {
Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::root())
})?;
if new_root.version() != next_root_version {
return Err(Error::AttemptedMetadataRollBack {
role: MetadataPath::root(),
trusted_version: trusted_root.version(),
new_version: new_root.version(),
});
}
new_root
};
self.purge_metadata();
self.trusted_root = verified;
Ok(())
}
pub fn update_timestamp(
&mut self,
start_time: &DateTime<Utc>,
raw_timestamp: &RawSignedMetadata<D, TimestampMetadata>,
) -> Result<Option<&Verified<TimestampMetadata>>> {
let verified = {
let trusted_root = &self.trusted_root;
let new_timestamp = verify::verify_signatures(
&MetadataPath::timestamp(),
raw_timestamp,
trusted_root.timestamp().threshold(),
trusted_root.timestamp_keys(),
)?;
if let Some(trusted_timestamp) = &self.trusted_timestamp {
match new_timestamp.version().cmp(&trusted_timestamp.version()) {
Ordering::Less => {
return Err(Error::AttemptedMetadataRollBack {
role: MetadataPath::timestamp(),
trusted_version: trusted_timestamp.version(),
new_version: new_timestamp.version(),
});
}
Ordering::Equal => {
return Ok(None);
}
Ordering::Greater => {}
}
}
if let Some(trusted_snapshot) = &self.trusted_snapshot {
if trusted_snapshot.version() != new_timestamp.snapshot().version() {
self.trusted_snapshot = None;
}
}
if new_timestamp.expires() <= start_time {
return Err(Error::ExpiredMetadata(MetadataPath::timestamp()));
}
new_timestamp
};
self.trusted_timestamp = Some(verified);
Ok(self.trusted_timestamp.as_ref())
}
pub fn update_snapshot(
&mut self,
start_time: &DateTime<Utc>,
raw_snapshot: &RawSignedMetadata<D, SnapshotMetadata>,
) -> Result<bool> {
let verified = {
let trusted_root = self.trusted_root_unexpired(start_time)?;
let trusted_timestamp = self.trusted_timestamp_unexpired(start_time)?;
if let Some(trusted_snapshot) = &self.trusted_snapshot {
match trusted_timestamp
.snapshot()
.version()
.cmp(&trusted_snapshot.version())
{
Ordering::Less => {
return Err(Error::AttemptedMetadataRollBack {
role: MetadataPath::snapshot(),
trusted_version: trusted_snapshot.version(),
new_version: trusted_timestamp.snapshot().version(),
});
}
Ordering::Equal => {
return Ok(false);
}
Ordering::Greater => {}
}
}
let new_snapshot = verify::verify_signatures(
&MetadataPath::snapshot(),
raw_snapshot,
trusted_root.snapshot().threshold(),
trusted_root.snapshot_keys(),
)?;
if new_snapshot.version() != trusted_timestamp.snapshot().version() {
return Err(Error::WrongMetadataVersion {
parent_role: MetadataPath::timestamp(),
child_role: MetadataPath::snapshot(),
expected_version: trusted_timestamp.snapshot().version(),
new_version: new_snapshot.version(),
});
}
if let Some(trusted_snapshot) = &self.trusted_snapshot {
if new_snapshot.version() < trusted_snapshot.version() {
return Err(Error::AttemptedMetadataRollBack {
role: MetadataPath::snapshot(),
trusted_version: trusted_snapshot.version(),
new_version: new_snapshot.version(),
});
}
}
new_snapshot
};
if self
.trusted_targets
.as_ref()
.map(|s| s.version())
.unwrap_or(0)
!= verified
.meta()
.get(&MetadataPath::targets())
.map(|m| m.version())
.unwrap_or(0)
{
self.trusted_targets = None;
}
self.trusted_snapshot = Some(verified);
self.purge_delegations();
Ok(true)
}
fn purge_delegations(&mut self) {
let purge = {
let trusted_snapshot = match self.trusted_snapshot() {
Some(s) => s,
None => return,
};
let mut purge = HashSet::new();
for (role, trusted_definition) in trusted_snapshot.meta().iter() {
let trusted_delegation = match self.trusted_delegations.get(role) {
Some(d) => d,
None => continue,
};
if trusted_delegation.version() > trusted_definition.version() {
let _ = purge.insert(role.clone());
continue;
}
}
purge
};
for role in &purge {
let _ = self.trusted_delegations.remove(role);
}
}
pub fn update_targets(
&mut self,
start_time: &DateTime<Utc>,
raw_targets: &RawSignedMetadata<D, TargetsMetadata>,
) -> Result<bool> {
let verified = {
let trusted_root = self.trusted_root_unexpired(start_time)?;
let trusted_targets_version = self.trusted_targets.as_ref().map(|t| t.version());
self.verify_target_or_delegated_target(
start_time,
&MetadataPath::targets(),
raw_targets,
trusted_root.targets().threshold(),
trusted_root.targets_keys(),
trusted_targets_version,
)?
};
if let Some(verified) = verified {
self.trusted_targets = Some(verified);
Ok(true)
} else {
Ok(false)
}
}
pub fn update_delegated_targets(
&mut self,
start_time: &DateTime<Utc>,
parent_role: &MetadataPath,
role: &MetadataPath,
raw_delegated_targets: &RawSignedMetadata<D, TargetsMetadata>,
) -> Result<bool> {
let verified = {
let _ = self.trusted_root_unexpired(start_time)?;
let _ = self.trusted_snapshot_unexpired(start_time)?;
let trusted_targets = self.trusted_targets_unexpired(start_time)?;
if trusted_targets.delegations().is_none() {
return Err(Error::UnauthorizedDelegation {
parent_role: parent_role.clone(),
child_role: role.clone(),
});
};
let (threshold, keys) = self
.find_delegation_threshold_and_keys(parent_role, role)?
.ok_or_else(|| Error::UnauthorizedDelegation {
parent_role: parent_role.clone(),
child_role: role.clone(),
})?;
let trusted_delegated_targets_version =
self.trusted_delegations.get(role).map(|t| t.version());
self.verify_target_or_delegated_target(
start_time,
role,
raw_delegated_targets,
threshold,
keys.into_iter(),
trusted_delegated_targets_version,
)?
};
if let Some(verified) = verified {
let _ = self.trusted_delegations.insert(role.clone(), verified);
Ok(true)
} else {
Ok(false)
}
}
fn verify_target_or_delegated_target<'a>(
&self,
start_time: &DateTime<Utc>,
role: &MetadataPath,
raw_targets: &RawSignedMetadata<D, TargetsMetadata>,
trusted_targets_threshold: u32,
trusted_targets_keys: impl Iterator<Item = &'a PublicKey>,
trusted_targets_version: Option<u32>,
) -> Result<Option<Verified<TargetsMetadata>>> {
let trusted_snapshot = self.trusted_snapshot_unexpired(start_time)?;
let trusted_targets_description =
trusted_snapshot
.meta()
.get(role)
.ok_or_else(|| Error::MissingMetadataDescription {
parent_role: MetadataPath::snapshot(),
child_role: role.clone(),
})?;
let new_targets = verify::verify_signatures(
role,
raw_targets,
trusted_targets_threshold,
trusted_targets_keys,
)?;
if new_targets.version() != trusted_targets_description.version() {
return Err(Error::WrongMetadataVersion {
parent_role: MetadataPath::snapshot(),
child_role: role.clone(),
expected_version: trusted_targets_description.version(),
new_version: new_targets.version(),
});
}
if let Some(trusted_targets_version) = trusted_targets_version {
match new_targets.version().cmp(&trusted_targets_version) {
Ordering::Less => {
return Err(Error::AttemptedMetadataRollBack {
role: role.clone(),
trusted_version: trusted_targets_version,
new_version: new_targets.version(),
});
}
Ordering::Equal => {
return Ok(None);
}
Ordering::Greater => {}
}
}
if new_targets.expires() <= start_time {
return Err(Error::ExpiredMetadata(role.clone()));
}
Ok(Some(new_targets))
}
fn find_delegation_threshold_and_keys(
&self,
parent_role: &MetadataPath,
role: &MetadataPath,
) -> Result<Option<(u32, Vec<&PublicKey>)>> {
let trusted_parent = if parent_role == &MetadataPath::targets() {
if let Some(trusted_targets) = self.trusted_targets() {
trusted_targets
} else {
return Err(Error::MetadataNotFound {
path: parent_role.clone(),
version: MetadataVersion::None,
});
}
} else if let Some(trusted_parent) = self.trusted_delegations.get(parent_role) {
trusted_parent
} else {
return Err(Error::MetadataNotFound {
path: parent_role.clone(),
version: MetadataVersion::None,
});
};
let trusted_delegations = match trusted_parent.delegations() {
Some(d) => d,
None => return Ok(None),
};
for trusted_delegation in trusted_delegations.roles() {
if trusted_delegation.role() != role {
continue;
}
let authorized_keys = trusted_delegations
.keys()
.iter()
.filter_map(|(k, v)| {
if trusted_delegation.key_ids().contains(k) {
Some(v)
} else {
None
}
})
.collect();
return Ok(Some((trusted_delegation.threshold(), authorized_keys)));
}
Ok(None)
}
pub fn target_description(&self, target_path: &TargetPath) -> Result<TargetDescription> {
self.target_description_with_start_time(&Utc::now(), target_path)
}
pub fn target_description_with_start_time(
&self,
start_time: &DateTime<Utc>,
target_path: &TargetPath,
) -> Result<TargetDescription> {
let _ = self.trusted_root_unexpired(start_time)?;
let _ = self.trusted_snapshot_unexpired(start_time)?;
let targets = self.trusted_targets_unexpired(start_time)?;
if let Some(d) = targets.targets().get(target_path) {
return Ok(d.clone());
}
fn lookup<D: DataInterchange>(
start_time: &DateTime<Utc>,
tuf: &Database<D>,
default_terminate: bool,
current_depth: u32,
target_path: &TargetPath,
delegations: &Delegations,
parents: &[HashSet<TargetPath>],
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 trusted_delegation = match tuf.trusted_delegations.get(delegation.role()) {
Some(trusted_delegation) => trusted_delegation,
None => return (delegation.terminating(), None),
};
if trusted_delegation.expires() <= start_time {
return (delegation.terminating(), None);
}
if let Some(target) = trusted_delegation.targets().get(target_path) {
return (delegation.terminating(), Some(target.clone()));
}
if let Some(trusted_child_delegation) = trusted_delegation.delegations() {
let mut new_parents = parents.to_vec();
new_parents.push(delegation.paths().clone());
let (term, res) = lookup(
start_time,
tuf,
delegation.terminating(),
current_depth + 1,
target_path,
trusted_child_delegation,
&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(
start_time,
self,
false,
0,
target_path,
d,
&[],
&mut visited,
)
.1
.ok_or_else(|| Error::TargetNotFound(target_path.clone()))
}
None => Err(Error::TargetNotFound(target_path.clone())),
}
}
fn purge_metadata(&mut self) {
self.trusted_snapshot = None;
self.trusted_targets = None;
self.trusted_timestamp = None;
self.trusted_delegations.clear();
}
fn trusted_root_unexpired(&self, start_time: &DateTime<Utc>) -> Result<&RootMetadata> {
let trusted_root = &self.trusted_root;
if trusted_root.expires() <= start_time {
return Err(Error::ExpiredMetadata(MetadataPath::root()));
}
Ok(trusted_root)
}
fn trusted_timestamp_unexpired(
&self,
start_time: &DateTime<Utc>,
) -> Result<&TimestampMetadata> {
match self.trusted_timestamp {
Some(ref trusted_timestamp) => {
if trusted_timestamp.expires() <= start_time {
return Err(Error::ExpiredMetadata(MetadataPath::timestamp()));
}
Ok(trusted_timestamp)
}
None => Err(Error::MetadataNotFound {
path: MetadataPath::timestamp(),
version: MetadataVersion::None,
}),
}
}
fn trusted_snapshot_unexpired(&self, start_time: &DateTime<Utc>) -> Result<&SnapshotMetadata> {
match self.trusted_snapshot {
Some(ref trusted_snapshot) => {
if trusted_snapshot.expires() <= start_time {
return Err(Error::ExpiredMetadata(MetadataPath::snapshot()));
}
Ok(trusted_snapshot)
}
None => Err(Error::MetadataNotFound {
path: MetadataPath::snapshot(),
version: MetadataVersion::None,
}),
}
}
fn trusted_targets_unexpired(&self, start_time: &DateTime<Utc>) -> Result<&TargetsMetadata> {
match self.trusted_targets {
Some(ref trusted_targets) => {
if trusted_targets.expires() <= start_time {
return Err(Error::ExpiredMetadata(MetadataPath::targets()));
}
Ok(trusted_targets)
}
None => Err(Error::MetadataNotFound {
path: MetadataPath::targets(),
version: MetadataVersion::None,
}),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::crypto::{Ed25519PrivateKey, HashAlgorithm, PrivateKey};
use crate::interchange::Json;
use crate::metadata::{
RawSignedMetadataSetBuilder, RootMetadataBuilder, SnapshotMetadataBuilder,
TargetsMetadataBuilder, TimestampMetadataBuilder,
};
use assert_matches::assert_matches;
use lazy_static::lazy_static;
use std::iter::once;
lazy_static! {
static ref KEYS: Vec<Ed25519PrivateKey> = {
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| Ed25519PrivateKey::from_pkcs8(b).unwrap())
.collect()
};
}
#[test]
fn root_trusted_keys_success() {
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 raw_root = root.to_raw().unwrap();
assert_matches!(
Database::from_root_with_trusted_keys(&raw_root, 1, once(KEYS[0].public())),
Ok(_)
);
}
#[test]
fn root_trusted_keys_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();
let raw_root = root.to_raw().unwrap();
assert_matches!(
Database::from_root_with_trusted_keys(&raw_root, 1, once(KEYS[1].public())),
Err(Error::MetadataMissingSignatures {
role,
number_of_valid_signatures: 0,
threshold: 1,
})
if role == MetadataPath::root()
);
}
#[test]
fn from_trusted_metadata_success() {
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()
.to_raw()
.unwrap();
let metadata = RawSignedMetadataSetBuilder::new().root(root).build();
assert_matches!(Database::from_trusted_metadata(&metadata), Ok(_));
}
#[test]
fn from_trusted_metadata_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[1])
.unwrap()
.to_raw()
.unwrap();
let metadata = RawSignedMetadataSetBuilder::new().root(root).build();
assert_matches!(
Database::from_trusted_metadata(&metadata),
Err(Error::MetadataMissingSignatures {
role,
number_of_valid_signatures: 0,
threshold: 1,
})
if role == MetadataPath::root()
);
}
#[test]
fn from_metadata_with_trusted_keys_success() {
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()
.to_raw()
.unwrap();
let metadata = RawSignedMetadataSetBuilder::new().root(root).build();
assert_matches!(
Database::from_metadata_with_trusted_keys(&metadata, 1, once(KEYS[0].public())),
Ok(_)
);
}
#[test]
fn from_metadata_with_trusted_keys_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()
.to_raw()
.unwrap();
let metadata = RawSignedMetadataSetBuilder::new().root(root).build();
assert_matches!(
Database::from_metadata_with_trusted_keys(&metadata, 1, once(KEYS[1].public())),
Err(Error::MetadataMissingSignatures {
role,
number_of_valid_signatures: 0,
threshold: 1,
})
if role == MetadataPath::root()
);
}
#[test]
fn good_root_rotation() {
let raw_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()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_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();
root.add_signature(&KEYS[0]).unwrap();
let raw_root = root.to_raw().unwrap();
assert_matches!(tuf.update_root(&raw_root), Ok(()));
assert_matches!(
tuf.update_root(&raw_root),
Err(Error::AttemptedMetadataRollBack { role, trusted_version: 2, new_version: 2 })
if role == MetadataPath::root()
);
}
#[test]
fn no_cross_sign_root_rotation() {
let raw_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()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let raw_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()
.to_raw()
.unwrap();
assert!(tuf.update_root(&raw_root).is_err());
}
#[test]
fn good_timestamp_update() {
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::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_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();
let raw_timestamp = timestamp.to_raw().unwrap();
assert_matches!(
tuf.update_timestamp(&now, &raw_timestamp),
Ok(Some(_parsed_timestamp))
);
assert_matches!(tuf.update_timestamp(&now, &raw_timestamp), Ok(None))
}
#[test]
fn bad_timestamp_update_wrong_key() {
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::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
assert!(tuf.update_timestamp(&now, &raw_timestamp).is_err())
}
#[test]
fn good_snapshot_update() {
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[2].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let snapshot = SnapshotMetadataBuilder::new().signed(&KEYS[1]).unwrap();
let raw_snapshot = snapshot.to_raw().unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap()
.to_raw()
.unwrap();
tuf.update_timestamp(&now, &raw_timestamp).unwrap();
assert_matches!(tuf.update_snapshot(&now, &raw_snapshot), Ok(true));
assert_matches!(tuf.update_snapshot(&now, &raw_snapshot), Ok(false));
}
#[test]
fn bad_snapshot_update_wrong_key() {
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[2].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_snapshot = snapshot.to_raw().unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap()
.to_raw()
.unwrap();
tuf.update_timestamp(&now, &raw_timestamp).unwrap();
assert!(tuf.update_snapshot(&now, &raw_snapshot).is_err());
}
#[test]
fn bad_snapshot_update_wrong_version() {
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[2].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.version(2)
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap()
.to_raw()
.unwrap();
tuf.update_timestamp(&now, &raw_timestamp).unwrap();
let raw_snapshot = SnapshotMetadataBuilder::new()
.version(1)
.signed::<Json>(&KEYS[1])
.unwrap()
.to_raw()
.unwrap();
assert!(tuf.update_snapshot(&now, &raw_snapshot).is_err());
}
#[test]
fn good_targets_update() {
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::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let signed_targets = TargetsMetadataBuilder::new()
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_targets = signed_targets.to_raw().unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.insert_metadata(&signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_snapshot = snapshot.to_raw().unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
tuf.update_timestamp(&now, &raw_timestamp).unwrap();
tuf.update_snapshot(&now, &raw_snapshot).unwrap();
assert_matches!(tuf.update_targets(&now, &raw_targets), Ok(true));
assert_matches!(tuf.update_targets(&now, &raw_targets), Ok(false));
}
#[test]
fn bad_targets_update_wrong_key() {
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::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let signed_targets = TargetsMetadataBuilder::new()
.signed::<Json>(&KEYS[3])
.unwrap();
let raw_targets = signed_targets.to_raw().unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.insert_metadata(&signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_snapshot = snapshot.to_raw().unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
tuf.update_timestamp(&now, &raw_timestamp).unwrap();
tuf.update_snapshot(&now, &raw_snapshot).unwrap();
assert!(tuf.update_targets(&now, &raw_targets).is_err());
}
#[test]
fn bad_targets_update_wrong_version() {
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::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let mut tuf = Database::from_trusted_root(&raw_root).unwrap();
let signed_targets = TargetsMetadataBuilder::new()
.version(2)
.signed::<Json>(&KEYS[2])
.unwrap();
let snapshot = SnapshotMetadataBuilder::new()
.insert_metadata(&signed_targets, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_snapshot = snapshot.to_raw().unwrap();
let raw_timestamp =
TimestampMetadataBuilder::from_snapshot(&snapshot, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
tuf.update_timestamp(&now, &raw_timestamp).unwrap();
tuf.update_snapshot(&now, &raw_snapshot).unwrap();
let raw_targets = TargetsMetadataBuilder::new()
.version(1)
.signed::<Json>(&KEYS[2])
.unwrap()
.to_raw()
.unwrap();
assert!(tuf.update_targets(&now, &raw_targets).is_err());
}
#[test]
fn test_update_metadata_succeeds_with_good_metadata() {
let raw_root1 = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.targets_key(KEYS[1].public().clone())
.snapshot_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let signed_targets1 = TargetsMetadataBuilder::new()
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_targets1 = signed_targets1.to_raw().unwrap();
let snapshot1 = SnapshotMetadataBuilder::new()
.insert_metadata(&signed_targets1, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_snapshot1 = snapshot1.to_raw().unwrap();
let raw_timestamp1 =
TimestampMetadataBuilder::from_snapshot(&snapshot1, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
let metadata1 = RawSignedMetadataSetBuilder::new()
.root(raw_root1)
.targets(raw_targets1)
.snapshot(raw_snapshot1)
.timestamp(raw_timestamp1)
.build();
let mut tuf = Database::from_trusted_metadata(&metadata1).unwrap();
let raw_root2 = RootMetadataBuilder::new()
.version(2)
.root_key(KEYS[0].public().clone())
.targets_key(KEYS[1].public().clone())
.snapshot_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let signed_targets2 = TargetsMetadataBuilder::new()
.version(2)
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_targets2 = signed_targets2.to_raw().unwrap();
let snapshot2 = SnapshotMetadataBuilder::new()
.version(2)
.insert_metadata(&signed_targets2, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_snapshot2 = snapshot2.to_raw().unwrap();
let raw_timestamp2 =
TimestampMetadataBuilder::from_snapshot(&snapshot2, &[HashAlgorithm::Sha256])
.unwrap()
.version(2)
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
let metadata2 = RawSignedMetadataSetBuilder::new()
.root(raw_root2)
.targets(raw_targets2)
.snapshot(raw_snapshot2)
.timestamp(raw_timestamp2)
.build();
assert_matches!(tuf.update_metadata(&metadata2), Ok(true));
}
#[test]
fn test_update_metadata_fails_with_bad_metadata() {
let raw_root1 = RootMetadataBuilder::new()
.root_key(KEYS[0].public().clone())
.targets_key(KEYS[1].public().clone())
.snapshot_key(KEYS[2].public().clone())
.timestamp_key(KEYS[3].public().clone())
.signed::<Json>(&KEYS[0])
.unwrap()
.to_raw()
.unwrap();
let signed_targets1 = TargetsMetadataBuilder::new()
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_targets1 = signed_targets1.to_raw().unwrap();
let snapshot1 = SnapshotMetadataBuilder::new()
.insert_metadata(&signed_targets1, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_snapshot1 = snapshot1.to_raw().unwrap();
let raw_timestamp1 =
TimestampMetadataBuilder::from_snapshot(&snapshot1, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
let metadata1 = RawSignedMetadataSetBuilder::new()
.root(raw_root1)
.targets(raw_targets1)
.snapshot(raw_snapshot1)
.timestamp(raw_timestamp1)
.build();
let mut tuf = Database::from_trusted_metadata(&metadata1).unwrap();
let raw_root2 = RootMetadataBuilder::new()
.version(2)
.root_key(KEYS[1].public().clone())
.targets_key(KEYS[2].public().clone())
.snapshot_key(KEYS[3].public().clone())
.timestamp_key(KEYS[4].public().clone())
.signed::<Json>(&KEYS[1])
.unwrap()
.to_raw()
.unwrap();
let signed_targets2 = TargetsMetadataBuilder::new()
.version(2)
.signed::<Json>(&KEYS[1])
.unwrap();
let raw_targets2 = signed_targets2.to_raw().unwrap();
let snapshot2 = SnapshotMetadataBuilder::new()
.version(2)
.insert_metadata(&signed_targets2, &[HashAlgorithm::Sha256])
.unwrap()
.signed::<Json>(&KEYS[2])
.unwrap();
let raw_snapshot2 = snapshot2.to_raw().unwrap();
let raw_timestamp2 =
TimestampMetadataBuilder::from_snapshot(&snapshot2, &[HashAlgorithm::Sha256])
.unwrap()
.version(2)
.signed::<Json>(&KEYS[3])
.unwrap()
.to_raw()
.unwrap();
let metadata2 = RawSignedMetadataSetBuilder::new()
.root(raw_root2)
.targets(raw_targets2)
.snapshot(raw_snapshot2)
.timestamp(raw_timestamp2)
.build();
assert_matches!(
tuf.update_metadata(&metadata2),
Err(Error::MetadataMissingSignatures {
role,
number_of_valid_signatures: 0,
threshold: 1,
})
if role == MetadataPath::root()
);
}
}