blob: 14ffae00f21b5a96286883e7abf25340e64c6973 [file] [log] [blame]
//! TUF metadata.
use chrono::offset::Utc;
use chrono::{DateTime, Duration};
use futures_io::AsyncRead;
use serde::de::{Deserialize, DeserializeOwned, Deserializer, Error as DeserializeError};
use serde::ser::{Error as SerializeError, Serialize, Serializer};
use serde_derive::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Debug, Display};
use std::marker::PhantomData;
use std::str;
use crate::crypto::{self, HashAlgorithm, HashValue, KeyId, PrivateKey, PublicKey, Signature};
use crate::error::Error;
use crate::interchange::cjson::shims;
use crate::interchange::DataInterchange;
use crate::Result;
#[rustfmt::skip]
static PATH_ILLEGAL_COMPONENTS: &[&str] = &[
".", // current dir
"..", // parent dir
// TODO ? "0", // may translate to nul in windows
];
#[rustfmt::skip]
static PATH_ILLEGAL_COMPONENTS_CASE_INSENSITIVE: &[&str] = &[
// DOS device files
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
"KEYBD$",
"CLOCK$",
"SCREEN$",
"$IDLE$",
"CONFIG$",
];
#[rustfmt::skip]
static PATH_ILLEGAL_STRINGS: &[&str] = &[
":", // for *nix compatibility
"\\", // for windows compatibility
"<",
">",
"\"",
"|",
"?",
"*",
// control characters, all illegal in FAT
"\u{000}",
"\u{001}",
"\u{002}",
"\u{003}",
"\u{004}",
"\u{005}",
"\u{006}",
"\u{007}",
"\u{008}",
"\u{009}",
"\u{00a}",
"\u{00b}",
"\u{00c}",
"\u{00d}",
"\u{00e}",
"\u{00f}",
"\u{010}",
"\u{011}",
"\u{012}",
"\u{013}",
"\u{014}",
"\u{015}",
"\u{016}",
"\u{017}",
"\u{018}",
"\u{019}",
"\u{01a}",
"\u{01b}",
"\u{01c}",
"\u{01d}",
"\u{01e}",
"\u{01f}",
"\u{07f}",
];
fn safe_path(path: &str) -> Result<()> {
if path.is_empty() {
return Err(Error::IllegalArgument("Path cannot be empty".into()));
}
if path.starts_with('/') {
return Err(Error::IllegalArgument("Cannot start with '/'".into()));
}
for bad_str in PATH_ILLEGAL_STRINGS {
if path.contains(bad_str) {
return Err(Error::IllegalArgument(format!(
"Path cannot contain {:?}",
bad_str
)));
}
}
for component in path.split('/') {
for bad_str in PATH_ILLEGAL_COMPONENTS {
if component == *bad_str {
return Err(Error::IllegalArgument(format!(
"Path cannot have component {:?}",
component
)));
}
}
let component_lower = component.to_lowercase();
for bad_str in PATH_ILLEGAL_COMPONENTS_CASE_INSENSITIVE {
if component_lower.as_str() == *bad_str {
return Err(Error::IllegalArgument(format!(
"Path cannot have component {:?}",
component
)));
}
}
}
Ok(())
}
/// The TUF role.
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Role {
/// The root role.
#[serde(rename = "root")]
Root,
/// The snapshot role.
#[serde(rename = "snapshot")]
Snapshot,
/// The targets role.
#[serde(rename = "targets")]
Targets,
/// The timestamp role.
#[serde(rename = "timestamp")]
Timestamp,
}
impl Role {
/// Check if this role could be associated with a given path.
///
/// ```
/// use tuf::metadata::{MetadataPath, Role};
///
/// assert!(Role::Root.fuzzy_matches_path(&MetadataPath::from_role(&Role::Root)));
/// assert!(Role::Snapshot.fuzzy_matches_path(&MetadataPath::from_role(&Role::Snapshot)));
/// assert!(Role::Targets.fuzzy_matches_path(&MetadataPath::from_role(&Role::Targets)));
/// assert!(Role::Timestamp.fuzzy_matches_path(&MetadataPath::from_role(&Role::Timestamp)));
///
/// assert!(!Role::Root.fuzzy_matches_path(&MetadataPath::from_role(&Role::Snapshot)));
/// assert!(!Role::Root.fuzzy_matches_path(&MetadataPath::new("wat").unwrap()));
/// ```
pub fn fuzzy_matches_path(&self, path: &MetadataPath) -> bool {
match *self {
Role::Root if &path.0 == "root" => true,
Role::Snapshot if &path.0 == "snapshot" => true,
Role::Timestamp if &path.0 == "timestamp" => true,
Role::Targets if &path.0 == "targets" => true,
Role::Targets if !&["root", "snapshot", "targets"].contains(&path.0.as_str()) => true,
_ => false,
}
}
/// Return the name of the role.
pub fn name(&self) -> &'static str {
match *self {
Role::Root => "root",
Role::Snapshot => "snapshot",
Role::Targets => "targets",
Role::Timestamp => "timestamp",
}
}
}
impl Display for Role {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.name())
}
}
/// Enum used for addressing versioned TUF metadata.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
pub enum MetadataVersion {
/// The metadata is unversioned. This is the latest version of the metadata.
None,
/// The metadata is addressed by a specific version number.
Number(u32),
}
impl MetadataVersion {
/// Converts this struct into the string used for addressing metadata.
pub fn prefix(&self) -> String {
match *self {
MetadataVersion::None => String::new(),
MetadataVersion::Number(ref x) => format!("{}.", x),
}
}
}
/// Top level trait used for role metadata.
pub trait Metadata: Debug + PartialEq + Serialize + DeserializeOwned {
/// The role associated with the metadata.
const ROLE: Role;
/// The version number.
fn version(&self) -> u32;
/// An immutable reference to the metadata's expiration `DateTime`.
fn expires(&self) -> &DateTime<Utc>;
}
/// Unverified raw metadata with attached signatures and type information identifying the
/// metadata's type and serialization format.
#[derive(Debug, Clone, PartialEq)]
pub struct RawSignedMetadata<D, M> {
bytes: Vec<u8>,
_marker: PhantomData<(D, M)>,
}
impl<D, M> RawSignedMetadata<D, M>
where
D: DataInterchange,
M: Metadata,
{
/// Create a new [`RawSignedMetadata`] using the provided `bytes`.
pub fn new(bytes: Vec<u8>) -> Self {
Self {
bytes,
_marker: PhantomData,
}
}
/// Access this metadata's inner raw bytes.
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
/// Parse this metadata.
///
/// **WARNING**: This does not verify signatures, so it exposes users to potential parser
/// exploits.
pub fn parse_untrusted(&self) -> Result<SignedMetadata<D, M>> {
D::from_slice(&self.bytes)
}
}
/// A collection of [RawSignedMetadata] that describes the metadata at one
/// commit.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct RawSignedMetadataSet<D> {
root: Option<RawSignedMetadata<D, RootMetadata>>,
targets: Option<RawSignedMetadata<D, TargetsMetadata>>,
snapshot: Option<RawSignedMetadata<D, SnapshotMetadata>>,
timestamp: Option<RawSignedMetadata<D, TimestampMetadata>>,
}
impl<D> RawSignedMetadataSet<D> {
/// Returns a reference to the built root metadata, if any.
pub fn root(&self) -> Option<&RawSignedMetadata<D, RootMetadata>> {
self.root.as_ref()
}
/// Returns a reference to the built targets metadata, if any.
pub fn targets(&self) -> Option<&RawSignedMetadata<D, TargetsMetadata>> {
self.targets.as_ref()
}
/// Returns a reference to the built snapshot metadata, if any.
pub fn snapshot(&self) -> Option<&RawSignedMetadata<D, SnapshotMetadata>> {
self.snapshot.as_ref()
}
/// Returns a reference to the built timestamp metadata, if any.
pub fn timestamp(&self) -> Option<&RawSignedMetadata<D, TimestampMetadata>> {
self.timestamp.as_ref()
}
}
/// Builder for [RawSignedMetadataSet].
#[derive(Default)]
pub struct RawSignedMetadataSetBuilder<D>
where
D: DataInterchange,
{
metadata: RawSignedMetadataSet<D>,
}
impl<D> RawSignedMetadataSetBuilder<D>
where
D: DataInterchange,
{
/// Create a new [RawSignedMetadataSetBuilder].
pub fn new() -> Self {
Self {
metadata: RawSignedMetadataSet {
root: None,
targets: None,
snapshot: None,
timestamp: None,
},
}
}
/// Set or replace the root metadata.
pub fn root(mut self, root: RawSignedMetadata<D, RootMetadata>) -> Self {
self.metadata.root = Some(root);
self
}
/// Set or replace the targets metadata.
pub fn targets(mut self, targets: RawSignedMetadata<D, TargetsMetadata>) -> Self {
self.metadata.targets = Some(targets);
self
}
/// Set or replace the snapshot metadata.
pub fn snapshot(mut self, snapshot: RawSignedMetadata<D, SnapshotMetadata>) -> Self {
self.metadata.snapshot = Some(snapshot);
self
}
/// Set or replace the timestamp metadata.
pub fn timestamp(mut self, timestamp: RawSignedMetadata<D, TimestampMetadata>) -> Self {
self.metadata.timestamp = Some(timestamp);
self
}
/// Return a [RawSignedMetadataSet].
pub fn build(self) -> RawSignedMetadataSet<D> {
self.metadata
}
}
/// Helper to construct `SignedMetadata`.
#[derive(Debug, Clone)]
pub struct SignedMetadataBuilder<D, M>
where
D: DataInterchange,
{
signatures: HashMap<KeyId, Signature>,
metadata: D::RawData,
metadata_bytes: Vec<u8>,
_marker: PhantomData<M>,
}
impl<D, M> SignedMetadataBuilder<D, M>
where
D: DataInterchange,
M: Metadata,
{
/// Create a new `SignedMetadataBuilder` from a given `Metadata`.
pub fn from_metadata(metadata: &M) -> Result<Self> {
let metadata = D::serialize(metadata)?;
Self::from_raw_metadata(metadata)
}
/// Create a new `SignedMetadataBuilder` from manually serialized metadata to be signed.
/// Returns an error if `metadata` cannot be parsed into `M`.
pub fn from_raw_metadata(metadata: D::RawData) -> Result<Self> {
let _ensure_metadata_parses: M = D::deserialize(&metadata)?;
let metadata_bytes = D::canonicalize(&metadata)?;
Ok(Self {
signatures: HashMap::new(),
metadata,
metadata_bytes,
_marker: PhantomData,
})
}
/// Sign the metadata using the given `private_key`, replacing any existing signatures with the
/// same `KeyId`.
///
/// **WARNING**: You should never have multiple TUF private keys on the same machine, so if
/// you're using this to append several signatures at once, you are doing something wrong. The
/// preferred method is to generate your copy of the metadata locally and use
/// `SignedMetadata::merge_signatures` to perform the "append" operations.
pub fn sign(mut self, private_key: &dyn PrivateKey) -> Result<Self> {
let sig = private_key.sign(&self.metadata_bytes)?;
let _ = self.signatures.insert(sig.key_id().clone(), sig);
Ok(self)
}
/// Construct a new `SignedMetadata` using the included signatures, sorting the signatures by
/// `KeyId`.
pub fn build(self) -> SignedMetadata<D, M> {
let mut signatures = self
.signatures
.into_iter()
.map(|(_k, v)| v)
.collect::<Vec<_>>();
signatures.sort_unstable_by(|a, b| a.key_id().cmp(b.key_id()));
SignedMetadata {
signatures,
metadata: self.metadata,
_marker: PhantomData,
}
}
}
/// Serialized metadata with attached unverified signatures.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SignedMetadata<D, M>
where
D: DataInterchange,
{
signatures: Vec<Signature>,
#[serde(rename = "signed")]
metadata: D::RawData,
#[serde(skip_serializing, skip_deserializing)]
_marker: PhantomData<M>,
}
impl<D, M> SignedMetadata<D, M>
where
D: DataInterchange,
M: Metadata,
{
/// Create a new `SignedMetadata`. The supplied private key is used to sign the canonicalized
/// bytes of the provided metadata with the provided scheme.
///
/// ```
/// # use chrono::prelude::*;
/// # use tuf::crypto::{Ed25519PrivateKey, PrivateKey, SignatureScheme, HashAlgorithm};
/// # use tuf::interchange::Json;
/// # use tuf::metadata::{SignedMetadata, SnapshotMetadataBuilder};
/// #
/// # let key: &[u8] = include_bytes!("../tests/ed25519/ed25519-1.pk8.der");
/// let key = Ed25519PrivateKey::from_pkcs8(&key).unwrap();
///
/// let snapshot = SnapshotMetadataBuilder::new().build().unwrap();
/// SignedMetadata::<Json, _>::new(&snapshot, &key).unwrap();
/// ```
pub fn new(metadata: &M, private_key: &dyn PrivateKey) -> Result<Self> {
let raw = D::serialize(metadata)?;
let bytes = D::canonicalize(&raw)?;
let sig = private_key.sign(&bytes)?;
Ok(Self {
signatures: vec![sig],
metadata: raw,
_marker: PhantomData,
})
}
/// Serialize this metadata to canonical bytes suitable for serialization. Note that this
/// method is only intended to serialize signed metadata generated by this crate, not to
/// re-serialize metadata that was originally obtained from a remote source.
///
/// TUF metadata hashes are on the raw bytes of the metadata, so it is not guaranteed that the
/// hash of the returned bytes will match a hash included in, for example, a snapshot metadata
/// file, as:
/// * Parsing metadata removes unknown fields, which would not be included in the returned
/// bytes,
/// * DataInterchange implementations only guarantee the bytes are canonical for the purpose of
/// a signature. Metadata obtained from a remote source may have included different whitespace
/// or ordered fields in a way that is not preserved when parsing that metadata.
pub fn to_raw(&self) -> Result<RawSignedMetadata<D, M>> {
let bytes = D::canonicalize(&D::serialize(self)?)?;
Ok(RawSignedMetadata::new(bytes))
}
/// Append a signature to this signed metadata. Will overwrite signature by keys with the same
/// ID.
///
/// **WARNING**: You should never have multiple TUF private keys on the same machine, so if
/// you're using this to append several signatures at once, you are doing something wrong. The
/// preferred method is to generate your copy of the metadata locally and use `merge_signatures`
/// to perform the "append" operations.
///
/// ```
/// # use chrono::prelude::*;
/// # use tuf::crypto::{Ed25519PrivateKey, PrivateKey, SignatureScheme, HashAlgorithm};
/// # use tuf::interchange::Json;
/// # use tuf::metadata::{SignedMetadata, SnapshotMetadataBuilder};
/// #
/// let key_1: &[u8] = include_bytes!("../tests/ed25519/ed25519-1.pk8.der");
/// let key_1 = Ed25519PrivateKey::from_pkcs8(&key_1).unwrap();
///
/// // Note: This is for demonstration purposes only.
/// // You should never have multiple private keys on the same device.
/// let key_2: &[u8] = include_bytes!("../tests/ed25519/ed25519-2.pk8.der");
/// let key_2 = Ed25519PrivateKey::from_pkcs8(&key_2).unwrap();
///
/// let snapshot = SnapshotMetadataBuilder::new().build().unwrap();
/// let mut snapshot = SignedMetadata::<Json, _>::new(&snapshot, &key_1).unwrap();
///
/// snapshot.add_signature(&key_2).unwrap();
/// assert_eq!(snapshot.signatures().len(), 2);
///
/// snapshot.add_signature(&key_2).unwrap();
/// assert_eq!(snapshot.signatures().len(), 2);
/// ```
pub fn add_signature(&mut self, private_key: &dyn PrivateKey) -> Result<()> {
let bytes = D::canonicalize(&self.metadata)?;
let sig = private_key.sign(&bytes)?;
self.signatures
.retain(|s| s.key_id() != private_key.public().key_id());
self.signatures.push(sig);
self.signatures.sort();
Ok(())
}
/// Merge the singatures from `other` into `self` if and only if
/// `self.as_ref() == other.as_ref()`. If `self` and `other` contain signatures from the same
/// key ID, then the signatures from `self` will replace the signatures from `other`.
pub fn merge_signatures(&mut self, other: &Self) -> Result<()> {
if self.metadata != other.metadata {
return Err(Error::IllegalArgument(
"Attempted to merge unequal metadata".into(),
));
}
let key_ids = self
.signatures
.iter()
.map(|s| s.key_id().clone())
.collect::<HashSet<KeyId>>();
self.signatures.extend(
other
.signatures
.iter()
.filter(|s| !key_ids.contains(s.key_id()))
.cloned(),
);
self.signatures.sort();
Ok(())
}
/// An immutable reference to the signatures.
pub fn signatures(&self) -> &[Signature] {
&self.signatures
}
/// Parse the version number of this metadata without verifying signatures.
///
/// This operation is generally unsafe to do with metadata obtained from an untrusted source,
/// but rolling forward to the most recent root.json requires using the version number of the
/// latest root.json.
pub(crate) fn parse_version_untrusted(&self) -> Result<u32> {
#[derive(Deserialize)]
pub struct MetadataVersion {
version: u32,
}
let meta: MetadataVersion = D::deserialize(&self.metadata)?;
Ok(meta.version)
}
/// Parse this metadata without verifying signatures.
///
/// This operation is not safe to do with metadata obtained from an untrusted source.
pub fn assume_valid(&self) -> Result<M> {
D::deserialize(&self.metadata)
}
}
/// Helper to construct `RootMetadata`.
pub struct RootMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
consistent_snapshot: bool,
keys: HashMap<KeyId, PublicKey>,
root_threshold: u32,
root_key_ids: Vec<KeyId>,
snapshot_threshold: u32,
snapshot_key_ids: Vec<KeyId>,
targets_threshold: u32,
targets_key_ids: Vec<KeyId>,
timestamp_threshold: u32,
timestamp_key_ids: Vec<KeyId>,
}
impl RootMetadataBuilder {
/// Create a new `RootMetadataBuilder`. It defaults to:
///
/// * version: 1,
/// * expires: 365 days from the current time.
/// * consistent snapshot: true
/// * role thresholds: 1
pub fn new() -> Self {
RootMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(365),
consistent_snapshot: true,
keys: HashMap::new(),
root_threshold: 1,
root_key_ids: Vec::new(),
snapshot_threshold: 1,
snapshot_key_ids: Vec::new(),
targets_threshold: 1,
targets_key_ids: Vec::new(),
timestamp_threshold: 1,
timestamp_key_ids: Vec::new(),
}
}
/// Set the version number for this metadata.
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
/// Set the time this metadata expires.
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
/// Set this metadata to have a consistent snapshot.
pub fn consistent_snapshot(mut self, consistent_snapshot: bool) -> Self {
self.consistent_snapshot = consistent_snapshot;
self
}
/// Set the root threshold.
pub fn root_threshold(mut self, threshold: u32) -> Self {
self.root_threshold = threshold;
self
}
/// Add a root public key.
pub fn root_key(mut self, public_key: PublicKey) -> Self {
let key_id = public_key.key_id().clone();
self.keys.insert(key_id.clone(), public_key);
self.root_key_ids.push(key_id);
self
}
/// Set the snapshot threshold.
pub fn snapshot_threshold(mut self, threshold: u32) -> Self {
self.snapshot_threshold = threshold;
self
}
/// Add a snapshot public key.
pub fn snapshot_key(mut self, public_key: PublicKey) -> Self {
let key_id = public_key.key_id().clone();
self.keys.insert(key_id.clone(), public_key);
self.snapshot_key_ids.push(key_id);
self
}
/// Set the targets threshold.
pub fn targets_threshold(mut self, threshold: u32) -> Self {
self.targets_threshold = threshold;
self
}
/// Add a targets public key.
pub fn targets_key(mut self, public_key: PublicKey) -> Self {
let key_id = public_key.key_id().clone();
self.keys.insert(key_id.clone(), public_key);
self.targets_key_ids.push(key_id);
self
}
/// Set the timestamp threshold.
pub fn timestamp_threshold(mut self, threshold: u32) -> Self {
self.timestamp_threshold = threshold;
self
}
/// Add a timestamp public key.
pub fn timestamp_key(mut self, public_key: PublicKey) -> Self {
let key_id = public_key.key_id().clone();
self.keys.insert(key_id.clone(), public_key);
self.timestamp_key_ids.push(key_id);
self
}
/// Construct a new `RootMetadata`.
pub fn build(self) -> Result<RootMetadata> {
RootMetadata::new(
self.version,
self.expires,
self.consistent_snapshot,
self.keys,
RoleDefinition::new(self.root_threshold, self.root_key_ids)?,
RoleDefinition::new(self.snapshot_threshold, self.snapshot_key_ids)?,
RoleDefinition::new(self.targets_threshold, self.targets_key_ids)?,
RoleDefinition::new(self.timestamp_threshold, self.timestamp_key_ids)?,
)
}
/// Construct a new `SignedMetadata<D, RootMetadata>`.
pub fn signed<D>(self, private_key: &dyn PrivateKey) -> Result<SignedMetadata<D, RootMetadata>>
where
D: DataInterchange,
{
SignedMetadata::new(&self.build()?, private_key)
}
}
impl Default for RootMetadataBuilder {
fn default() -> Self {
RootMetadataBuilder::new()
}
}
impl From<RootMetadata> for RootMetadataBuilder {
fn from(metadata: RootMetadata) -> Self {
RootMetadataBuilder {
version: metadata.version,
expires: metadata.expires,
consistent_snapshot: metadata.consistent_snapshot,
keys: metadata.keys,
root_threshold: metadata.root.threshold,
root_key_ids: metadata.root.key_ids,
snapshot_threshold: metadata.snapshot.threshold,
snapshot_key_ids: metadata.snapshot.key_ids,
targets_threshold: metadata.targets.threshold,
targets_key_ids: metadata.targets.key_ids,
timestamp_threshold: metadata.timestamp.threshold,
timestamp_key_ids: metadata.timestamp.key_ids,
}
}
}
/// Metadata for the root role.
#[derive(Debug, Clone, PartialEq)]
pub struct RootMetadata {
version: u32,
expires: DateTime<Utc>,
consistent_snapshot: bool,
keys: HashMap<KeyId, PublicKey>,
root: RoleDefinition,
snapshot: RoleDefinition,
targets: RoleDefinition,
timestamp: RoleDefinition,
}
impl RootMetadata {
/// Create new `RootMetadata`.
pub fn new(
version: u32,
expires: DateTime<Utc>,
consistent_snapshot: bool,
keys: HashMap<KeyId, PublicKey>,
root: RoleDefinition,
snapshot: RoleDefinition,
targets: RoleDefinition,
timestamp: RoleDefinition,
) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(format!(
"Metadata version must be greater than zero. Found: {}",
version
)));
}
Ok(RootMetadata {
version,
expires,
consistent_snapshot,
keys,
root,
snapshot,
targets,
timestamp,
})
}
/// Whether or not this repository is currently implementing that TUF consistent snapshot
/// feature.
pub fn consistent_snapshot(&self) -> bool {
self.consistent_snapshot
}
/// An immutable reference to the map of trusted keys.
pub fn keys(&self) -> &HashMap<KeyId, PublicKey> {
&self.keys
}
/// An iterator over all the trusted root public keys.
pub fn root_keys(&self) -> impl Iterator<Item = &PublicKey> {
self.keys().iter().filter_map(move |(k, v)| {
if self.root.key_ids().contains(k) {
Some(v)
} else {
None
}
})
}
/// An iterator over all the trusted snapshot public keys.
pub fn snapshot_keys(&self) -> impl Iterator<Item = &PublicKey> {
self.keys().iter().filter_map(move |(k, v)| {
if self.snapshot.key_ids().contains(k) {
Some(v)
} else {
None
}
})
}
/// An iterator over all the trusted targets public keys.
pub fn targets_keys(&self) -> impl Iterator<Item = &PublicKey> {
self.keys().iter().filter_map(move |(k, v)| {
if self.targets.key_ids().contains(k) {
Some(v)
} else {
None
}
})
}
/// An iterator over all the trusted timestamp public keys.
pub fn timestamp_keys(&self) -> impl Iterator<Item = &PublicKey> {
self.keys().iter().filter_map(move |(k, v)| {
if self.timestamp.key_ids().contains(k) {
Some(v)
} else {
None
}
})
}
/// An immutable reference to the root role's definition.
pub fn root(&self) -> &RoleDefinition {
&self.root
}
/// An immutable reference to the snapshot role's definition.
pub fn snapshot(&self) -> &RoleDefinition {
&self.snapshot
}
/// An immutable reference to the targets role's definition.
pub fn targets(&self) -> &RoleDefinition {
&self.targets
}
/// An immutable reference to the timestamp role's definition.
pub fn timestamp(&self) -> &RoleDefinition {
&self.timestamp
}
}
impl Metadata for RootMetadata {
const ROLE: Role = Role::Root;
fn version(&self) -> u32 {
self.version
}
fn expires(&self) -> &DateTime<Utc> {
&self.expires
}
}
impl Serialize for RootMetadata {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let m = shims::RootMetadata::from(self)
.map_err(|e| SerializeError::custom(format!("{:?}", e)))?;
m.serialize(ser)
}
}
impl<'de> Deserialize<'de> for RootMetadata {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::RootMetadata = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// The definition of what allows a role to be trusted.
#[derive(Clone, Debug, PartialEq)]
pub struct RoleDefinition {
threshold: u32,
key_ids: Vec<KeyId>,
}
impl RoleDefinition {
/// Create a new `RoleDefinition` with a given threshold and set of authorized `KeyID`s.
pub fn new(threshold: u32, key_ids: Vec<KeyId>) -> Result<Self> {
if threshold < 1 {
return Err(Error::IllegalArgument(format!("Threshold: {}", threshold)));
}
if key_ids.is_empty() {
return Err(Error::IllegalArgument(
"Cannot define a role with no associated key IDs".into(),
));
}
if (key_ids.len() as u64) < u64::from(threshold) {
return Err(Error::IllegalArgument(format!(
"Cannot have a threshold greater than the number of associated key IDs. {} vs. {}",
threshold,
key_ids.len()
)));
}
Ok(RoleDefinition { threshold, key_ids })
}
/// The threshold number of signatures required for the role to be trusted.
pub fn threshold(&self) -> u32 {
self.threshold
}
/// An immutable reference to the set of `KeyID`s that are authorized to sign the role.
pub fn key_ids(&self) -> &[KeyId] {
&self.key_ids
}
}
impl Serialize for RoleDefinition {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::RoleDefinition::from(self)
.map_err(|e| SerializeError::custom(format!("{:?}", e)))?
.serialize(ser)
}
}
impl<'de> Deserialize<'de> for RoleDefinition {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::RoleDefinition = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Wrapper for a path to metadata.
///
/// Note: This should **not** contain the file extension. This is automatically added by the
/// library depending on what type of data interchange format is being used.
///
/// ```
/// use tuf::metadata::MetadataPath;
///
/// // right
/// let _ = MetadataPath::new("root");
///
/// // wrong
/// let _ = MetadataPath::new("root.json");
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct MetadataPath(String);
impl MetadataPath {
/// Create a new `MetadataPath` from a `String`.
///
/// ```
/// # use tuf::metadata::MetadataPath;
/// assert!(MetadataPath::new("foo").is_ok());
/// assert!(MetadataPath::new("/foo").is_err());
/// assert!(MetadataPath::new("../foo").is_err());
/// assert!(MetadataPath::new("foo/..").is_err());
/// assert!(MetadataPath::new("foo/../bar").is_err());
/// assert!(MetadataPath::new("..foo").is_ok());
/// assert!(MetadataPath::new("foo/..bar").is_ok());
/// assert!(MetadataPath::new("foo/bar..").is_ok());
/// ```
pub fn new<P: Into<String>>(path: P) -> Result<Self> {
let path = path.into();
safe_path(&path)?;
Ok(MetadataPath(path))
}
/// Create a metadata path from the given role.
///
/// ```
/// # use tuf::metadata::{Role, MetadataPath};
/// assert_eq!(MetadataPath::from_role(&Role::Root),
/// MetadataPath::new("root").unwrap());
/// assert_eq!(MetadataPath::from_role(&Role::Snapshot),
/// MetadataPath::new("snapshot").unwrap());
/// assert_eq!(MetadataPath::from_role(&Role::Targets),
/// MetadataPath::new("targets").unwrap());
/// assert_eq!(MetadataPath::from_role(&Role::Timestamp),
/// MetadataPath::new("timestamp").unwrap());
/// ```
pub fn from_role(role: &Role) -> Self {
Self::new(format!("{}", role)).unwrap()
}
/// Split `MetadataPath` into components that can be joined to create URL paths, Unix paths, or
/// Windows paths.
///
/// ```
/// # use tuf::crypto::HashValue;
/// # use tuf::interchange::Json;
/// # use tuf::metadata::{MetadataPath, MetadataVersion};
/// #
/// let path = MetadataPath::new("foo/bar").unwrap();
/// assert_eq!(path.components::<Json>(MetadataVersion::None),
/// ["foo".to_string(), "bar.json".to_string()]);
/// assert_eq!(path.components::<Json>(MetadataVersion::Number(1)),
/// ["foo".to_string(), "1.bar.json".to_string()]);
/// ```
pub fn components<D>(&self, version: MetadataVersion) -> Vec<String>
where
D: DataInterchange,
{
let mut buf: Vec<String> = self.0.split('/').map(|s| s.to_string()).collect();
let len = buf.len();
buf[len - 1] = format!("{}{}.{}", version.prefix(), buf[len - 1], D::extension());
buf
}
}
impl Display for MetadataPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl<'de> Deserialize<'de> for MetadataPath {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let s: String = Deserialize::deserialize(de)?;
MetadataPath::new(s).map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Helper to construct `TimestampMetadata`.
pub struct TimestampMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
snapshot: MetadataDescription,
}
impl TimestampMetadataBuilder {
/// Create a new `TimestampMetadataBuilder` from a given snapshot. It defaults to:
///
/// * version: 1
/// * expires: 1 day from the current time.
pub fn from_snapshot<D>(
snapshot: &SignedMetadata<D, SnapshotMetadata>,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
D: DataInterchange,
{
let raw_snapshot = snapshot.to_raw()?;
let description = MetadataDescription::from_slice(
raw_snapshot.as_bytes(),
snapshot.parse_version_untrusted()?,
hash_algs,
)?;
Ok(Self::from_metadata_description(description))
}
/// Create a new `TimestampMetadataBuilder` from a given
/// `MetadataDescription`. It defaults to:
///
/// * version: 1
/// * expires: 1 day from the current time.
pub fn from_metadata_description(description: MetadataDescription) -> Self {
TimestampMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(1),
snapshot: description,
}
}
/// Set the version number for this metadata.
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
/// Set the time this metadata expires.
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
/// Construct a new `TimestampMetadata`.
pub fn build(self) -> Result<TimestampMetadata> {
TimestampMetadata::new(self.version, self.expires, self.snapshot)
}
/// Construct a new `SignedMetadata<D, TimestampMetadata>`.
pub fn signed<D>(
self,
private_key: &dyn PrivateKey,
) -> Result<SignedMetadata<D, TimestampMetadata>>
where
D: DataInterchange,
{
SignedMetadata::new(&self.build()?, private_key)
}
}
/// Metadata for the timestamp role.
#[derive(Debug, Clone, PartialEq)]
pub struct TimestampMetadata {
version: u32,
expires: DateTime<Utc>,
snapshot: MetadataDescription,
}
impl TimestampMetadata {
/// Create new `TimestampMetadata`.
pub fn new(
version: u32,
expires: DateTime<Utc>,
snapshot: MetadataDescription,
) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(format!(
"Metadata version must be greater than zero. Found: {}",
version
)));
}
Ok(TimestampMetadata {
version,
expires,
snapshot,
})
}
/// An immutable reference to the snapshot description.
pub fn snapshot(&self) -> &MetadataDescription {
&self.snapshot
}
}
impl Metadata for TimestampMetadata {
const ROLE: Role = Role::Timestamp;
fn version(&self) -> u32 {
self.version
}
fn expires(&self) -> &DateTime<Utc> {
&self.expires
}
}
impl Serialize for TimestampMetadata {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::TimestampMetadata::from(self)
.map_err(|e| SerializeError::custom(format!("{:?}", e)))?
.serialize(ser)
}
}
impl<'de> Deserialize<'de> for TimestampMetadata {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::TimestampMetadata = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Description of a piece of metadata, used in verification.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct MetadataDescription {
version: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
length: Option<usize>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
hashes: HashMap<HashAlgorithm, HashValue>,
}
impl MetadataDescription {
/// Create a `MetadataDescription` from a slice. Size and hashes will be calculated.
pub fn from_slice(buf: &[u8], version: u32, hash_algs: &[HashAlgorithm]) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(
"Version must be greater than zero".into(),
));
}
let hashes = if hash_algs.is_empty() {
HashMap::new()
} else {
crypto::calculate_hashes_from_slice(buf, hash_algs)?
};
Ok(MetadataDescription {
version,
length: Some(buf.len()),
hashes,
})
}
/// Create a new `MetadataDescription`.
pub fn new(
version: u32,
length: Option<usize>,
hashes: HashMap<HashAlgorithm, HashValue>,
) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(format!(
"Metadata version must be greater than zero. Found: {}",
version
)));
}
Ok(MetadataDescription {
version,
length,
hashes,
})
}
/// The version of the described metadata.
pub fn version(&self) -> u32 {
self.version
}
/// The length of the described metadata.
pub fn length(&self) -> Option<usize> {
self.length
}
/// An immutable reference to the hashes of the described metadata.
pub fn hashes(&self) -> &HashMap<HashAlgorithm, HashValue> {
&self.hashes
}
}
impl<'de> Deserialize<'de> for MetadataDescription {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::MetadataDescription = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Helper to construct `SnapshotMetadata`.
pub struct SnapshotMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
meta: HashMap<MetadataPath, MetadataDescription>,
}
impl SnapshotMetadataBuilder {
/// Create a new `SnapshotMetadataBuilder`. It defaults to:
///
/// * version: 1
/// * expires: 7 days from the current time.
pub fn new() -> Self {
SnapshotMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(7),
meta: HashMap::new(),
}
}
/// Create a new [SnapshotMetadataBuilder] from a given snapshot. It defaults to:
///
/// * version: 1
/// * expires: 7 day from the current time.
pub fn from_targets<D>(
targets: &SignedMetadata<D, TargetsMetadata>,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
D: DataInterchange,
{
SnapshotMetadataBuilder::new().insert_metadata(targets, hash_algs)
}
/// Set the version number for this metadata.
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
/// Set the time this metadata expires.
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
/// Add metadata to this snapshot metadata using the default path.
pub fn insert_metadata<D, M>(
self,
metadata: &SignedMetadata<D, M>,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
M: Metadata,
D: DataInterchange,
{
self.insert_metadata_with_path(M::ROLE.name(), metadata, hash_algs)
}
/// Add metadata to this snapshot metadata using a custom path.
pub fn insert_metadata_with_path<P, D, M>(
self,
path: P,
metadata: &SignedMetadata<D, M>,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
P: Into<String>,
M: Metadata,
D: DataInterchange,
{
let raw_metadata = metadata.to_raw()?;
let description = MetadataDescription::from_slice(
raw_metadata.as_bytes(),
metadata.parse_version_untrusted()?,
hash_algs,
)?;
let path = MetadataPath::new(path)?;
Ok(self.insert_metadata_description(path, description))
}
/// Add `MetadataDescription` to this snapshot metadata using a custom path.
pub fn insert_metadata_description(
mut self,
path: MetadataPath,
description: MetadataDescription,
) -> Self {
self.meta.insert(path, description);
self
}
/// Construct a new `SnapshotMetadata`.
pub fn build(self) -> Result<SnapshotMetadata> {
SnapshotMetadata::new(self.version, self.expires, self.meta)
}
/// Construct a new `SignedMetadata<D, SnapshotMetadata>`.
pub fn signed<D>(
self,
private_key: &dyn PrivateKey,
) -> Result<SignedMetadata<D, SnapshotMetadata>>
where
D: DataInterchange,
{
SignedMetadata::new(&self.build()?, private_key)
}
}
impl Default for SnapshotMetadataBuilder {
fn default() -> Self {
SnapshotMetadataBuilder::new()
}
}
impl From<SnapshotMetadata> for SnapshotMetadataBuilder {
fn from(meta: SnapshotMetadata) -> Self {
SnapshotMetadataBuilder {
version: meta.version,
expires: meta.expires,
meta: meta.meta,
}
}
}
/// Metadata for the snapshot role.
#[derive(Debug, Clone, PartialEq)]
pub struct SnapshotMetadata {
version: u32,
expires: DateTime<Utc>,
meta: HashMap<MetadataPath, MetadataDescription>,
}
impl SnapshotMetadata {
/// Create new `SnapshotMetadata`.
pub fn new(
version: u32,
expires: DateTime<Utc>,
meta: HashMap<MetadataPath, MetadataDescription>,
) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(format!(
"Metadata version must be greater than zero. Found: {}",
version
)));
}
Ok(SnapshotMetadata {
version,
expires,
meta,
})
}
/// An immutable reference to the metadata paths and descriptions.
pub fn meta(&self) -> &HashMap<MetadataPath, MetadataDescription> {
&self.meta
}
}
impl Metadata for SnapshotMetadata {
const ROLE: Role = Role::Snapshot;
fn version(&self) -> u32 {
self.version
}
fn expires(&self) -> &DateTime<Utc> {
&self.expires
}
}
impl Serialize for SnapshotMetadata {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::SnapshotMetadata::from(self)
.map_err(|e| SerializeError::custom(format!("{:?}", e)))?
.serialize(ser)
}
}
impl<'de> Deserialize<'de> for SnapshotMetadata {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::SnapshotMetadata = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Wrapper for the virtual path to a target.
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize)]
pub struct TargetPath(String);
impl TargetPath {
/// Create a new `TargetPath` from a `String`.
///
/// ```
/// # use tuf::metadata::TargetPath;
/// assert!(TargetPath::new("foo").is_ok());
/// assert!(TargetPath::new("/foo").is_err());
/// assert!(TargetPath::new("../foo").is_err());
/// assert!(TargetPath::new("foo/..").is_err());
/// assert!(TargetPath::new("foo/../bar").is_err());
/// assert!(TargetPath::new("..foo").is_ok());
/// assert!(TargetPath::new("foo/..bar").is_ok());
/// assert!(TargetPath::new("foo/bar..").is_ok());
/// ```
pub fn new<P: Into<String>>(path: P) -> Result<Self> {
let path = path.into();
safe_path(&path)?;
Ok(TargetPath(path))
}
/// Split `TargetPath` into components that can be joined to create URL paths, Unix
/// paths, or Windows paths.
///
/// ```
/// # use tuf::metadata::TargetPath;
/// let path = TargetPath::new("foo/bar").unwrap();
/// assert_eq!(path.components(), ["foo".to_string(), "bar".to_string()]);
/// ```
pub fn components(&self) -> Vec<String> {
self.0.split('/').map(|s| s.to_string()).collect()
}
/// Return whether this path is the child of another path.
///
/// ```
/// # use tuf::metadata::TargetPath;
/// let path1 = TargetPath::new("foo").unwrap();
/// let path2 = TargetPath::new("foo/bar").unwrap();
/// assert!(!path2.is_child(&path1));
///
/// let path1 = TargetPath::new("foo/").unwrap();
/// let path2 = TargetPath::new("foo/bar").unwrap();
/// assert!(path2.is_child(&path1));
///
/// let path2 = TargetPath::new("foo/bar/baz").unwrap();
/// assert!(path2.is_child(&path1));
///
/// let path2 = TargetPath::new("wat").unwrap();
/// assert!(!path2.is_child(&path1))
/// ```
pub fn is_child(&self, parent: &Self) -> bool {
if !parent.0.ends_with('/') {
return false;
}
self.0.starts_with(&parent.0)
}
/// Whether or not the current target is available at the end of the given chain of target
/// paths. For the chain to be valid, each target path in a group must be a child of of all
/// previous groups.
// TODO this is hideous and uses way too much clone/heap but I think recursively,
// so here we are
pub fn matches_chain(&self, parents: &[HashSet<TargetPath>]) -> bool {
if parents.is_empty() {
return false;
}
if parents.len() == 1 {
return parents[0].iter().any(|p| p == self || self.is_child(p));
}
let new = parents[1..]
.iter()
.map(|group| {
group
.iter()
.filter(|parent| {
parents[0]
.iter()
.any(|p| parent.is_child(p) || parent == &p)
})
.cloned()
.collect::<HashSet<_>>()
})
.collect::<Vec<_>>();
self.matches_chain(&*new)
}
/// Prefix the target path with a hash value to support TUF spec 5.5.2.
pub fn with_hash_prefix(&self, hash: &HashValue) -> Result<TargetPath> {
let mut components = self.components();
let file_name = components
.pop()
.ok_or_else(|| Error::IllegalArgument("Path cannot be empty".into()))?;
components.push(format!("{}.{}", hash, file_name));
TargetPath::new(components.join("/"))
}
/// The string value of the path.
pub fn as_str(&self) -> &str {
&self.0
}
}
impl ToString for TargetPath {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl<'de> Deserialize<'de> for TargetPath {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let s: String = Deserialize::deserialize(de)?;
TargetPath::new(s).map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
impl Borrow<str> for TargetPath {
fn borrow(&self) -> &str {
self.as_str()
}
}
/// Description of a target, used in verification.
#[derive(Debug, Clone, PartialEq)]
pub struct TargetDescription {
length: u64,
hashes: HashMap<HashAlgorithm, HashValue>,
custom: HashMap<String, serde_json::Value>,
}
impl TargetDescription {
/// Create a new `TargetDescription`.
///
/// Note: Creating this manually could lead to errors, and the `from_reader` method is
/// preferred.
pub fn new(
length: u64,
hashes: HashMap<HashAlgorithm, HashValue>,
custom: HashMap<String, serde_json::Value>,
) -> Result<Self> {
if hashes.is_empty() {
return Err(Error::IllegalArgument(
"Cannot have empty set of hashes".into(),
));
}
Ok(TargetDescription {
length,
hashes,
custom,
})
}
/// Read the from the given slice and calculate the length and hash values.
///
/// ```
/// # use data_encoding::BASE64URL;
/// # use tuf::crypto::{HashAlgorithm,HashValue};
/// # use tuf::metadata::TargetDescription;
/// #
/// let bytes: &[u8] = b"it was a pleasure to burn";
///
/// let target_description = TargetDescription::from_slice(
/// bytes,
/// &[HashAlgorithm::Sha256, HashAlgorithm::Sha512],
/// ).unwrap();
///
/// let s = "Rd9zlbzrdWfeL7gnIEi05X-Yv2TCpy4qqZM1N72ZWQs=";
/// let sha256 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// let s ="tuIxwKybYdvJpWuUj6dubvpwhkAozWB6hMJIRzqn2jOUdtDTBg381brV4K\
/// BU1zKP8GShoJuXEtCf5NkDTCEJgQ==";
/// let sha512 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// assert_eq!(target_description.length(), bytes.len() as u64);
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha256), Some(&sha256));
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha512), Some(&sha512));
/// ```
pub fn from_slice(buf: &[u8], hash_algs: &[HashAlgorithm]) -> Result<Self> {
Self::from_slice_with_custom(buf, hash_algs, HashMap::new())
}
/// Read the from the given reader and custom metadata and calculate the length and hash
/// values.
///
/// ```
/// # use data_encoding::BASE64URL;
/// # use serde_json::Value;
/// # use std::collections::HashMap;
/// # use tuf::crypto::{HashAlgorithm,HashValue};
/// # use tuf::metadata::TargetDescription;
/// #
/// let bytes: &[u8] = b"it was a pleasure to burn";
///
/// let mut custom = HashMap::new();
/// custom.insert("Hello".into(), "World".into());
///
/// let target_description = TargetDescription::from_slice_with_custom(
/// bytes,
/// &[HashAlgorithm::Sha256, HashAlgorithm::Sha512],
/// custom,
/// ).unwrap();
///
/// let s = "Rd9zlbzrdWfeL7gnIEi05X-Yv2TCpy4qqZM1N72ZWQs=";
/// let sha256 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// let s ="tuIxwKybYdvJpWuUj6dubvpwhkAozWB6hMJIRzqn2jOUdtDTBg381brV4K\
/// BU1zKP8GShoJuXEtCf5NkDTCEJgQ==";
/// let sha512 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// assert_eq!(target_description.length(), bytes.len() as u64);
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha256), Some(&sha256));
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha512), Some(&sha512));
/// assert_eq!(target_description.custom().get("Hello"), Some(&"World".into()));
/// ```
pub fn from_slice_with_custom(
buf: &[u8],
hash_algs: &[HashAlgorithm],
custom: HashMap<String, serde_json::Value>,
) -> Result<Self> {
let hashes = crypto::calculate_hashes_from_slice(buf, hash_algs)?;
Ok(TargetDescription {
length: buf.len() as u64,
hashes,
custom,
})
}
/// Read the from the given reader and calculate the length and hash values.
///
/// ```
/// # use data_encoding::BASE64URL;
/// # use futures_executor::block_on;
/// # use tuf::crypto::{HashAlgorithm,HashValue};
/// # use tuf::metadata::TargetDescription;
/// #
/// # block_on(async {
/// let bytes: &[u8] = b"it was a pleasure to burn";
///
/// let target_description = TargetDescription::from_reader(
/// bytes,
/// &[HashAlgorithm::Sha256, HashAlgorithm::Sha512],
/// ).await.unwrap();
///
/// let s = "Rd9zlbzrdWfeL7gnIEi05X-Yv2TCpy4qqZM1N72ZWQs=";
/// let sha256 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// let s ="tuIxwKybYdvJpWuUj6dubvpwhkAozWB6hMJIRzqn2jOUdtDTBg381brV4K\
/// BU1zKP8GShoJuXEtCf5NkDTCEJgQ==";
/// let sha512 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// assert_eq!(target_description.length(), bytes.len() as u64);
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha256), Some(&sha256));
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha512), Some(&sha512));
/// # })
/// ```
pub async fn from_reader<R>(read: R, hash_algs: &[HashAlgorithm]) -> Result<Self>
where
R: AsyncRead + Unpin,
{
Self::from_reader_with_custom(read, hash_algs, HashMap::new()).await
}
/// Read the from the given reader and custom metadata and calculate the length and hash
/// values.
///
/// ```
/// # use data_encoding::BASE64URL;
/// # use futures_executor::block_on;
/// # use serde_json::Value;
/// # use std::collections::HashMap;
/// # use tuf::crypto::{HashAlgorithm,HashValue};
/// # use tuf::metadata::TargetDescription;
/// #
/// # block_on(async {
/// let bytes: &[u8] = b"it was a pleasure to burn";
///
/// let mut custom = HashMap::new();
/// custom.insert("Hello".into(), "World".into());
///
/// let target_description = TargetDescription::from_reader_with_custom(
/// bytes,
/// &[HashAlgorithm::Sha256, HashAlgorithm::Sha512],
/// custom,
/// ).await.unwrap();
///
/// let s = "Rd9zlbzrdWfeL7gnIEi05X-Yv2TCpy4qqZM1N72ZWQs=";
/// let sha256 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// let s ="tuIxwKybYdvJpWuUj6dubvpwhkAozWB6hMJIRzqn2jOUdtDTBg381brV4K\
/// BU1zKP8GShoJuXEtCf5NkDTCEJgQ==";
/// let sha512 = HashValue::new(BASE64URL.decode(s.as_bytes()).unwrap());
///
/// assert_eq!(target_description.length(), bytes.len() as u64);
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha256), Some(&sha256));
/// assert_eq!(target_description.hashes().get(&HashAlgorithm::Sha512), Some(&sha512));
/// assert_eq!(target_description.custom().get("Hello"), Some(&"World".into()));
/// })
/// ```
pub async fn from_reader_with_custom<R>(
read: R,
hash_algs: &[HashAlgorithm],
custom: HashMap<String, serde_json::Value>,
) -> Result<Self>
where
R: AsyncRead + Unpin,
{
let (length, hashes) = crypto::calculate_hashes_from_reader(read, hash_algs).await?;
Ok(TargetDescription {
length,
hashes,
custom,
})
}
/// The maximum length of the target.
pub fn length(&self) -> u64 {
self.length
}
/// An immutable reference to the list of calculated hashes.
pub fn hashes(&self) -> &HashMap<HashAlgorithm, HashValue> {
&self.hashes
}
/// An immutable reference to the custom metadata.
pub fn custom(&self) -> &HashMap<String, serde_json::Value> {
&self.custom
}
}
impl Serialize for TargetDescription {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::TargetDescription::from(self).serialize(ser)
}
}
impl<'de> Deserialize<'de> for TargetDescription {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::TargetDescription = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Metadata for the targets role.
#[derive(Debug, Clone, PartialEq)]
pub struct TargetsMetadata {
version: u32,
expires: DateTime<Utc>,
targets: HashMap<TargetPath, TargetDescription>,
delegations: Option<Delegations>,
}
impl TargetsMetadata {
/// Create new `TargetsMetadata`.
pub fn new(
version: u32,
expires: DateTime<Utc>,
targets: HashMap<TargetPath, TargetDescription>,
delegations: Option<Delegations>,
) -> Result<Self> {
if version < 1 {
return Err(Error::IllegalArgument(format!(
"Metadata version must be greater than zero. Found: {}",
version
)));
}
Ok(TargetsMetadata {
version,
expires,
targets,
delegations,
})
}
/// An immutable reference to the descriptions of targets.
pub fn targets(&self) -> &HashMap<TargetPath, TargetDescription> {
&self.targets
}
/// An immutable reference to the optional delegations.
pub fn delegations(&self) -> Option<&Delegations> {
self.delegations.as_ref()
}
}
impl Metadata for TargetsMetadata {
const ROLE: Role = Role::Targets;
fn version(&self) -> u32 {
self.version
}
fn expires(&self) -> &DateTime<Utc> {
&self.expires
}
}
impl Serialize for TargetsMetadata {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::TargetsMetadata::from(self)
.map_err(|e| SerializeError::custom(format!("{:?}", e)))?
.serialize(ser)
}
}
impl<'de> Deserialize<'de> for TargetsMetadata {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::TargetsMetadata = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// Helper to construct `TargetsMetadata`.
pub struct TargetsMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
targets: HashMap<TargetPath, TargetDescription>,
delegations: Option<Delegations>,
}
impl TargetsMetadataBuilder {
/// Create a new `TargetsMetadataBuilder`. It defaults to:
///
/// * version: 1
/// * expires: 90 days from the current time.
pub fn new() -> Self {
TargetsMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(90),
targets: HashMap::new(),
delegations: None,
}
}
/// Set the version number for this metadata.
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
/// Set the time this metadata expires.
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
/// Add target to the target metadata.
pub fn insert_target_from_slice(
self,
path: TargetPath,
buf: &[u8],
hash_algs: &[HashAlgorithm],
) -> Result<Self> {
let description = TargetDescription::from_slice(buf, hash_algs)?;
Ok(self.insert_target_description(path, description))
}
/// Add target to the target metadata.
pub async fn insert_target_from_reader<R>(
self,
path: TargetPath,
read: R,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
R: AsyncRead + Unpin,
{
let description = TargetDescription::from_reader(read, hash_algs).await?;
Ok(self.insert_target_description(path, description))
}
/// Add `TargetDescription` to this target metadata target description.
pub fn insert_target_description(
mut self,
path: TargetPath,
description: TargetDescription,
) -> Self {
self.targets.insert(path, description);
self
}
/// Add `Delegations` to this target metadata.
pub fn delegations(mut self, delegations: Delegations) -> Self {
self.delegations = Some(delegations);
self
}
/// Construct a new `TargetsMetadata`.
pub fn build(self) -> Result<TargetsMetadata> {
TargetsMetadata::new(self.version, self.expires, self.targets, self.delegations)
}
/// Construct a new `SignedMetadata<D, TargetsMetadata>`.
pub fn signed<D>(
self,
private_key: &dyn PrivateKey,
) -> Result<SignedMetadata<D, TargetsMetadata>>
where
D: DataInterchange,
{
SignedMetadata::new(&self.build()?, private_key)
}
}
impl Default for TargetsMetadataBuilder {
fn default() -> Self {
TargetsMetadataBuilder::new()
}
}
/// Wrapper to described a collections of delegations.
#[derive(Debug, PartialEq, Clone)]
pub struct Delegations {
keys: HashMap<KeyId, PublicKey>,
roles: Vec<Delegation>,
}
impl Delegations {
// TODO check all keys are used
// TODO check all roles have their ID in the set of keys
/// Create a new `Delegations` wrapper from the given set of trusted keys and roles.
pub fn new(keys: HashMap<KeyId, PublicKey>, roles: Vec<Delegation>) -> Result<Self> {
if keys.is_empty() {
return Err(Error::IllegalArgument("Keys cannot be empty.".into()));
}
if roles.is_empty() {
return Err(Error::IllegalArgument("Roles cannot be empty.".into()));
}
if roles.len()
!= roles
.iter()
.map(|r| &r.role)
.collect::<HashSet<&MetadataPath>>()
.len()
{
return Err(Error::IllegalArgument(
"Cannot have duplicated roles in delegations.".into(),
));
}
Ok(Delegations { keys, roles })
}
/// An immutable reference to the keys used for this set of delegations.
pub fn keys(&self) -> &HashMap<KeyId, PublicKey> {
&self.keys
}
/// An immutable reference to the delegated roles.
pub fn roles(&self) -> &Vec<Delegation> {
&self.roles
}
}
impl Serialize for Delegations {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::Delegations::from(self).serialize(ser)
}
}
impl<'de> Deserialize<'de> for Delegations {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::Delegations = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
/// A delegated targets role.
#[derive(Debug, PartialEq, Clone)]
pub struct Delegation {
role: MetadataPath,
terminating: bool,
threshold: u32,
key_ids: HashSet<KeyId>,
paths: HashSet<TargetPath>,
}
impl Delegation {
/// Create a new delegation.
pub fn new(
role: MetadataPath,
terminating: bool,
threshold: u32,
key_ids: HashSet<KeyId>,
paths: HashSet<TargetPath>,
) -> Result<Self> {
if key_ids.is_empty() {
return Err(Error::IllegalArgument("Cannot have empty key IDs".into()));
}
if paths.is_empty() {
return Err(Error::IllegalArgument("Cannot have empty paths".into()));
}
if threshold < 1 {
return Err(Error::IllegalArgument("Cannot have threshold < 1".into()));
}
if (key_ids.len() as u64) < u64::from(threshold) {
return Err(Error::IllegalArgument(
"Cannot have threshold less than number of keys".into(),
));
}
Ok(Delegation {
role,
terminating,
threshold,
key_ids,
paths,
})
}
/// An immutable reference to the delegations's metadata path (role).
pub fn role(&self) -> &MetadataPath {
&self.role
}
/// Whether or not this delegation is terminating.
pub fn terminating(&self) -> bool {
self.terminating
}
/// An immutable reference to the delegations's trusted key IDs.
pub fn key_ids(&self) -> &HashSet<KeyId> {
&self.key_ids
}
/// The delegation's threshold.
pub fn threshold(&self) -> u32 {
self.threshold
}
/// An immutable reference to the delegation's authorized paths.
pub fn paths(&self) -> &HashSet<TargetPath> {
&self.paths
}
}
impl Serialize for Delegation {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::Delegation::from(self).serialize(ser)
}
}
impl<'de> Deserialize<'de> for Delegation {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let intermediate: shims::Delegation = Deserialize::deserialize(de)?;
intermediate
.try_into()
.map_err(|e| DeserializeError::custom(format!("{:?}", e)))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::crypto::Ed25519PrivateKey;
use crate::interchange::Json;
use crate::verify::verify_signatures;
use assert_matches::assert_matches;
use chrono::prelude::*;
use futures_executor::block_on;
use maplit::{hashmap, hashset};
use pretty_assertions::assert_eq;
use serde_json::json;
use std::str::FromStr;
const ED25519_1_PK8: &[u8] = include_bytes!("../tests/ed25519/ed25519-1.pk8.der");
const ED25519_2_PK8: &[u8] = include_bytes!("../tests/ed25519/ed25519-2.pk8.der");
const ED25519_3_PK8: &[u8] = include_bytes!("../tests/ed25519/ed25519-3.pk8.der");
const ED25519_4_PK8: &[u8] = include_bytes!("../tests/ed25519/ed25519-4.pk8.der");
#[test]
fn no_pardir_in_target_path() {
let bad_paths = &[
"..",
"../some/path",
"../some/path/",
"some/../path",
"some/../path/..",
];
for path in bad_paths.iter() {
assert!(safe_path(*path).is_err());
assert!(TargetPath::new(path.to_string()).is_err());
assert!(MetadataPath::new(path.to_string()).is_err());
assert!(TargetPath::new(path.to_string()).is_err());
}
}
#[test]
fn path_matches_chain() {
let test_cases: &[(bool, &str, &[&[&str]])] = &[
// simplest case
(true, "foo", &[&["foo"]]),
// direct delegation case
(true, "foo", &[&["foo"], &["foo"]]),
// is a dir
(false, "foo", &[&["foo/"]]),
// target not in last position
(false, "foo", &[&["foo"], &["bar"]]),
// target nested
(true, "foo/bar", &[&["foo/"], &["foo/bar"]]),
// target illegally nested
(false, "foo/bar", &[&["baz/"], &["foo/bar"]]),
// target illegally deeply nested
(
false,
"foo/bar/baz",
&[&["foo/"], &["foo/quux/"], &["foo/bar/baz"]],
),
// empty
(false, "foo", &[&[]]),
// empty 2
(false, "foo", &[&[], &["foo"]]),
// empty 3
(false, "foo", &[&["foo"], &[]]),
];
for case in test_cases {
let expected = case.0;
let target = TargetPath::new(case.1).unwrap();
let parents = case
.2
.iter()
.map(|group| {
group
.iter()
.map(|p| TargetPath::new(p.to_string()).unwrap())
.collect::<HashSet<_>>()
})
.collect::<Vec<_>>();
println!(
"CASE: expect: {} path: {:?} parents: {:?}",
expected, target, parents
);
assert_eq!(target.matches_chain(&parents), expected);
}
}
#[test]
fn serde_target_path() {
let s = "foo/bar";
let t = serde_json::from_str::<TargetPath>(&format!("\"{}\"", s)).unwrap();
assert_eq!(t.to_string().as_str(), s);
assert_eq!(serde_json::to_value(t).unwrap(), json!("foo/bar"));
}
#[test]
fn serde_metadata_path() {
let s = "foo/bar";
let m = serde_json::from_str::<MetadataPath>(&format!("\"{}\"", s)).unwrap();
assert_eq!(m.to_string().as_str(), s);
assert_eq!(serde_json::to_value(m).unwrap(), json!("foo/bar"));
}
#[test]
fn serde_target_description() {
let s: &[u8] = b"from water does all life begin";
let description = TargetDescription::from_slice(s, &[HashAlgorithm::Sha256]).unwrap();
let jsn_str = serde_json::to_string(&description).unwrap();
let jsn = json!({
"length": 30,
"hashes": {
"sha256": "fc5d745c712bc86ea9a31264dac0c956eeb53857f677eed05829\
bb71013cae18",
},
});
let parsed_str: TargetDescription = serde_json::from_str(&jsn_str).unwrap();
let parsed_jsn: TargetDescription = serde_json::from_value(jsn).unwrap();
assert_eq!(parsed_str, parsed_jsn);
}
#[test]
fn serde_role_definition() {
// keyid ordering must be preserved.
let keyids = vec![
KeyId::from_str("40e35e8f6003ab90d104710cf88901edab931597401f91c19eeb366060ab3d53")
.unwrap(),
KeyId::from_str("01892c662c8cd79fab20edec21de1dcb8b75d9353103face7fe086ff5c0098e4")
.unwrap(),
KeyId::from_str("4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db")
.unwrap(),
];
let role_def = RoleDefinition::new(3, keyids).unwrap();
let jsn = json!({
"threshold": 3,
"keyids": [
"40e35e8f6003ab90d104710cf88901edab931597401f91c19eeb366060ab3d53",
"01892c662c8cd79fab20edec21de1dcb8b75d9353103face7fe086ff5c0098e4",
"4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db",
],
});
let encoded = serde_json::to_value(&role_def).unwrap();
assert_eq!(encoded, jsn);
let decoded: RoleDefinition = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, role_def);
}
#[test]
fn serde_invalid_role_definitions() {
let jsn = json!({
"threshold": 0,
"keyids": [
"01892c662c8cd79fab20edec21de1dcb8b75d9353103face7fe086ff5c0098e4",
"4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db",
],
});
assert!(serde_json::from_value::<RoleDefinition>(jsn).is_err());
let jsn = json!({
"threshold": -1,
"keyids": [
"01892c662c8cd79fab20edec21de1dcb8b75d9353103face7fe086ff5c0098e4",
"4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db",
],
});
assert!(serde_json::from_value::<RoleDefinition>(jsn).is_err());
}
#[test]
fn serde_root_metadata() {
let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let snapshot_key = Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap();
let targets_key = Ed25519PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap();
let timestamp_key = Ed25519PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap();
let root = RootMetadataBuilder::new()
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.root_key(root_key.public().clone())
.snapshot_key(snapshot_key.public().clone())
.targets_key(targets_key.public().clone())
.timestamp_key(timestamp_key.public().clone())
.build()
.unwrap();
let jsn = json!({
"_type": "root",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"consistent_snapshot": true,
"keys": {
"09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyid_hash_algorithms": ["sha256", "sha512"],
"keyval": {
"public": "1410ae3053aa70bbfa98428a879d64d3002a3578f7dfaaeb1cb0764e860f7e0b",
},
},
"40e35e8f6003ab90d104710cf88901edab931597401f91c19eeb366060ab3d53": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyid_hash_algorithms": ["sha256", "sha512"],
"keyval": {
"public": "166376c90a7f717d027056272f361c252fb050bed1a067ff2089a0302fbab73d",
},
},
"a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyid_hash_algorithms": ["sha256", "sha512"],
"keyval": {
"public": "eb8ac26b5c9ef0279e3be3e82262a93bce16fe58ee422500d38caf461c65a3b6",
},
},
"fd7b7741686fa44903f1e4b61d7db869939f402b4acedc044767922c7d309983": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyid_hash_algorithms": ["sha256", "sha512"],
"keyval": {
"public": "68d9ecb387371005a8eb8e60105305c34356a8fcd859d7fef3cc228bf2b2b3b2",
},
}
},
"roles": {
"root": {
"threshold": 1,
"keyids": ["a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a"],
},
"snapshot": {
"threshold": 1,
"keyids": ["fd7b7741686fa44903f1e4b61d7db869939f402b4acedc044767922c7d309983"],
},
"targets": {
"threshold": 1,
"keyids": ["40e35e8f6003ab90d104710cf88901edab931597401f91c19eeb366060ab3d53"],
},
"timestamp": {
"threshold": 1,
"keyids": ["09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1"],
},
},
});
let encoded = serde_json::to_value(&root).unwrap();
assert_eq!(encoded, jsn);
let decoded: RootMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, root);
}
fn jsn_root_metadata_without_keyid_hash_algos() -> serde_json::Value {
json!({
"_type": "root",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"consistent_snapshot": false,
"keys": {
"12435b260b6172bd750aeb102f54a347c56b109e0524ab1f144593c07af66356": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "68d9ecb387371005a8eb8e60105305c34356a8fcd859d7fef3cc228bf2b2b3b2",
},
},
"3af6b427c05274532231760f39d81212fdf8ac1a9f8fddf12722623ccec02fec": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "1410ae3053aa70bbfa98428a879d64d3002a3578f7dfaaeb1cb0764e860f7e0b",
},
},
"b9c336828063cf4fe5348e9fe2d86827c7b3104a76b1f4484a56bbef1ef08cfb": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "166376c90a7f717d027056272f361c252fb050bed1a067ff2089a0302fbab73d",
},
},
"e0294a3f17cc8563c3ed5fceb3bd8d3f6bfeeaca499b5c9572729ae015566554": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "eb8ac26b5c9ef0279e3be3e82262a93bce16fe58ee422500d38caf461c65a3b6",
},
}
},
"roles": {
"root": {
"threshold": 1,
"keyids": ["e0294a3f17cc8563c3ed5fceb3bd8d3f6bfeeaca499b5c9572729ae015566554"],
},
"snapshot": {
"threshold": 1,
"keyids": ["12435b260b6172bd750aeb102f54a347c56b109e0524ab1f144593c07af66356"],
},
"targets": {
"threshold": 1,
"keyids": ["b9c336828063cf4fe5348e9fe2d86827c7b3104a76b1f4484a56bbef1ef08cfb"],
},
"timestamp": {
"threshold": 1,
"keyids": ["3af6b427c05274532231760f39d81212fdf8ac1a9f8fddf12722623ccec02fec"],
},
},
})
}
#[test]
fn de_ser_root_metadata_without_keyid_hash_algorithms() {
let jsn = jsn_root_metadata_without_keyid_hash_algos();
let decoded: RootMetadata = serde_json::from_value(jsn.clone()).unwrap();
let encoded = serde_json::to_value(decoded).unwrap();
assert_eq!(jsn, encoded);
}
#[test]
fn de_ser_root_metadata_wrong_key_id() {
let jsn = jsn_root_metadata_without_keyid_hash_algos();
let mut jsn_str = str::from_utf8(&Json::canonicalize(&jsn).unwrap())
.unwrap()
.to_owned();
// Replace the key id to something else.
jsn_str = jsn_str.replace(
"12435b260b6172bd750aeb102f54a347c56b109e0524ab1f144593c07af66356",
"00435b260b6172bd750aeb102f54a347c56b109e0524ab1f144593c07af66356",
);
let decoded: RootMetadata = serde_json::from_str(&jsn_str).unwrap();
assert_eq!(3, decoded.keys.len());
}
#[test]
fn sign_and_verify_root_metadata() {
let jsn = jsn_root_metadata_without_keyid_hash_algos();
let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let decoded: RootMetadata = serde_json::from_value(jsn).unwrap();
let signed: SignedMetadata<crate::interchange::cjson::Json, _> =
SignedMetadata::new(&decoded, &root_key).unwrap();
let raw_root = signed.to_raw().unwrap();
assert_matches!(
verify_signatures(&raw_root, 1, &[root_key.public().clone()]),
Ok(_)
);
}
#[test]
fn verify_signed_serialized_root_metadata() {
let jsn = json!({
"signatures": [{
"keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a",
"sig": "c4ba838e0d3f783716393a4d691f568f840733ff488bb79ac68287e97e0b31d63fcef392dbc978e878c2103ba231905af634cc651d6f0e63a35782d051ac6e00"
}],
"signed": jsn_root_metadata_without_keyid_hash_algos()
});
let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let decoded: SignedMetadata<crate::interchange::cjson::Json, RootMetadata> =
serde_json::from_value(jsn).unwrap();
let raw_root = decoded.to_raw().unwrap();
assert_matches!(
verify_signatures(&raw_root, 1, &[root_key.public().clone()]),
Ok(_)
);
}
#[test]
fn verify_signed_serialized_root_metadata_with_duplicate_sig() {
let jsn = json!({
"signatures": [{
"keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a",
"sig": "c4ba838e0d3f783716393a4d691f568f840733ff488bb79ac68287e97e0b31d63fcef392dbc978e878c2103ba231905af634cc651d6f0e63a35782d051ac6e00"
},
{
"keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a",
"sig": "c4ba838e0d3f783716393a4d691f568f840733ff488bb79ac68287e97e0b31d63fcef392dbc978e878c2103ba231905af634cc651d6f0e63a35782d051ac6e00"
}],
"signed": jsn_root_metadata_without_keyid_hash_algos()
});
let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let decoded: SignedMetadata<crate::interchange::cjson::Json, RootMetadata> =
serde_json::from_value(jsn).unwrap();
let raw_root = decoded.to_raw().unwrap();
assert_matches!(
verify_signatures(&raw_root, 2, &[root_key.public().clone()]),
Err(Error::VerificationFailure(_))
);
assert_matches!(
verify_signatures(&raw_root, 1, &[root_key.public().clone()]),
Ok(_)
);
}
fn verify_signature_with_unknown_fields<M>(mut metadata: serde_json::Value)
where
M: Metadata,
{
let key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let public_keys = vec![key.public().clone()];
let mut standard = SignedMetadataBuilder::<Json, M>::from_raw_metadata(metadata.clone())
.unwrap()
.sign(&key)
.unwrap()
.build()
.to_raw()
.unwrap()
.parse_untrusted()
.unwrap();
metadata.as_object_mut().unwrap().insert(
"custom".into(),
json!({
"metadata": ["please", "sign", "me"],
"this-too": 42,
}),
);
let mut custom = SignedMetadataBuilder::<Json, M>::from_raw_metadata(metadata)
.unwrap()
.sign(&key)
.unwrap()
.build()
.to_raw()
.unwrap()
.parse_untrusted()
.unwrap();
// Ensure the signatures are valid as-is.
assert_matches!(
verify_signatures(&standard.to_raw().unwrap(), 1, &public_keys),
Ok(_)
);
assert_matches!(
verify_signatures(&custom.to_raw().unwrap(), 1, std::iter::once(key.public())),
Ok(_)
);
// But not if the metadata was signed with custom fields and they are now missing or
// unexpected new fields appear.
std::mem::swap(&mut standard.metadata, &mut custom.metadata);
assert_matches!(
verify_signatures(
&standard.to_raw().unwrap(),
1,
std::iter::once(key.public())
),
Err(Error::VerificationFailure(_))
);
assert_matches!(
verify_signatures(&custom.to_raw().unwrap(), 1, std::iter::once(key.public())),
Err(Error::VerificationFailure(_))
);
}
#[test]
fn unknown_fields_included_in_root_metadata_signature() {
verify_signature_with_unknown_fields::<RootMetadata>(
jsn_root_metadata_without_keyid_hash_algos(),
);
}
#[test]
fn unknown_fields_included_in_timestamp_metadata_signature() {
verify_signature_with_unknown_fields::<TimestampMetadata>(make_timestamp());
}
#[test]
fn unknown_fields_included_in_snapshot_metadata_signature() {
verify_signature_with_unknown_fields::<SnapshotMetadata>(make_snapshot());
}
#[test]
fn unknown_fields_included_in_targets_metadata_signature() {
verify_signature_with_unknown_fields::<TargetsMetadata>(make_targets());
}
#[test]
fn serde_timestamp_metadata() {
let description = MetadataDescription::new(
1,
Some(100),
hashmap! { HashAlgorithm::Sha256 => HashValue::new(vec![]) },
)
.unwrap();
let timestamp = TimestampMetadataBuilder::from_metadata_description(description)
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.build()
.unwrap();
let jsn = json!({
"_type": "timestamp",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"snapshot.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": "",
},
},
}
});
let encoded = serde_json::to_value(&timestamp).unwrap();
assert_eq!(encoded, jsn);
let decoded: TimestampMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, timestamp);
}
// Deserialize timestamp metadata with optional length and hashes
#[test]
fn serde_timestamp_metadata_without_length_and_hashes() {
let description = MetadataDescription::new(1, None, HashMap::new()).unwrap();
let timestamp = TimestampMetadataBuilder::from_metadata_description(description)
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.build()
.unwrap();
let jsn = json!({
"_type": "timestamp",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"snapshot.json": {
"version": 1
},
}
});
let encoded = serde_json::to_value(&timestamp).unwrap();
assert_eq!(encoded, jsn);
let decoded: TimestampMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, timestamp);
}
#[test]
fn serde_timestamp_metadata_missing_snapshot() {
let jsn = json!({
"_type": "timestamp",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {}
});
assert_matches!(
serde_json::from_value::<TimestampMetadata>(jsn),
Err(ref err) if err.to_string() == "missing field `snapshot.json`"
);
}
#[test]
fn serde_timestamp_metadata_extra_metadata() {
let jsn = json!({
"_type": "timestamp",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"snapshot.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": "",
},
},
"targets.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": "",
},
},
}
});
assert_matches!(
serde_json::from_value::<TimestampMetadata>(jsn),
Err(ref err) if err.to_string() ==
"unknown field `targets.json`, expected `snapshot.json`"
);
}
#[test]
fn serde_snapshot_metadata() {
let snapshot = SnapshotMetadataBuilder::new()
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.insert_metadata_description(
MetadataPath::new("targets").unwrap(),
MetadataDescription::new(
1,
Some(100),
hashmap! { HashAlgorithm::Sha256 => HashValue::new(vec![]) },
)
.unwrap(),
)
.build()
.unwrap();
let jsn = json!({
"_type": "snapshot",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"targets.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": "",
},
},
},
});
let encoded = serde_json::to_value(&snapshot).unwrap();
assert_eq!(encoded, jsn);
let decoded: SnapshotMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, snapshot);
}
// Deserialize snapshot metadata with optional length and hashes
#[test]
fn serde_snapshot_optional_length_and_hashes() {
let snapshot = SnapshotMetadataBuilder::new()
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.insert_metadata_description(
MetadataPath::new("targets").unwrap(),
MetadataDescription::new(1, None, HashMap::new()).unwrap(),
)
.build()
.unwrap();
let jsn = json!({
"_type": "snapshot",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"targets.json": {
"version": 1,
},
},
});
let encoded = serde_json::to_value(&snapshot).unwrap();
assert_eq!(encoded, jsn);
let decoded: SnapshotMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, snapshot);
}
#[test]
fn serde_targets_metadata() {
block_on(async {
let targets = TargetsMetadataBuilder::new()
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.insert_target_from_slice(
TargetPath::new("insert-target-from-slice").unwrap(),
&b"foo"[..],
&[HashAlgorithm::Sha256],
)
.unwrap()
.insert_target_from_reader(
TargetPath::new("insert-target-from-reader").unwrap(),
&b"foo"[..],
&[HashAlgorithm::Sha256],
)
.await
.unwrap()
.insert_target_description(
TargetPath::new("insert-target-description-from-slice-with-custom").unwrap(),
TargetDescription::from_slice_with_custom(
&b"foo"[..],
&[HashAlgorithm::Sha256],
HashMap::new(),
)
.unwrap(),
)
.insert_target_description(
TargetPath::new("insert-target-description-from-reader-with-custom").unwrap(),
TargetDescription::from_reader_with_custom(
&b"foo"[..],
&[HashAlgorithm::Sha256],
hashmap! {
"foo".into() => 1.into(),
"bar".into() => "baz".into(),
},
)
.await
.unwrap(),
)
.build()
.unwrap();
let jsn = json!({
"_type": "targets",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"targets": {
"insert-target-from-slice": {
"length": 3,
"hashes": {
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483\
bfa0f98a5e886266e7ae",
},
},
"insert-target-description-from-slice-with-custom": {
"length": 3,
"hashes": {
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483\
bfa0f98a5e886266e7ae",
},
},
"insert-target-from-reader": {
"length": 3,
"hashes": {
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483\
bfa0f98a5e886266e7ae",
},
},
"insert-target-description-from-reader-with-custom": {
"length": 3,
"hashes": {
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483\
bfa0f98a5e886266e7ae",
},
"custom": {
"foo": 1,
"bar": "baz",
},
},
},
});
let encoded = serde_json::to_value(&targets).unwrap();
assert_eq!(encoded, jsn);
let decoded: TargetsMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, targets);
})
}
#[test]
fn serde_targets_with_delegations_metadata() {
let key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let delegations = Delegations::new(
hashmap! { key.public().key_id().clone() => key.public().clone() },
vec![Delegation::new(
MetadataPath::new("foo/bar").unwrap(),
false,
1,
hashset!(key.public().key_id().clone()),
hashset!(TargetPath::new("baz/quux").unwrap()),
)
.unwrap()],
)
.unwrap();
let targets = TargetsMetadataBuilder::new()
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.delegations(delegations)
.build()
.unwrap();
let jsn = json!({
"_type": "targets",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"targets": {},
"delegations": {
"keys": {
"a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyid_hash_algorithms": ["sha256", "sha512"],
"keyval": {
"public": "eb8ac26b5c9ef0279e3be3e82262a93bce16fe58\
ee422500d38caf461c65a3b6",
}
},
},
"roles": [
{
"role": "foo/bar",
"terminating": false,
"threshold": 1,
"keyids": ["a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a"],
"paths": ["baz/quux"],
},
],
}
});
let encoded = serde_json::to_value(&targets).unwrap();
assert_eq!(encoded, jsn);
let decoded: TargetsMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, targets);
}
#[test]
fn serde_signed_metadata() {
let snapshot = SnapshotMetadataBuilder::new()
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.insert_metadata_description(
MetadataPath::new("targets").unwrap(),
MetadataDescription::new(
1,
Some(100),
hashmap! { HashAlgorithm::Sha256 => HashValue::new(vec![]) },
)
.unwrap(),
)
.build()
.unwrap();
let key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let signed = SignedMetadata::<Json, _>::new(&snapshot, &key).unwrap();
let jsn = json!({
"signatures": [
{
"keyid": "a9f3ebc9b138762563a9c27b6edd439959e559709babd123e8d449ba2c18c61a",
"sig": "ea48ddc7b3ea614b394e508eb8722100f94ff1a4e3aac3af09d\
a0dada4f878431e8ac26160833405ec239924dfe62edf605fee8294\
c49b4acade55c76e817602",
}
],
"signed": {
"_type": "snapshot",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"targets.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": "",
},
},
},
},
});
let encoded = serde_json::to_value(&signed).unwrap();
assert_eq!(encoded, jsn, "{:#?} != {:#?}", encoded, jsn);
let decoded: SignedMetadata<Json, SnapshotMetadata> =
serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, signed);
}
///////////////////////////////////////////////////////////////////////////////////////////////
//
// Here there be test cases about what metadata is allowed to be parsed wherein we do all sorts
// of naughty things and make sure the parsers puke appropriately.
// ______________
// ,===:'., `-._
// `:.`---.__ `-._
// `:. `--. `.
// \. `. `.
// (,,(, \. `. ____,-`.,
// (,' `/ \. ,--.___`.'
// , ,' ,--. `, \.;' `
// `{o, { \ : \;
// |,,' / / //
// j;; / ,' ,-//. ,---. ,
// \;' / ,' / _ \ / _ \ ,'/
// \ `' / \ `' / \ `.' /
// `.___,' `.__,' `.__,'
//
///////////////////////////////////////////////////////////////////////////////////////////////
// TODO test for mismatched ed25519/rsa keys/schemes
fn make_root() -> serde_json::Value {
let root_key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8).unwrap();
let snapshot_key = Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8).unwrap();
let targets_key = Ed25519PrivateKey::from_pkcs8(ED25519_3_PK8).unwrap();
let timestamp_key = Ed25519PrivateKey::from_pkcs8(ED25519_4_PK8).unwrap();
let root = RootMetadataBuilder::new()
.expires(Utc.ymd(2038, 1, 1).and_hms(0, 0, 0))
.root_key(root_key.public().clone())
.snapshot_key(snapshot_key.public().clone())
.targets_key(targets_key.public().clone())
.timestamp_key(timestamp_key.public().clone())
.build()
.unwrap();
serde_json::to_value(&root).unwrap()
}
fn make_snapshot() -> serde_json::Value {
let snapshot = SnapshotMetadataBuilder::new()
.expires(Utc.ymd(2038, 1, 1).and_hms(0, 0, 0))
.build()
.unwrap();
serde_json::to_value(&snapshot).unwrap()
}
fn make_timestamp() -> serde_json::Value {
let description =
MetadataDescription::from_slice(&[][..], 1, &[HashAlgorithm::Sha256]).unwrap();
let timestamp = TimestampMetadataBuilder::from_metadata_description(description)
.expires(Utc.ymd(2017, 1, 1).and_hms(0, 0, 0))
.build()
.unwrap();
serde_json::to_value(&timestamp).unwrap()
}
fn make_targets() -> serde_json::Value {
let targets =
TargetsMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), hashmap!(), None)
.unwrap();
serde_json::to_value(&targets).unwrap()
}
fn make_delegations() -> serde_json::Value {
let key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8)
.unwrap()
.public()
.clone();
let delegations = Delegations::new(
hashmap! { key.key_id().clone() => key.clone() },
vec![Delegation::new(
MetadataPath::new("foo").unwrap(),
false,
1,
hashset!(key.key_id().clone()),
hashset!(TargetPath::new("bar").unwrap()),
)
.unwrap()],
)
.unwrap();
serde_json::to_value(&delegations).unwrap()
}
fn make_delegation() -> serde_json::Value {
let key = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8)
.unwrap()
.public()
.clone();
let delegation = Delegation::new(
MetadataPath::new("foo").unwrap(),
false,
1,
hashset!(key.key_id().clone()),
hashset!(TargetPath::new("bar").unwrap()),
)
.unwrap();
serde_json::to_value(&delegation).unwrap()
}
fn set_version(value: &mut serde_json::Value, version: i64) {
match value.as_object_mut() {
Some(obj) => {
let _ = obj.insert("version".into(), json!(version));
}
None => panic!(),
}
}
// Refuse to deserialize root metadata if the version is not > 0
#[test]
fn deserialize_json_root_illegal_version() {
let mut root_json = make_root();
set_version(&mut root_json, 0);
assert!(serde_json::from_value::<RootMetadata>(root_json.clone()).is_err());
let mut root_json = make_root();
set_version(&mut root_json, -1);
assert!(serde_json::from_value::<RootMetadata>(root_json).is_err());
}
// Refuse to deserialize root metadata if it contains duplicate keys
#[test]
fn deserialize_json_root_duplicate_keys() {
let root_json = r#"{
"_type": "root",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"consistent_snapshot": false,
"keys": {
"09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "1410ae3053aa70bbfa98428a879d64d3002a3578f7dfaaeb1cb0764e860f7e0b"
}
},
"09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "166376c90a7f717d027056272f361c252fb050bed1a067ff2089a0302fbab73d"
}
}
},
"roles": {
"root": {
"threshold": 1,
"keyids": ["09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1"]
},
"snapshot": {
"threshold": 1,
"keyids": ["09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1"]
},
"targets": {
"threshold": 1,
"keyids": ["09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1"]
},
"timestamp": {
"threshold": 1,
"keyids": ["09557ed63f91b5b95917d46f66c63ea79bdaef1b008ba823808bca849f1d18a1"]
}
}
}"#;
match serde_json::from_str::<RootMetadata>(root_json) {
Err(ref err) if err.is_data() => {
assert!(
err.to_string().starts_with("Cannot have duplicate keys"),
"unexpected err: {:?}",
err
);
}
result => panic!("unexpected result: {:?}", result),
}
}
fn set_threshold(value: &mut serde_json::Value, threshold: i32) {
match value.as_object_mut() {
Some(obj) => {
let _ = obj.insert("threshold".into(), json!(threshold));
}
None => panic!(),
}
}
// Refuse to deserialize role definitions with illegal thresholds
#[test]
fn deserialize_json_role_definition_illegal_threshold() {
let role_def = RoleDefinition::new(
1,
vec![Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8)
.unwrap()
.public()
.key_id()
.clone()],
)
.unwrap();
let mut jsn = serde_json::to_value(&role_def).unwrap();
set_threshold(&mut jsn, 0);
assert!(serde_json::from_value::<RoleDefinition>(jsn).is_err());
let mut jsn = serde_json::to_value(&role_def).unwrap();
set_threshold(&mut jsn, -1);
assert!(serde_json::from_value::<RoleDefinition>(jsn).is_err());
let role_def = RoleDefinition::new(
2,
vec![
Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8)
.unwrap()
.public()
.key_id()
.clone(),
Ed25519PrivateKey::from_pkcs8(ED25519_2_PK8)
.unwrap()
.public()
.key_id()
.clone(),
],
)
.unwrap();
let mut jsn = serde_json::to_value(&role_def).unwrap();
set_threshold(&mut jsn, 3);
assert!(serde_json::from_value::<RoleDefinition>(jsn).is_err());
}
// Refuse to deserialize root metadata with wrong type field
#[test]
fn deserialize_json_root_bad_type() {
let mut root = make_root();
let _ = root
.as_object_mut()
.unwrap()
.insert("_type".into(), json!("snapshot"));
assert!(serde_json::from_value::<RootMetadata>(root).is_err());
}
// Refuse to deserialize root metadata with unknown spec version
#[test]
fn deserialize_json_root_bad_spec_version() {
let mut root = make_root();
let _ = root
.as_object_mut()
.unwrap()
.insert("spec_version".into(), json!("0"));
assert!(serde_json::from_value::<RootMetadata>(root).is_err());
}
// Refuse to deserialize role definitions with duplicated key ids
#[test]
fn deserialize_json_role_definition_duplicate_key_ids() {
let key_id = Ed25519PrivateKey::from_pkcs8(ED25519_1_PK8)
.unwrap()
.public()
.key_id()
.clone();
let role_def = RoleDefinition::new(1, vec![key_id.clone()]).unwrap();
let mut jsn = serde_json::to_value(&role_def).unwrap();
match jsn.as_object_mut() {
Some(obj) => match obj.get_mut("keyids").unwrap().as_array_mut() {
Some(arr) => arr.push(json!(key_id)),
None => panic!(),
},
None => panic!(),
}
assert!(serde_json::from_value::<RoleDefinition>(jsn).is_err());
}
// Refuse to deserialize snapshot metadata with illegal versions
#[test]
fn deserialize_json_snapshot_illegal_version() {
let mut snapshot = make_snapshot();
set_version(&mut snapshot, 0);
assert!(serde_json::from_value::<SnapshotMetadata>(snapshot).is_err());
let mut snapshot = make_snapshot();
set_version(&mut snapshot, -1);
assert!(serde_json::from_value::<SnapshotMetadata>(snapshot).is_err());
}
// Refuse to deserialize snapshot metadata with wrong type field
#[test]
fn deserialize_json_snapshot_bad_type() {
let mut snapshot = make_snapshot();
let _ = snapshot
.as_object_mut()
.unwrap()
.insert("_type".into(), json!("root"));
assert!(serde_json::from_value::<SnapshotMetadata>(snapshot).is_err());
}
// Refuse to deserialize snapshot metadata with unknown spec version
#[test]
fn deserialize_json_snapshot_spec_version() {
let mut snapshot = make_snapshot();
let _ = snapshot
.as_object_mut()
.unwrap()
.insert("spec_version".into(), json!("0"));
assert!(serde_json::from_value::<SnapshotMetadata>(snapshot).is_err());
}
// Refuse to deserialize snapshot metadata if it contains duplicate metadata
#[test]
fn deserialize_json_snapshot_duplicate_metadata() {
let snapshot_json = r#"{
"_type": "snapshot",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"targets.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": ""
}
},
"targets.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": ""
}
}
}
}"#;
match serde_json::from_str::<SnapshotMetadata>(snapshot_json) {
Err(ref err) if err.is_data() => {}
result => panic!("unexpected result: {:?}", result),
}
}
// Refuse to deserialize timestamp metadata with illegal versions
#[test]
fn deserialize_json_timestamp_illegal_version() {
let mut timestamp = make_timestamp();
set_version(&mut timestamp, 0);
assert!(serde_json::from_value::<TimestampMetadata>(timestamp).is_err());
let mut timestamp = make_timestamp();
set_version(&mut timestamp, -1);
assert!(serde_json::from_value::<TimestampMetadata>(timestamp).is_err());
}
// Refuse to deserialize timestamp metadata with wrong type field
#[test]
fn deserialize_json_timestamp_bad_type() {
let mut timestamp = make_timestamp();
let _ = timestamp
.as_object_mut()
.unwrap()
.insert("_type".into(), json!("root"));
assert!(serde_json::from_value::<TimestampMetadata>(timestamp).is_err());
}
// Refuse to deserialize timestamp metadata with unknown spec version
#[test]
fn deserialize_json_timestamp_bad_spec_version() {
let mut timestamp = make_timestamp();
let _ = timestamp
.as_object_mut()
.unwrap()
.insert("spec_version".into(), json!("0"));
assert!(serde_json::from_value::<TimestampMetadata>(timestamp).is_err());
}
// Refuse to deserialize timestamp metadata if it contains duplicate metadata
#[test]
fn deserialize_json_timestamp_duplicate_metadata() {
let timestamp_json = r#"{
"_type": "timestamp",
"spec_version": "1.0",
"version": 1,
"expires": "2017-01-01T00:00:00Z",
"meta": {
"snapshot.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": ""
}
},
"snapshot.json": {
"version": 1,
"length": 100,
"hashes": {
"sha256": ""
}
}
}
}"#;
match serde_json::from_str::<TimestampMetadata>(timestamp_json) {
Err(ref err) if err.is_data() => {}
result => panic!("unexpected result: {:?}", result),
}
}
// Refuse to deserialize targets metadata with illegal versions
#[test]
fn deserialize_json_targets_illegal_version() {
let mut targets = make_targets();
set_version(&mut targets, 0);
assert!(serde_json::from_value::<TargetsMetadata>(targets).is_err());
let mut targets = make_targets();
set_version(&mut targets, -1);
assert!(serde_json::from_value::<TargetsMetadata>(targets).is_err());
}
// Refuse to deserialize targets metadata with wrong type field
#[test]
fn deserialize_json_targets_bad_type() {
let mut targets = make_targets();
let _ = targets
.as_object_mut()
.unwrap()
.insert("_type".into(), json!("root"));
assert!(serde_json::from_value::<TargetsMetadata>(targets).is_err());
}
// Refuse to deserialize targets metadata with unknown spec version
#[test]
fn deserialize_json_targets_bad_spec_version() {
let mut targets = make_targets();
let _ = targets
.as_object_mut()
.unwrap()
.insert("spec_version".into(), json!("0"));
assert!(serde_json::from_value::<TargetsMetadata>(targets).is_err());
}
// Refuse to deserialize delegations with no keys
#[test]
fn deserialize_json_delegations_no_keys() {
let mut delegations = make_delegations();
delegations
.as_object_mut()
.unwrap()
.get_mut("keys")
.unwrap()
.as_object_mut()
.unwrap()
.clear();
assert!(serde_json::from_value::<Delegations>(delegations).is_err());
}
// Refuse to deserialize delegations with no roles
#[test]
fn deserialize_json_delegations_no_roles() {
let mut delegations = make_delegations();
delegations
.as_object_mut()
.unwrap()
.get_mut("roles")
.unwrap()
.as_array_mut()
.unwrap()
.clear();
assert!(serde_json::from_value::<Delegations>(delegations).is_err());
}
// Refuse to deserialize delegations with duplicated roles
#[test]
fn deserialize_json_delegations_duplicated_roles() {
let mut delegations = make_delegations();
let dupe = delegations
.as_object()
.unwrap()
.get("roles")
.unwrap()
.as_array()
.unwrap()[0]
.clone();
delegations
.as_object_mut()
.unwrap()
.get_mut("roles")
.unwrap()
.as_array_mut()
.unwrap()
.push(dupe);
assert!(serde_json::from_value::<Delegations>(delegations).is_err());
}
// Refuse to deserialize a delegation with insufficient threshold
#[test]
fn deserialize_json_delegation_bad_threshold() {
let mut delegation = make_delegation();
set_threshold(&mut delegation, 0);
assert!(serde_json::from_value::<Delegation>(delegation).is_err());
let mut delegation = make_delegation();
set_threshold(&mut delegation, 2);
assert!(serde_json::from_value::<Delegation>(delegation).is_err());
}
// Refuse to deserialize a delegation with duplicate key IDs
#[test]
fn deserialize_json_delegation_duplicate_key_ids() {
let mut delegation = make_delegation();
let dupe = delegation
.as_object()
.unwrap()
.get("keyids")
.unwrap()
.as_array()
.unwrap()[0]
.clone();
delegation
.as_object_mut()
.unwrap()
.get_mut("keyids")
.unwrap()
.as_array_mut()
.unwrap()
.push(dupe);
assert!(serde_json::from_value::<Delegation>(delegation).is_err());
}
// Refuse to deserialize a delegation with duplicate paths
#[test]
fn deserialize_json_delegation_duplicate_paths() {
let mut delegation = make_delegation();
let dupe = delegation
.as_object()
.unwrap()
.get("paths")
.unwrap()
.as_array()
.unwrap()[0]
.clone();
delegation
.as_object_mut()
.unwrap()
.get_mut("paths")
.unwrap()
.as_array_mut()
.unwrap()
.push(dupe);
assert!(serde_json::from_value::<Delegation>(delegation).is_err());
}
// Refuse to deserialize a Delegations struct with duplicate keys
#[test]
fn deserialize_json_delegations_duplicate_keys() {
let delegations_json = r#"{
"keys": {
"qfrfBrkB4lBBSDEBlZgaTGS_SrE6UfmON9kP4i3dJFY=": {
"public_key": "MCwwBwYDK2VwBQADIQDrisJrXJ7wJ5474-giYqk7zhb-WO5CJQDTjK9GHGWjtg==",
"scheme": "ed25519",
"type": "ed25519"
},
"qfrfBrkB4lBBSDEBlZgaTGS_SrE6UfmON9kP4i3dJFY=": {
"public_key": "MCwwBwYDK2VwBQADIQDrisJrXJ7wJ5474-giYqk7zhb-WO5CJQDTjK9GHGWjtg==",
"scheme": "ed25519",
"type": "ed25519"
}
},
"roles": [
{
"keyids": [
"qfrfBrkB4lBBSDEBlZgaTGS_SrE6UfmON9kP4i3dJFY="
],
"paths": [
"bar"
],
"role": "foo",
"terminating": false,
"threshold": 1
}
]
}"#;
match serde_json::from_str::<Delegations>(delegations_json) {
Err(ref err) if err.is_data() => {}
result => panic!("unexpected result: {:?}", result),
}
}
}