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, Cow};
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] = &[
".", "..", ];
#[rustfmt::skip]
static PATH_ILLEGAL_COMPONENTS_CASE_INSENSITIVE: &[&str] = &[
"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] = &[
":", "\\", "<",
">",
"\"",
"|",
"?",
"*",
"\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(())
}
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Role {
#[serde(rename = "root")]
Root,
#[serde(rename = "snapshot")]
Snapshot,
#[serde(rename = "targets")]
Targets,
#[serde(rename = "timestamp")]
Timestamp,
}
impl Role {
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_ref()) => true,
_ => false,
}
}
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())
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
pub enum MetadataVersion {
None,
Number(u32),
}
impl Display for MetadataVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MetadataVersion::None => f.write_str("none"),
MetadataVersion::Number(version) => write!(f, "{}", version),
}
}
}
impl MetadataVersion {
pub fn prefix(&self) -> String {
match *self {
MetadataVersion::None => String::new(),
MetadataVersion::Number(ref x) => format!("{}.", x),
}
}
}
pub trait Metadata: Debug + PartialEq + Serialize + DeserializeOwned {
const ROLE: Role;
fn version(&self) -> u32;
fn expires(&self) -> &DateTime<Utc>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawSignedMetadata<D, M> {
bytes: Vec<u8>,
_marker: PhantomData<(D, M)>,
}
impl<D, M> RawSignedMetadata<D, M>
where
D: DataInterchange,
M: Metadata,
{
pub fn new(bytes: Vec<u8>) -> Self {
Self {
bytes,
_marker: PhantomData,
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn parse_untrusted(&self) -> Result<SignedMetadata<D, M>> {
D::from_slice(&self.bytes)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
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> {
pub fn root(&self) -> Option<&RawSignedMetadata<D, RootMetadata>> {
self.root.as_ref()
}
pub fn targets(&self) -> Option<&RawSignedMetadata<D, TargetsMetadata>> {
self.targets.as_ref()
}
pub fn snapshot(&self) -> Option<&RawSignedMetadata<D, SnapshotMetadata>> {
self.snapshot.as_ref()
}
pub fn timestamp(&self) -> Option<&RawSignedMetadata<D, TimestampMetadata>> {
self.timestamp.as_ref()
}
}
#[derive(Default)]
pub struct RawSignedMetadataSetBuilder<D>
where
D: DataInterchange,
{
metadata: RawSignedMetadataSet<D>,
}
impl<D> RawSignedMetadataSetBuilder<D>
where
D: DataInterchange,
{
pub fn new() -> Self {
Self {
metadata: RawSignedMetadataSet {
root: None,
targets: None,
snapshot: None,
timestamp: None,
},
}
}
pub fn root(mut self, root: RawSignedMetadata<D, RootMetadata>) -> Self {
self.metadata.root = Some(root);
self
}
pub fn targets(mut self, targets: RawSignedMetadata<D, TargetsMetadata>) -> Self {
self.metadata.targets = Some(targets);
self
}
pub fn snapshot(mut self, snapshot: RawSignedMetadata<D, SnapshotMetadata>) -> Self {
self.metadata.snapshot = Some(snapshot);
self
}
pub fn timestamp(mut self, timestamp: RawSignedMetadata<D, TimestampMetadata>) -> Self {
self.metadata.timestamp = Some(timestamp);
self
}
pub fn build(self) -> RawSignedMetadataSet<D> {
self.metadata
}
}
#[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,
{
pub fn from_metadata(metadata: &M) -> Result<Self> {
let metadata = D::serialize(metadata)?;
Self::from_raw_metadata(metadata)
}
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,
})
}
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)
}
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,
}
}
}
#[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,
{
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,
})
}
pub fn to_raw(&self) -> Result<RawSignedMetadata<D, M>> {
let bytes = D::canonicalize(&D::serialize(self)?)?;
Ok(RawSignedMetadata::new(bytes))
}
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(())
}
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(())
}
pub fn signatures(&self) -> &[Signature] {
&self.signatures
}
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)
}
pub fn assume_valid(&self) -> Result<M> {
D::deserialize(&self.metadata)
}
}
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 {
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(),
}
}
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
pub fn consistent_snapshot(mut self, consistent_snapshot: bool) -> Self {
self.consistent_snapshot = consistent_snapshot;
self
}
pub fn root_threshold(mut self, threshold: u32) -> Self {
self.root_threshold = threshold;
self
}
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
}
pub fn snapshot_threshold(mut self, threshold: u32) -> Self {
self.snapshot_threshold = threshold;
self
}
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
}
pub fn targets_threshold(mut self, threshold: u32) -> Self {
self.targets_threshold = threshold;
self
}
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
}
pub fn timestamp_threshold(mut self, threshold: u32) -> Self {
self.timestamp_threshold = threshold;
self
}
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
}
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)?,
)
}
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,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
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 {
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,
})
}
pub fn consistent_snapshot(&self) -> bool {
self.consistent_snapshot
}
pub fn keys(&self) -> &HashMap<KeyId, PublicKey> {
&self.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
}
})
}
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
}
})
}
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
}
})
}
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
}
})
}
pub fn root(&self) -> &RoleDefinition {
&self.root
}
pub fn snapshot(&self) -> &RoleDefinition {
&self.snapshot
}
pub fn targets(&self) -> &RoleDefinition {
&self.targets
}
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)))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoleDefinition {
threshold: u32,
key_ids: Vec<KeyId>,
}
impl RoleDefinition {
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 })
}
pub fn threshold(&self) -> u32 {
self.threshold
}
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)))
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct MetadataPath(Cow<'static, str>);
impl MetadataPath {
pub fn root() -> Self {
MetadataPath(Role::Root.name().into())
}
pub fn timestamp() -> Self {
MetadataPath(Role::Timestamp.name().into())
}
pub fn snapshot() -> Self {
MetadataPath(Role::Snapshot.name().into())
}
pub fn targets() -> Self {
MetadataPath(Role::Targets.name().into())
}
pub fn new<P: Into<Cow<'static, str>>>(path: P) -> Result<Self> {
let path = path.into();
match path.as_ref() {
"root" => Ok(MetadataPath::root()),
"timestamp" => Ok(MetadataPath::timestamp()),
"snapshot" => Ok(MetadataPath::snapshot()),
"targets" => Ok(MetadataPath::targets()),
_ => {
safe_path(&path)?;
Ok(MetadataPath(path))
}
}
}
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 From<Role> for MetadataPath {
fn from(role: Role) -> MetadataPath {
match role {
Role::Root => MetadataPath::root(),
Role::Timestamp => MetadataPath::timestamp(),
Role::Snapshot => MetadataPath::snapshot(),
Role::Targets => MetadataPath::targets(),
}
}
}
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)))
}
}
pub struct TimestampMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
snapshot: MetadataDescription,
}
impl TimestampMetadataBuilder {
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))
}
pub fn from_metadata_description(description: MetadataDescription) -> Self {
TimestampMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(1),
snapshot: description,
}
}
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
pub fn build(self) -> Result<TimestampMetadata> {
TimestampMetadata::new(self.version, self.expires, self.snapshot)
}
pub fn signed<D>(
self,
private_key: &dyn PrivateKey,
) -> Result<SignedMetadata<D, TimestampMetadata>>
where
D: DataInterchange,
{
SignedMetadata::new(&self.build()?, private_key)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimestampMetadata {
version: u32,
expires: DateTime<Utc>,
snapshot: MetadataDescription,
}
impl 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,
})
}
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)))
}
}
#[derive(Debug, Clone, PartialEq, Eq, 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 {
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,
})
}
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,
})
}
pub fn version(&self) -> u32 {
self.version
}
pub fn length(&self) -> Option<usize> {
self.length
}
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)))
}
}
pub struct SnapshotMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
meta: HashMap<MetadataPath, MetadataDescription>,
}
impl SnapshotMetadataBuilder {
pub fn new() -> Self {
SnapshotMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(7),
meta: HashMap::new(),
}
}
pub fn from_targets<D>(
targets: &SignedMetadata<D, TargetsMetadata>,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
D: DataInterchange,
{
SnapshotMetadataBuilder::new().insert_metadata(targets, hash_algs)
}
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
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)
}
pub fn insert_metadata_with_path<P, D, M>(
self,
path: P,
metadata: &SignedMetadata<D, M>,
hash_algs: &[HashAlgorithm],
) -> Result<Self>
where
P: Into<Cow<'static, str>>,
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))
}
pub fn insert_metadata_description(
mut self,
path: MetadataPath,
description: MetadataDescription,
) -> Self {
self.meta.insert(path, description);
self
}
pub fn build(self) -> Result<SnapshotMetadata> {
SnapshotMetadata::new(self.version, self.expires, self.meta)
}
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,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SnapshotMetadata {
version: u32,
expires: DateTime<Utc>,
meta: HashMap<MetadataPath, MetadataDescription>,
}
impl 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,
})
}
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)))
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize)]
pub struct TargetPath(String);
impl TargetPath {
pub fn new<P: Into<String>>(path: P) -> Result<Self> {
let path = path.into();
safe_path(&path)?;
Ok(TargetPath(path))
}
pub fn components(&self) -> Vec<String> {
self.0.split('/').map(|s| s.to_string()).collect()
}
pub fn is_child(&self, parent: &Self) -> bool {
if !parent.0.ends_with('/') {
return false;
}
self.0.starts_with(&parent.0)
}
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)
}
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("/"))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for TargetPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
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()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetDescription {
length: u64,
hashes: HashMap<HashAlgorithm, HashValue>,
custom: HashMap<String, serde_json::Value>,
}
impl TargetDescription {
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,
})
}
pub fn from_slice(buf: &[u8], hash_algs: &[HashAlgorithm]) -> Result<Self> {
Self::from_slice_with_custom(buf, hash_algs, HashMap::new())
}
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,
})
}
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
}
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,
})
}
pub fn length(&self) -> u64 {
self.length
}
pub fn hashes(&self) -> &HashMap<HashAlgorithm, HashValue> {
&self.hashes
}
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)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetsMetadata {
version: u32,
expires: DateTime<Utc>,
targets: HashMap<TargetPath, TargetDescription>,
delegations: Option<Delegations>,
}
impl 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,
})
}
pub fn targets(&self) -> &HashMap<TargetPath, TargetDescription> {
&self.targets
}
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)))
}
}
pub struct TargetsMetadataBuilder {
version: u32,
expires: DateTime<Utc>,
targets: HashMap<TargetPath, TargetDescription>,
delegations: Option<Delegations>,
}
impl TargetsMetadataBuilder {
pub fn new() -> Self {
TargetsMetadataBuilder {
version: 1,
expires: Utc::now() + Duration::days(90),
targets: HashMap::new(),
delegations: None,
}
}
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
pub fn expires(mut self, expires: DateTime<Utc>) -> Self {
self.expires = expires;
self
}
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))
}
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))
}
pub fn insert_target_description(
mut self,
path: TargetPath,
description: TargetDescription,
) -> Self {
self.targets.insert(path, description);
self
}
pub fn delegations(mut self, delegations: Delegations) -> Self {
self.delegations = Some(delegations);
self
}
pub fn build(self) -> Result<TargetsMetadata> {
TargetsMetadata::new(self.version, self.expires, self.targets, self.delegations)
}
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()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Delegations {
keys: HashMap<KeyId, PublicKey>,
roles: Vec<Delegation>,
}
impl Delegations {
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 })
}
pub fn keys(&self) -> &HashMap<KeyId, PublicKey> {
&self.keys
}
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)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Delegation {
role: MetadataPath,
terminating: bool,
threshold: u32,
key_ids: HashSet<KeyId>,
paths: HashSet<TargetPath>,
}
impl 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,
})
}
pub fn role(&self) -> &MetadataPath {
&self.role
}
pub fn terminating(&self) -> bool {
self.terminating
}
pub fn key_ids(&self) -> &HashSet<KeyId> {
&self.key_ids
}
pub fn threshold(&self) -> u32 {
self.threshold
}
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]])] = &[
(true, "foo", &[&["foo"]]),
(true, "foo", &[&["foo"], &["foo"]]),
(false, "foo", &[&["foo/"]]),
(false, "foo", &[&["foo"], &["bar"]]),
(true, "foo/bar", &[&["foo/"], &["foo/bar"]]),
(false, "foo/bar", &[&["baz/"], &["foo/bar"]]),
(
false,
"foo/bar/baz",
&[&["foo/"], &["foo/quux/"], &["foo/bar/baz"]],
),
(false, "foo", &[&[]]),
(false, "foo", &[&[], &["foo"]]),
(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() {
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();
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(
&MetadataPath::root(),
&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(
&MetadataPath::root(),
&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(&MetadataPath::root(), &raw_root, 2, &[root_key.public().clone()]),
Err(Error::MetadataMissingSignatures {
role,
number_of_valid_signatures: 1,
threshold: 2,
})
if role == MetadataPath::root()
);
assert_matches!(
verify_signatures(
&MetadataPath::root(),
&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();
assert_matches!(
verify_signatures(
&M::ROLE.into(),
&standard.to_raw().unwrap(),
1,
&public_keys
),
Ok(_)
);
assert_matches!(
verify_signatures(
&M::ROLE.into(),
&custom.to_raw().unwrap(),
1,
std::iter::once(key.public())
),
Ok(_)
);
std::mem::swap(&mut standard.metadata, &mut custom.metadata);
assert_matches!(
verify_signatures(
&M::ROLE.into(),
&standard.to_raw().unwrap(),
1,
std::iter::once(key.public())
),
Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
if role == M::ROLE.into()
);
assert_matches!(
verify_signatures(
&M::ROLE.into(),
&custom.to_raw().unwrap(),
1,
std::iter::once(key.public())
),
Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
if role == M::ROLE.into()
);
}
#[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(×tamp).unwrap();
assert_eq!(encoded, jsn);
let decoded: TimestampMetadata = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, timestamp);
}
#[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(×tamp).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);
}
#[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);
}
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(×tamp).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!(),
}
}
#[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());
}
#[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!(),
}
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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),
}
}
#[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());
}
#[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());
}
#[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());
}
#[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),
}
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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());
}
#[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),
}
}
}