blob: 994605497095b018dcf39b31366d603141e49456 [file] [log] [blame]
//! Clients for high level interactions with TUF repositories.
use chrono::offset::Utc;
use Result;
use crypto;
use error::Error;
use interchange::DataInterchange;
use metadata::{MetadataVersion, RootMetadata, Role, MetadataPath};
use repository::Repository;
use tuf::Tuf;
/// A client that interacts with TUF repositories.
pub struct Client<D, L, R>
where
D: DataInterchange,
L: Repository<D>,
R: Repository<D>,
{
tuf: Tuf<D>,
config: Config,
local: L,
remote: R,
}
impl<D, L, R> Client<D, L, R>
where
D: DataInterchange,
L: Repository<D>,
R: Repository<D>,
{
/// Create a new TUF client from the given `Tuf` (metadata storage) and local and remote
/// repositories.
pub fn new(tuf: Tuf<D>, config: Config, local: L, remote: R) -> Self {
Client {
tuf: tuf,
config: config,
local: local,
remote: remote,
}
}
/// Update TUF metadata from local and remote repositories.
///
/// Returns `true` if an update occurred and `false` otherwise.
// TODO this might need to be split into `update_local` and `update_remote` to be useful to
// implementers.
pub fn update(&mut self) -> Result<bool> {
if self.update_root()? && self.update_timestamp()? {
self.update_snapshot()
} else {
Ok(false)
}
}
/// Returns `true` if an update occurred and `false` otherwise.
fn update_root(&mut self) -> Result<bool> {
let local_updated =
Self::update_root_chain(&mut self.tuf, &self.config.max_root_size, &mut self.local)?;
let remote_updated =
Self::update_root_chain(&mut self.tuf, &self.config.max_root_size, &mut self.remote)?;
if self.tuf.root().expires() <= &Utc::now() {
Err(Error::ExpiredMetadata(Role::Root))
} else {
Ok(local_updated || remote_updated)
}
}
fn update_root_chain<T>(
tuf: &mut Tuf<D>,
max_root_size: &Option<usize>,
repo: &mut T,
) -> Result<bool>
where
T: Repository<D>,
{
let latest_root = repo.fetch_metadata(
&Role::Root,
&MetadataVersion::None,
max_root_size,
None,
)?;
let latest_version = D::deserialize::<RootMetadata>(latest_root.unverified_signed())?
.version();
if latest_version < tuf.root().version() {
return Err(Error::VerificationFailure(format!(
"Latest root version is lower than current root version: {} < {}",
latest_version,
tuf.root().version()
)));
} else if latest_version == tuf.root().version() {
return Ok(false);
}
let err_msg = "TUF claimed no update occurred when one should have. \
This is a programming error. Please report this as a bug.";
for i in (tuf.root().version() + 1)..latest_version {
let signed = repo.fetch_metadata(
&Role::Root,
&MetadataVersion::Number(i),
max_root_size,
None,
)?;
if !tuf.update_root(signed)? {
error!("{}", err_msg);
return Err(Error::Generic(err_msg.into()));
}
}
if !tuf.update_root(latest_root)? {
error!("{}", err_msg);
return Err(Error::Generic(err_msg.into()));
}
Ok(true)
}
/// Returns `true` if an update occurred and `false` otherwise.
fn update_timestamp(&mut self) -> Result<bool> {
let ts = self.local.fetch_metadata(
&Role::Timestamp,
&MetadataVersion::None,
&self.config.max_timestamp_size,
None,
)?;
self.tuf.update_timestamp(ts)?;
let ts = self.remote.fetch_metadata(
&Role::Timestamp,
&MetadataVersion::None,
&self.config.max_timestamp_size,
None,
)?;
self.tuf.update_timestamp(ts)
}
/// Returns `true` if an update occurred and `false` otherwise.
fn update_snapshot(&mut self) -> Result<bool> {
let snapshot_description = match self.tuf.timestamp() {
Some(ts) => {
match ts.meta().get(&MetadataPath::from_role(&Role::Timestamp)) {
Some(d) => Ok(d),
None => Err(Error::VerificationFailure(
"Timestamp metadata did not contain a description of the \
current snapshot metadata"
.into(),
)),
}
}
None => Err(Error::MissingMetadata(Role::Timestamp)),
}?
.clone();
let hashes = match snapshot_description.hashes() {
Some(hashes) => Some(crypto::hash_preference(hashes)?),
None => None,
};
let snap = self.local.fetch_metadata(
&Role::Snapshot,
&MetadataVersion::None,
&snapshot_description.length(),
hashes,
)?;
self.tuf.update_snapshot(snap)?;
let snap = self.remote.fetch_metadata(
&Role::Snapshot,
&MetadataVersion::None,
&snapshot_description.length(),
hashes,
)?;
self.tuf.update_snapshot(snap)
}
}
/// Configuration for a TUF `Client`.
pub struct Config {
max_root_size: Option<usize>,
max_timestamp_size: Option<usize>,
}
impl Config {
/// Initialize a `ConfigBuilder` with the default values.
pub fn build() -> ConfigBuilder {
ConfigBuilder::default()
}
}
/// Helper for building a validating a TUF `Config`.
pub struct ConfigBuilder {
max_root_size: Option<usize>,
max_timestamp_size: Option<usize>,
}
impl ConfigBuilder {
/// Validate this builder return a `Config` if validation succeeds.
pub fn finish(self) -> Result<Config> {
Ok(Config {
max_root_size: self.max_root_size,
max_timestamp_size: self.max_timestamp_size,
})
}
/// Set the optional maximum download size for root metadata.
pub fn max_root_size(mut self, max: Option<usize>) -> Self {
self.max_root_size = max;
self
}
/// Set the optional maximum download size for timestamp metadata.
pub fn max_timestamp_size(mut self, max: Option<usize>) -> Self {
self.max_timestamp_size = max;
self
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
ConfigBuilder {
max_root_size: Some(1024 * 1024),
max_timestamp_size: Some(32 * 1024),
}
}
}