blob: 5dd3e8ed4fff886259cabaa88e96c41c656ab9ab [file] [log] [blame]
//! Components needed to verify TUF metadata and targets.
use chrono::offset::Utc;
use std::collections::HashSet;
use std::marker::PhantomData;
use Result;
use crypto::KeyId;
use error::Error;
use interchange::DataInterchange;
use metadata::{SignedMetadata, RootMetadata, VerificationStatus, TimestampMetadata, Role,
SnapshotMetadata, MetadataPath, TargetsMetadata, TargetPath, TargetDescription};
/// Contains trusted TUF metadata and can be used to verify other metadata and targets.
#[derive(Debug)]
pub struct Tuf<D: DataInterchange> {
root: RootMetadata,
snapshot: Option<SnapshotMetadata>,
targets: Option<TargetsMetadata>,
timestamp: Option<TimestampMetadata>,
_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<V>(
mut signed_root: SignedMetadata<D, RootMetadata, V>,
root_key_ids: &[KeyId],
) -> Result<Self>
where
V: VerificationStatus,
{
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<V>(signed_root: SignedMetadata<D, RootMetadata, V>) -> Result<Self>
where
V: VerificationStatus,
{
let root = D::deserialize::<RootMetadata>(signed_root.unverified_signed())?;
let _ = signed_root.verify(
root.root().threshold(),
root.root().key_ids(),
root.keys(),
)?;
Ok(Tuf {
root: root,
snapshot: None,
targets: None,
timestamp: None,
_interchange: PhantomData,
})
}
/// An immutable reference to the root metadata.
pub fn root(&self) -> &RootMetadata {
&self.root
}
/// An immutable reference to the optional snapshot metadata.
pub fn snapshot(&self) -> Option<&SnapshotMetadata> {
self.snapshot.as_ref()
}
/// An immutable reference to the optional targets metadata.
pub fn targets(&self) -> Option<&TargetsMetadata> {
self.targets.as_ref()
}
/// An immutable reference to the optional timestamp metadata.
pub fn timestamp(&self) -> Option<&TimestampMetadata> {
self.timestamp.as_ref()
}
/// Return the list of all available targets.
pub fn available_targets(&self) -> Result<HashSet<&TargetPath>> {
let _ = self.safe_root_ref()?; // ensure root still valid
// TODO add delegations
Ok(
self.safe_targets_ref()?
.targets()
.keys()
.collect::<HashSet<&TargetPath>>(),
)
}
/// Verify and update the root metadata.
pub fn update_root<V>(
&mut self,
signed_root: SignedMetadata<D, RootMetadata, V>,
) -> Result<bool>
where
V: VerificationStatus,
{
let signed_root = signed_root.verify(
self.root.root().threshold(),
self.root.root().key_ids(),
self.root.keys(),
)?;
let root = D::deserialize::<RootMetadata>(signed_root.unverified_signed())?;
match root.version() {
x if x == self.root.version() => {
info!(
"Attempted to update root to new metadata with the same version. \
Refusing to update."
);
return Ok(false);
}
x if x < self.root.version() => {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back root metadata at version {} to {}.",
self.root.version(),
x
)))
}
_ => (),
}
let _ = signed_root.verify(
root.root().threshold(),
root.root().key_ids(),
root.keys(),
)?;
self.purge_metadata();
self.root = root;
Ok(true)
}
/// Verify and update the timestamp metadata.
pub fn update_timestamp<V>(
&mut self,
signed_timestamp: SignedMetadata<D, TimestampMetadata, V>,
) -> Result<bool>
where
V: VerificationStatus,
{
let signed_timestamp = signed_timestamp.verify(
self.root.timestamp().threshold(),
self.root.timestamp().key_ids(),
self.root.keys(),
)?;
let current_version = self.timestamp.as_ref().map(|t| t.version()).unwrap_or(0);
let timestamp: TimestampMetadata = D::deserialize(&signed_timestamp.signed())?;
if timestamp.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Timestamp));
}
if timestamp.version() < current_version {
Err(Error::VerificationFailure(format!(
"Attempted to roll back timestamp metadata at version {} to {}.",
current_version,
timestamp.version()
)))
} else if timestamp.version() == current_version {
Ok(false)
} else {
self.timestamp = Some(timestamp);
Ok(true)
}
}
/// Verify and update the snapshot metadata.
pub fn update_snapshot<V>(
&mut self,
signed_snapshot: SignedMetadata<D, SnapshotMetadata, V>,
) -> Result<bool>
where
V: VerificationStatus,
{
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 {
return Err(Error::VerificationFailure(format!(
"Attempted to roll back snapshot metadata at version {} to {}.",
current_version,
snapshot_description.version()
)));
} else if snapshot_description.version() == current_version {
return Ok(false);
}
let signed_snapshot = signed_snapshot.verify(
root.snapshot().threshold(),
root.snapshot().key_ids(),
root.keys(),
)?;
let snapshot: SnapshotMetadata = D::deserialize(&signed_snapshot.signed())?;
if snapshot.version() != snapshot_description.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(),
snapshot.version()
)));
}
if snapshot.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Snapshot));
}
snapshot
};
self.snapshot = Some(snapshot);
Ok(true)
}
/// Verify and update the targets metadata.
pub fn update_targets<V>(
&mut self,
signed_targets: SignedMetadata<D, TargetsMetadata, V>,
) -> Result<bool>
where
V: VerificationStatus,
{
let targets = {
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.targets.as_ref().map(|t| t.version()).unwrap_or(0);
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);
}
let signed_targets = signed_targets.verify(
root.targets().threshold(),
root.targets().key_ids(),
root.keys(),
)?;
let targets: TargetsMetadata = D::deserialize(&signed_targets.signed())?;
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));
}
targets
};
self.targets = Some(targets);
Ok(true)
}
/// Get a reference to the description needed to verify the target defined by the given
/// `TargetPath`. 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: &TargetPath) -> Result<&TargetDescription> {
let _ = self.safe_root_ref()?;
let _ = self.safe_snapshot_ref()?;
let targets = self.safe_targets_ref()?;
targets.targets().get(target_path).ok_or(
Error::TargetUnavailable,
)
// TODO include searching delegations
}
fn purge_metadata(&mut self) {
self.snapshot = None;
self.targets = None;
self.timestamp = None;
// TODO include delegations
}
fn safe_root_ref(&self) -> Result<&RootMetadata> {
if self.root.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Root));
}
Ok(&self.root)
}
fn safe_snapshot_ref(&self) -> Result<&SnapshotMetadata> {
match &self.snapshot {
&Some(ref snapshot) => {
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) => {
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) => {
if timestamp.expires() <= &Utc::now() {
return Err(Error::ExpiredMetadata(Role::Timestamp));
}
Ok(timestamp)
}
&None => Err(Error::MissingMetadata(Role::Timestamp)),
}
}
}