blob: 470ad2560c833df96eb1f0b5fed46780238b917d [file] [log] [blame]
//! Structures and functions to aid in various TUF data interchange formats.
mod cjson;
use json;
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use std::fmt::Debug;
use std::io::{Read, Write};
use Result;
use error::Error;
/// The format used for data interchange, serialization, and deserialization.
pub trait DataInterchange: Debug + PartialEq + Clone {
/// The type of data that is contained in the `signed` portion of metadata.
type RawData: Serialize + DeserializeOwned + Clone + PartialEq;
/// The data interchange's extension.
fn extension() -> &'static str;
/// A function that canonicalizes data to allow for deterministic signatures.
fn canonicalize(raw_data: &Self::RawData) -> Result<Vec<u8>>;
/// Deserialize from `RawData`.
fn deserialize<T>(raw_data: &Self::RawData) -> Result<T>
where
T: DeserializeOwned;
/// Serialize into `RawData`.
fn serialize<T>(data: &T) -> Result<Self::RawData>
where
T: Serialize;
/// Write a struct to a stream.
///
/// Note: This *MUST* write the bytes canonically for hashes to line up correctly in other
/// areas of the library.
fn to_writer<W, T: Sized>(writer: W, value: &T) -> Result<()>
where
W: Write,
T: Serialize;
/// Read a struct from a stream.
fn from_reader<R, T>(rdr: R) -> Result<T>
where
R: Read,
T: DeserializeOwned;
}
/// JSON data interchange.
///
/// # Schema
///
/// This doesn't use JSON Schema because that specification language is rage inducing. Here's
/// something else instead.
///
/// ## Common Entities
///
/// `NATURAL_NUMBER` is an integer in the range `[1, 2**32)`.
///
/// `EXPIRES` is an ISO-8601 date time in format `YYYY-MM-DD'T'hh:mm:ss'Z'`.
///
/// `KEY_ID` is the base64url encoded value of `sha256(spki(pub_key))`.
///
/// `PUB_KEY` is the following:
///
/// ```bash
/// {
/// "type": KEY_TYPE,
/// "value": PUBLIC
/// }
/// ```
///
/// `PUBLIC` is a base64url encoded `SubjectPublicKeyInfo` DER public key.
///
/// `KEY_TYPE` is a string (either `rsa` or `ed25519`).
///
/// `HASH_VALUE` is a base64url encoded hash value.
///
/// `METADATA_DESCRIPTION` is the following:
///
/// ```bash
/// {
/// "version": NATURAL_NUMBER,
/// "size": NATURAL_NUMBER,
/// "hashes": {
/// HASH_ALGORITHM: HASH_VALUE
/// ...
/// }
/// }
/// ```
///
/// ## `SignedMetadata`
///
/// ```bash
/// {
/// "signatures": [SIGNATURE],
/// "signed": SIGNED
/// }
/// ```
///
/// `SIGNED` is one of:
///
/// - `RootMetadata`
/// - `SnapshotMetadata`
/// - `TargetsMetadata`
/// - `TimestampMetadata`
///
/// The the elements of `signatures` must have unique `key_id`s.
///
/// ## `RootMetadata`
///
/// ```bash
/// {
/// "type": "root",
/// "version": NATURAL_NUMBER,
/// "expires": EXPIRES,
/// "keys": {
/// KEY_ID: PUB_KEY,
/// ...
/// },
/// "root": ROLE_DESCRIPTION,
/// "snapshot": ROLE_DESCRIPTION,
/// "targets": ROLE_DESCRIPTION,
/// "timestamp": ROLE_DESCRIPTION
/// }
/// ```
///
/// `ROLE_DESCRIPTION` is the following:
///
/// ```bash
/// {
/// "threshold": NATURAL_NUMBER,
/// "key_ids": [KEY_ID]
/// }
/// ```
///
/// ## `SnapshotMetadata`
///
/// ```bash
/// {
/// "type": "snapshot",
/// "version": NATURAL_NUMBER,
/// "expires": EXPIRES,
/// "meta": {
/// META_PATH: METADATA_DESCRIPTION
/// }
/// }
/// ```
///
/// `META_PATH` is a string.
///
///
/// ## `TargetsMetadata`
///
/// ```bash
/// {
/// "type": "timestamp",
/// "version": NATURAL_NUMBER,
/// "expires": EXPIRES,
/// "targets": {
/// TARGET_PATH: TARGET_DESCRIPTION
/// ...
/// },
/// "delegations": DELEGATIONS
/// }
/// ```
///
/// `DELEGATIONS` is optional and is described by the following:
///
/// ```bash
/// {
/// "keys": {
/// KEY_ID: PUB_KEY,
/// ...
/// },
/// "roles": {
/// ROLE: DELEGATION,
/// ...
/// }
/// }
/// ```
///
/// `DELEGATION` is:
///
/// ```bash
/// {
/// "name": ROLE,
/// "threshold": NATURAL_NUMBER,
/// "terminating": BOOLEAN,
/// "key_ids": [KEY_ID],
/// "paths": [PATH]
/// }
/// ```
///
/// `ROLE` is a string,
///
/// `PATH` is a string.
///
/// ## `TimestampMetadata`
///
/// ```bash
/// {
/// "type": "timestamp",
/// "version": NATURAL_NUMBER,
/// "expires": EXPIRES,
/// "snapshot": METADATA_DESCRIPTION
/// }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct JsonDataInterchange {}
impl DataInterchange for JsonDataInterchange {
type RawData = json::Value;
/// ```
/// use tuf::interchange::{DataInterchange, JsonDataInterchange};
///
/// assert_eq!(JsonDataInterchange::extension(), "json");
/// ```
fn extension() -> &'static str {
"json"
}
/// ```
/// use tuf::interchange::{DataInterchange, JsonDataInterchange};
/// use std::collections::HashMap;
///
/// let jsn: &[u8] = br#"{"foo": "bar", "baz": "quux"}"#;
/// let raw = JsonDataInterchange::from_reader(jsn).unwrap();
/// let out = JsonDataInterchange::canonicalize(&raw).unwrap();
/// assert_eq!(out, br#"{"baz":"quux","foo":"bar"}"#);
/// ```
fn canonicalize(raw_data: &Self::RawData) -> Result<Vec<u8>> {
cjson::canonicalize(raw_data).map_err(|e| Error::Opaque(e))
}
/// ```
/// #[macro_use]
/// extern crate serde_derive;
/// #[macro_use]
/// extern crate serde_json;
/// extern crate tuf;
///
/// use tuf::interchange::{DataInterchange, JsonDataInterchange};
/// use std::collections::HashMap;
///
/// #[derive(Deserialize, Debug, PartialEq)]
/// struct Thing {
/// foo: String,
/// bar: String,
/// }
///
/// fn main() {
/// let jsn = json!({"foo": "wat", "bar": "lol"});
/// let thing = Thing { foo: "wat".into(), bar: "lol".into() };
/// let de: Thing = JsonDataInterchange::deserialize(&jsn).unwrap();
/// assert_eq!(de, thing);
/// }
/// ```
fn deserialize<T>(raw_data: &Self::RawData) -> Result<T>
where
T: DeserializeOwned,
{
Ok(json::from_value(raw_data.clone())?)
}
/// ```
/// #[macro_use]
/// extern crate serde_derive;
/// #[macro_use]
/// extern crate serde_json;
/// extern crate tuf;
///
/// use tuf::interchange::{DataInterchange, JsonDataInterchange};
/// use std::collections::HashMap;
///
/// #[derive(Serialize)]
/// struct Thing {
/// foo: String,
/// bar: String,
/// }
///
/// fn main() {
/// let jsn = json!({"foo": "wat", "bar": "lol"});
/// let thing = Thing { foo: "wat".into(), bar: "lol".into() };
/// let se: serde_json::Value = JsonDataInterchange::serialize(&thing).unwrap();
/// assert_eq!(se, jsn);
/// }
/// ```
fn serialize<T>(data: &T) -> Result<Self::RawData>
where
T: Serialize,
{
Ok(json::to_value(data)?)
}
/// ```
/// use tuf::interchange::{DataInterchange, JsonDataInterchange};
///
/// let arr = vec![1, 2, 3];
/// let mut buf = Vec::new();
/// JsonDataInterchange::to_writer(&mut buf, &arr).unwrap();
/// assert!(&buf == b"[1, 2, 3]" || &buf == b"[1,2,3]");
/// ```
fn to_writer<W, T: Sized>(mut writer: W, value: &T) -> Result<()>
where
W: Write,
T: Serialize,
{
let bytes = Self::canonicalize(&Self::serialize(value)?)?;
writer.write_all(&bytes)?;
Ok(())
}
/// ```
/// use tuf::interchange::{DataInterchange, JsonDataInterchange};
/// use std::collections::HashMap;
///
/// let jsn: &[u8] = br#"{"foo": "bar", "baz": "quux"}"#;
/// let _: HashMap<String, String> = JsonDataInterchange::from_reader(jsn).unwrap();
/// ```
fn from_reader<R, T>(rdr: R) -> Result<T>
where
R: Read,
T: DeserializeOwned,
{
Ok(json::from_reader(rdr)?)
}
}