blob: f0d3214e6f8cfe6d69e1bfb3863bfc31cf94c427 [file] [log] [blame]
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use std::collections::BTreeMap;
use crate::error::Error;
use crate::interchange::DataInterchange;
use crate::Result;
pub(crate) mod pretty;
pub(crate) mod shims;
pub use pretty::JsonPretty;
/// 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 hex encoded value of `sha256(cjson(pub_key))`.
///
/// `PUB_KEY` is the following:
///
/// ```bash
/// {
/// "type": KEY_TYPE,
/// "scheme": SCHEME,
/// "value": PUBLIC
/// }
/// ```
///
/// `PUBLIC` is a base64url encoded `SubjectPublicKeyInfo` DER public key.
///
/// `KEY_TYPE` is a string (either `rsa` or `ed25519`).
///
/// `SCHEME` is a string (either `ed25519`, `rsassa-pss-sha256`, or `rsassa-pss-sha512`
///
/// `HASH_VALUE` is a hex encoded hash value.
///
/// `SIG_VALUE` is a hex encoded signature value.
///
/// `METADATA_DESCRIPTION` is the following:
///
/// ```bash
/// {
/// "version": NATURAL_NUMBER,
/// "length": NATURAL_NUMBER,
/// "hashes": {
/// HASH_ALGORITHM: HASH_VALUE
/// ...
/// }
/// }
/// ```
///
/// ## `SignedMetadata`
///
/// ```bash
/// {
/// "signatures": [SIGNATURE],
/// "signed": SIGNED
/// }
/// ```
///
/// `SIGNATURE` is:
///
/// ```bash
/// {
/// "keyid": KEY_ID,
/// "signature": SIG_VALUE
/// }
/// ```
///
/// `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": [PUB_KEY, ...]
/// "roles": {
/// "root": ROLE_DESCRIPTION,
/// "snapshot": ROLE_DESCRIPTION,
/// "targets": ROLE_DESCRIPTION,
/// "timestamp": ROLE_DESCRIPTION
/// }
/// }
/// ```
///
/// `ROLE_DESCRIPTION` is the following:
///
/// ```bash
/// {
/// "threshold": NATURAL_NUMBER,
/// "keyids": [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": [PUB_KEY, ...]
/// "roles": {
/// ROLE: DELEGATION,
/// ...
/// }
/// }
/// ```
///
/// `DELEGATION` is:
///
/// ```bash
/// {
/// "name": ROLE,
/// "threshold": NATURAL_NUMBER,
/// "terminating": BOOLEAN,
/// "keyids": [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 Json;
impl DataInterchange for Json {
type RawData = serde_json::Value;
/// ```
/// # use tuf::interchange::{DataInterchange, Json};
/// assert_eq!(Json::extension(), "json");
/// ```
fn extension() -> &'static str {
"json"
}
/// ```
/// # use tuf::interchange::{DataInterchange, Json};
/// # use std::collections::HashMap;
/// let jsn: &[u8] = br#"{"foo": "bar", "baz": "quux"}"#;
/// let raw = Json::from_slice(jsn).unwrap();
/// let out = Json::canonicalize(&raw).unwrap();
/// assert_eq!(out, br#"{"baz":"quux","foo":"bar"}"#);
/// ```
fn canonicalize(raw_data: &Self::RawData) -> Result<Vec<u8>> {
canonicalize(raw_data).map_err(Error::Opaque)
}
/// ```
/// # use serde_derive::Deserialize;
/// # use serde_json::json;
/// # use std::collections::HashMap;
/// # use tuf::interchange::{DataInterchange, Json};
/// #
/// #[derive(Deserialize, Debug, PartialEq)]
/// struct Thing {
/// foo: String,
/// bar: String,
/// }
///
/// let jsn = json!({"foo": "wat", "bar": "lol"});
/// let thing = Thing { foo: "wat".into(), bar: "lol".into() };
/// let de: Thing = Json::deserialize(&jsn).unwrap();
/// assert_eq!(de, thing);
/// ```
fn deserialize<T>(raw_data: &Self::RawData) -> Result<T>
where
T: DeserializeOwned,
{
Ok(serde_json::from_value(raw_data.clone())?)
}
/// ```
/// # use serde_derive::Serialize;
/// # use serde_json::json;
/// # use std::collections::HashMap;
/// # use tuf::interchange::{DataInterchange, Json};
/// #
/// #[derive(Serialize)]
/// struct Thing {
/// foo: String,
/// bar: String,
/// }
///
/// let jsn = json!({"foo": "wat", "bar": "lol"});
/// let thing = Thing { foo: "wat".into(), bar: "lol".into() };
/// let se: serde_json::Value = Json::serialize(&thing).unwrap();
/// assert_eq!(se, jsn);
/// ```
fn serialize<T>(data: &T) -> Result<Self::RawData>
where
T: Serialize,
{
Ok(serde_json::to_value(data)?)
}
/// ```
/// # use tuf::interchange::{DataInterchange, Json};
/// # use std::collections::HashMap;
/// let jsn: &[u8] = br#"{"foo": "bar", "baz": "quux"}"#;
/// let _: HashMap<String, String> = Json::from_slice(&jsn).unwrap();
/// ```
fn from_slice<T>(slice: &[u8]) -> Result<T>
where
T: DeserializeOwned,
{
Ok(serde_json::from_slice(slice)?)
}
}
fn canonicalize(jsn: &serde_json::Value) -> std::result::Result<Vec<u8>, String> {
let converted = convert(jsn)?;
let mut buf = Vec::new();
let _ = converted.write(&mut buf); // Vec<u8> impl always succeeds (or panics).
Ok(buf)
}
enum Value {
Array(Vec<Value>),
Bool(bool),
Null,
Number(Number),
Object(BTreeMap<String, Value>),
String(String),
}
impl Value {
fn write(&self, buf: &mut Vec<u8>) -> std::result::Result<(), String> {
match *self {
Value::Null => {
buf.extend(b"null");
Ok(())
}
Value::Bool(true) => {
buf.extend(b"true");
Ok(())
}
Value::Bool(false) => {
buf.extend(b"false");
Ok(())
}
Value::Number(Number::I64(n)) => itoa::write(buf, n)
.map(|_| ())
.map_err(|err| format!("Write error: {}", err)),
Value::Number(Number::U64(n)) => itoa::write(buf, n)
.map(|_| ())
.map_err(|err| format!("Write error: {}", err)),
Value::String(ref s) => {
// this mess is abusing serde_json to get json escaping
let s = serde_json::Value::String(s.clone());
let s = serde_json::to_string(&s).map_err(|e| format!("{:?}", e))?;
buf.extend(s.as_bytes());
Ok(())
}
Value::Array(ref arr) => {
buf.push(b'[');
let mut first = true;
for a in arr.iter() {
if !first {
buf.push(b',');
}
a.write(buf)?;
first = false;
}
buf.push(b']');
Ok(())
}
Value::Object(ref obj) => {
buf.push(b'{');
let mut first = true;
for (k, v) in obj.iter() {
if !first {
buf.push(b',');
}
first = false;
// this mess is abusing serde_json to get json escaping
let k = serde_json::Value::String(k.clone());
let k = serde_json::to_string(&k).map_err(|e| format!("{:?}", e))?;
buf.extend(k.as_bytes());
buf.push(b':');
v.write(buf)?;
}
buf.push(b'}');
Ok(())
}
}
}
}
enum Number {
I64(i64),
U64(u64),
}
fn convert(jsn: &serde_json::Value) -> std::result::Result<Value, String> {
match *jsn {
serde_json::Value::Null => Ok(Value::Null),
serde_json::Value::Bool(b) => Ok(Value::Bool(b)),
serde_json::Value::Number(ref n) => n
.as_i64()
.map(Number::I64)
.or_else(|| n.as_u64().map(Number::U64))
.map(Value::Number)
.ok_or_else(|| String::from("only i64 and u64 are supported")),
serde_json::Value::Array(ref arr) => {
let mut out = Vec::new();
for res in arr.iter().map(convert) {
out.push(res?)
}
Ok(Value::Array(out))
}
serde_json::Value::Object(ref obj) => {
let mut out = BTreeMap::new();
for (k, v) in obj.iter() {
let _ = out.insert(k.clone(), convert(v)?);
}
Ok(Value::Object(out))
}
serde_json::Value::String(ref s) => Ok(Value::String(s.clone())),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn write_str() {
let jsn = Value::String(String::from("wat"));
let mut out = Vec::new();
jsn.write(&mut out).unwrap();
assert_eq!(&out, b"\"wat\"");
}
#[test]
fn write_arr() {
let jsn = Value::Array(vec![
Value::String(String::from("wat")),
Value::String(String::from("lol")),
Value::String(String::from("no")),
]);
let mut out = Vec::new();
jsn.write(&mut out).unwrap();
assert_eq!(&out, b"[\"wat\",\"lol\",\"no\"]");
}
#[test]
fn write_obj() {
let mut map = BTreeMap::new();
let arr = Value::Array(vec![
Value::String(String::from("haha")),
Value::String(String::from("new\nline")),
]);
let _ = map.insert(String::from("lol"), arr);
let jsn = Value::Object(map);
let mut out = Vec::new();
jsn.write(&mut out).unwrap();
assert_eq!(&out, &b"{\"lol\":[\"haha\",\"new\\nline\"]}");
}
}