blob: 770fd0d7dfacba621a2e4cb5b48456233a5f8d49 [file] [log] [blame]
//! Structures used to represent TUF metadata
use chrono::DateTime;
use chrono::offset::Utc;
use serde::de::{Deserialize, DeserializeOwned, Deserializer, Error as DeserializeError};
use serde::ser::{Serialize, Serializer, Error as SerializeError};
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::marker::PhantomData;
use Result;
use crypto::{KeyId, PublicKey, Signature};
use error::Error;
use interchange::DataInterchange;
use shims;
/// Trait used to represent whether a piece of data is verified or not.
pub trait VerificationStatus {}
/// Type used to represent verified data.
pub struct Verified {}
impl VerificationStatus for Verified {}
/// Type used to represent unverified data.
pub struct Unverified {}
impl VerificationStatus for Unverified {}
/// 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,
}
/// Enum used for addressing versioned TUF metadata.
#[derive(Debug)]
pub enum MetadataVersion {
/// The metadata is unversioned.
None,
/// The metadata is addressed by a specific version number.
Number(u32),
/// The metadata is addressed by a hash prefix. Used with TUF's consistent snapshot feature.
Hash(String),
}
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),
&MetadataVersion::Hash(ref s) => format!("{}.", s),
}
}
}
/// Top level trait used for role metadata.
pub trait Metadata: Debug + PartialEq + Serialize + DeserializeOwned {}
/// A piece of raw metadata with attached signatures.
#[derive(Debug, Serialize, Deserialize)]
pub struct SignedMetadata<D, M, V>
where
D: DataInterchange,
M: Metadata,
V: VerificationStatus,
{
signatures: Vec<Signature>,
signed: D::RawData,
_interchage: PhantomData<D>,
_metadata: PhantomData<M>,
_verification: PhantomData<V>,
}
impl<D, M, V> SignedMetadata<D, M, V>
where
D: DataInterchange,
M: Metadata,
V: VerificationStatus,
{
/// An immutable reference to the signatures.
pub fn signatures(&self) -> &[Signature] {
&self.signatures
}
/// A mutable reference to the signatures.
pub fn signatures_mut(&mut self) -> &mut Vec<Signature> {
&mut self.signatures
}
/// An immutable reference to the unverified raw data.
///
/// *WARNING*: This data is untrusted.
pub fn unverified_signed(&self) -> &D::RawData {
&self.signed
}
/// Verify this metadata and convert its type to `Verified`.
pub fn verify(
self,
threshold: u32,
authorized_key_ids: &HashSet<KeyId>,
available_keys: &HashMap<KeyId, PublicKey>,
) -> Result<SignedMetadata<D, M, Verified>> {
if self.signatures.len() < 1 {
return Err(Error::VerificationFailure(
"The metadata was not signed with any authorized keys."
.into(),
));
}
if threshold < 1 {
return Err(Error::VerificationFailure(
"Threshold must be strictly greater than zero".into(),
));
}
let canonical_bytes = D::canonicalize(&self.signed)?;
let mut signatures_needed = threshold;
for sig in self.signatures.iter() {
if !authorized_key_ids.contains(sig.key_id()) {
warn!(
"Key ID {:?} is not authorized to sign root metadata.",
sig.key_id()
);
continue;
}
match available_keys.get(sig.key_id()) {
Some(ref pub_key) => {
match pub_key.verify(sig.scheme(), &canonical_bytes, sig.signature()) {
Ok(()) => {
debug!("Good signature from key ID {:?}", pub_key.key_id());
signatures_needed -= 1;
}
Err(e) => {
warn!("Bad signature from key ID {:?}: {:?}", pub_key.key_id(), e);
}
}
}
None => {
warn!(
"Key ID {:?} was not found in the set of available keys.",
sig.key_id()
);
}
}
if signatures_needed == 0 {
break;
}
}
if signatures_needed == 0 {
Ok(SignedMetadata {
signatures: self.signatures,
signed: self.signed,
_interchage: PhantomData,
_metadata: PhantomData,
_verification: PhantomData,
})
} else {
Err(Error::VerificationFailure(format!(
"Signature threshold not met: {}/{}",
threshold - signatures_needed,
threshold
)))
}
}
}
impl<D, M> SignedMetadata<D, M, Verified>
where
D: DataInterchange,
M: Metadata,
{
/// An immutable reference to the verified raw data.
pub fn signed(&self) -> &D::RawData {
self.unverified_signed()
}
}
/// Metdata for the root role.
#[derive(Debug, 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,
mut keys: Vec<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
)));
}
let keys = keys.drain(0..)
.map(|k| (k.key_id().clone(), k))
.collect::<HashMap<KeyId, PublicKey>>();
Ok(RootMetadata {
version: version,
expires: expires,
consistent_snapshot: consistent_snapshot,
keys: keys,
root: root,
snapshot: snapshot,
targets: targets,
timestamp: timestamp,
})
}
/// The version number.
pub fn version(&self) -> u32 {
self.version
}
/// An immutable reference to the metadata's expiration `DateTime`.
pub fn expires(&self) -> &DateTime<Utc> {
&self.expires
}
/// 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 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 {}
impl Serialize for RootMetadata {
fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
shims::RootMetadata::from(self)
.map_err(|e| SerializeError::custom(format!("{:?}", e)))?
.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: HashSet<KeyId>,
}
impl RoleDefinition {
/// Create a new `RoleDefinition` with a given threshold and set of authorized `KeyID`s.
pub fn new(threshold: u32, key_ids: HashSet<KeyId>) -> Result<Self> {
if threshold < 1 {
return Err(Error::IllegalArgument(format!("Threshold: {}", threshold)));
}
Ok(RoleDefinition {
threshold: threshold,
key_ids: 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) -> &HashSet<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))
})
}
}
#[cfg(test)]
mod test {
use json;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use crypto::{KeyType, PublicKey, KeyFormat};
#[test]
fn parse_spki_json() {
let mut jsn = json!({"type": "rsa", "value": {}});
let mut file = File::open(
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("rsa")
.join("spki-1.pub"),
).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let _ = jsn.as_object_mut()
.unwrap()
.get_mut("value")
.unwrap()
.as_object_mut()
.unwrap()
.insert("public".into(), json::Value::String(buf.trim().into()));
let key: PublicKey = json::from_value(jsn.clone()).unwrap();
assert_eq!(key.typ(), &KeyType::Rsa);
assert_eq!(key.format(), &KeyFormat::Spki);
let deserialized: json::Value = json::to_value(key).unwrap();
assert_eq!(deserialized, jsn);
}
#[test]
fn parse_pkcs1_json() {
let mut jsn = json!({"type": "rsa", "value": {}});
let mut file = File::open(
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("rsa")
.join("pkcs1-1.pub"),
).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let _ = jsn.as_object_mut()
.unwrap()
.get_mut("value")
.unwrap()
.as_object_mut()
.unwrap()
.insert("public".into(), json::Value::String(buf.trim().into()));
let key: PublicKey = json::from_value(jsn.clone()).unwrap();
assert_eq!(key.typ(), &KeyType::Rsa);
assert_eq!(key.format(), &KeyFormat::Pkcs1);
let deserialized: json::Value = json::to_value(key).unwrap();
assert_eq!(deserialized, jsn);
}
#[test]
fn parse_hex_json() {
let mut jsn = json!({"type": "ed25519", "value": {}});
let buf = "cf07711807f5176a4814613f3f348091dfc2b91f36b46a6abf6385f4ad14435b".to_string();
let _ = jsn.as_object_mut()
.unwrap()
.get_mut("value")
.unwrap()
.as_object_mut()
.unwrap()
.insert("public".into(), json::Value::String(buf.clone()));
let key: PublicKey = json::from_value(jsn.clone()).unwrap();
assert_eq!(key.typ(), &KeyType::Ed25519);
assert_eq!(key.format(), &KeyFormat::HexLower);
let deserialized: json::Value = json::to_value(key).unwrap();
assert_eq!(deserialized, jsn);
}
}