Skip to main content

tuf/pouf/pouf1/
mod.rs

1use serde::de::DeserializeOwned;
2use serde::ser::Serialize;
3use std::collections::BTreeMap;
4
5use crate::error::Error;
6use crate::pouf::Pouf;
7use crate::Result;
8
9pub(crate) mod shims;
10
11/// TUF POUF-1 implementation.
12///
13/// # Schema
14///
15/// ## Common Entities
16///
17/// `NATURAL_NUMBER` is an integer in the range `[1, 2**32)`.
18///
19/// `EXPIRES` is an ISO-8601 date time in format `YYYY-MM-DD'T'hh:mm:ss'Z'`.
20///
21/// `KEY_ID` is the hex encoded value of `sha256(cjson(pub_key))`.
22///
23/// `PUB_KEY` is the following:
24///
25/// ```bash
26/// {
27///   "type": KEY_TYPE,
28///   "scheme": SCHEME,
29///   "value": PUBLIC
30/// }
31/// ```
32///
33/// `PUBLIC` is a base64url encoded `SubjectPublicKeyInfo` DER public key.
34///
35/// `KEY_TYPE` is a string (`ed25519` is the only one currently supported).
36///
37/// `SCHEME` is a string (`ed25519` is the only one currently supported).
38///
39/// `HASH_VALUE` is a hex encoded hash value.
40///
41/// `SIG_VALUE` is a hex encoded signature value.
42///
43/// `METADATA_DESCRIPTION` is the following:
44///
45/// ```bash
46/// {
47///   "version": NATURAL_NUMBER,
48///   "length": NATURAL_NUMBER,
49///   "hashes": {
50///     HASH_ALGORITHM: HASH_VALUE
51///     ...
52///   }
53/// }
54/// ```
55///
56/// ## `SignedMetadata`
57///
58/// ```bash
59/// {
60///   "signatures": [SIGNATURE],
61///   "signed": SIGNED
62/// }
63/// ```
64///
65/// `SIGNATURE` is:
66///
67/// ```bash
68/// {
69///   "keyid": KEY_ID,
70///   "signature": SIG_VALUE
71/// }
72/// ```
73///
74/// `SIGNED` is one of:
75///
76/// - `RootMetadata`
77/// - `SnapshotMetadata`
78/// - `TargetsMetadata`
79/// - `TimestampMetadata`
80///
81/// The the elements of `signatures` must have unique `key_id`s.
82///
83/// ## `RootMetadata`
84///
85/// ```bash
86/// {
87///   "_type": "root",
88///   "version": NATURAL_NUMBER,
89///   "expires": EXPIRES,
90///   "keys": [PUB_KEY, ...]
91///   "roles": {
92///     "root": ROLE_DESCRIPTION,
93///     "snapshot": ROLE_DESCRIPTION,
94///     "targets": ROLE_DESCRIPTION,
95///     "timestamp": ROLE_DESCRIPTION
96///   }
97/// }
98/// ```
99///
100/// `ROLE_DESCRIPTION` is the following:
101///
102/// ```bash
103/// {
104///   "threshold": NATURAL_NUMBER,
105///   "keyids": [KEY_ID, ...]
106/// }
107/// ```
108///
109/// ## `SnapshotMetadata`
110///
111/// ```bash
112/// {
113///   "_type": "snapshot",
114///   "version": NATURAL_NUMBER,
115///   "expires": EXPIRES,
116///   "meta": {
117///     META_PATH: METADATA_DESCRIPTION
118///   }
119/// }
120/// ```
121///
122/// `META_PATH` is a string.
123///
124///
125/// ## `TargetsMetadata`
126///
127/// ```bash
128/// {
129///   "_type": "timestamp",
130///   "version": NATURAL_NUMBER,
131///   "expires": EXPIRES,
132///   "targets": {
133///     TARGET_PATH: TARGET_DESCRIPTION
134///     ...
135///   },
136///   "delegations": DELEGATIONS
137/// }
138/// ```
139///
140/// `DELEGATIONS` is optional and is described by the following:
141///
142/// ```bash
143/// {
144///   "keys": [PUB_KEY, ...]
145///   "roles": {
146///     ROLE: DELEGATION,
147///     ...
148///   }
149/// }
150/// ```
151///
152/// `DELEGATION` is:
153///
154/// ```bash
155/// {
156///   "name": ROLE,
157///   "threshold": NATURAL_NUMBER,
158///   "terminating": BOOLEAN,
159///   "keyids": [KEY_ID, ...],
160///   "paths": [PATH, ...]
161/// }
162/// ```
163///
164/// `ROLE` is a string,
165///
166/// `PATH` is a string.
167///
168/// ## `TimestampMetadata`
169///
170/// ```bash
171/// {
172///   "_type": "timestamp",
173///   "version": NATURAL_NUMBER,
174///   "expires": EXPIRES,
175///   "snapshot": METADATA_DESCRIPTION
176/// }
177/// ```
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct Pouf1;
180
181impl Pouf for Pouf1 {
182    type RawData = serde_json::Value;
183
184    /// ```
185    /// # use tuf::pouf::{Pouf, Pouf1};
186    /// assert_eq!(Pouf1::extension(), "json");
187    /// ```
188    fn extension() -> &'static str {
189        "json"
190    }
191
192    /// ```
193    /// # use tuf::pouf::{Pouf, Pouf1};
194    /// # use std::collections::HashMap;
195    /// let jsn: &[u8] = br#"{"foo": "bar", "baz": "quux"}"#;
196    /// let raw = Pouf1::from_slice(jsn).unwrap();
197    /// let out = Pouf1::canonicalize(&raw).unwrap();
198    /// assert_eq!(out, br#"{"baz":"quux","foo":"bar"}"#);
199    /// ```
200    fn canonicalize(raw_data: &Self::RawData) -> Result<Vec<u8>> {
201        canonicalize(raw_data).map_err(Error::Opaque)
202    }
203
204    /// ```
205    /// # use serde::Deserialize;
206    /// # use serde_json::json;
207    /// # use std::collections::HashMap;
208    /// # use tuf::pouf::{Pouf, Pouf1};
209    /// #
210    /// #[derive(Deserialize, Debug, PartialEq)]
211    /// struct Thing {
212    ///    foo: String,
213    ///    bar: String,
214    /// }
215    ///
216    /// let jsn = json!({"foo": "wat", "bar": "lol"});
217    /// let thing = Thing { foo: "wat".into(), bar: "lol".into() };
218    /// let de: Thing = Pouf1::deserialize(&jsn).unwrap();
219    /// assert_eq!(de, thing);
220    /// ```
221    fn deserialize<T>(raw_data: &Self::RawData) -> Result<T>
222    where
223        T: DeserializeOwned,
224    {
225        Ok(serde_json::from_value(raw_data.clone())?)
226    }
227
228    /// ```
229    /// # use serde::Serialize;
230    /// # use serde_json::json;
231    /// # use std::collections::HashMap;
232    /// # use tuf::pouf::{Pouf, Pouf1};
233    /// #
234    /// #[derive(Serialize)]
235    /// struct Thing {
236    ///    foo: String,
237    ///    bar: String,
238    /// }
239    ///
240    /// let jsn = json!({"foo": "wat", "bar": "lol"});
241    /// let thing = Thing { foo: "wat".into(), bar: "lol".into() };
242    /// let se: serde_json::Value = Pouf1::serialize(&thing).unwrap();
243    /// assert_eq!(se, jsn);
244    /// ```
245    fn serialize<T>(data: &T) -> Result<Self::RawData>
246    where
247        T: Serialize,
248    {
249        Ok(serde_json::to_value(data)?)
250    }
251
252    /// ```
253    /// # use tuf::pouf::{Pouf, Pouf1};
254    /// # use std::collections::HashMap;
255    /// let jsn: &[u8] = br#"{"foo": "bar", "baz": "quux"}"#;
256    /// let _: HashMap<String, String> = Pouf1::from_slice(&jsn).unwrap();
257    /// ```
258    fn from_slice<T>(slice: &[u8]) -> Result<T>
259    where
260        T: DeserializeOwned,
261    {
262        Ok(serde_json::from_slice(slice)?)
263    }
264}
265
266fn canonicalize(jsn: &serde_json::Value) -> std::result::Result<Vec<u8>, String> {
267    let converted = convert(jsn)?;
268    let mut buf = Vec::new();
269    let _ = converted.write(&mut buf); // Vec<u8> impl always succeeds (or panics).
270    Ok(buf)
271}
272
273enum Value {
274    Array(Vec<Value>),
275    Bool(bool),
276    Null,
277    Number(Number),
278    Object(BTreeMap<String, Value>),
279    String(String),
280}
281
282impl Value {
283    fn write(&self, buf: &mut Vec<u8>) -> std::result::Result<(), String> {
284        match *self {
285            Value::Null => {
286                buf.extend(b"null");
287            }
288            Value::Bool(true) => {
289                buf.extend(b"true");
290            }
291            Value::Bool(false) => {
292                buf.extend(b"false");
293            }
294            Value::Number(Number::I64(n)) => {
295                buf.extend(itoa::Buffer::new().format(n).bytes());
296            }
297            Value::Number(Number::U64(n)) => {
298                buf.extend(itoa::Buffer::new().format(n).bytes());
299            }
300            Value::String(ref s) => {
301                // this mess is abusing serde_json to get json escaping
302                let s = serde_json::Value::String(s.clone());
303                let s = serde_json::to_string(&s).map_err(|e| format!("{:?}", e))?;
304                buf.extend(s.as_bytes());
305            }
306            Value::Array(ref arr) => {
307                buf.push(b'[');
308                let mut first = true;
309                for a in arr.iter() {
310                    if !first {
311                        buf.push(b',');
312                    }
313                    a.write(buf)?;
314                    first = false;
315                }
316                buf.push(b']');
317            }
318            Value::Object(ref obj) => {
319                buf.push(b'{');
320                let mut first = true;
321                for (k, v) in obj.iter() {
322                    if !first {
323                        buf.push(b',');
324                    }
325                    first = false;
326
327                    // this mess is abusing serde_json to get json escaping
328                    let k = serde_json::Value::String(k.clone());
329                    let k = serde_json::to_string(&k).map_err(|e| format!("{:?}", e))?;
330                    buf.extend(k.as_bytes());
331
332                    buf.push(b':');
333                    v.write(buf)?;
334                }
335                buf.push(b'}');
336            }
337        }
338        Ok(())
339    }
340}
341
342enum Number {
343    I64(i64),
344    U64(u64),
345}
346
347fn convert(jsn: &serde_json::Value) -> std::result::Result<Value, String> {
348    match *jsn {
349        serde_json::Value::Null => Ok(Value::Null),
350        serde_json::Value::Bool(b) => Ok(Value::Bool(b)),
351        serde_json::Value::Number(ref n) => n
352            .as_i64()
353            .map(Number::I64)
354            .or_else(|| n.as_u64().map(Number::U64))
355            .map(Value::Number)
356            .ok_or_else(|| String::from("only i64 and u64 are supported")),
357        serde_json::Value::Array(ref arr) => {
358            let mut out = Vec::new();
359            for res in arr.iter().map(convert) {
360                out.push(res?)
361            }
362            Ok(Value::Array(out))
363        }
364        serde_json::Value::Object(ref obj) => {
365            let mut out = BTreeMap::new();
366            for (k, v) in obj.iter() {
367                let _ = out.insert(k.clone(), convert(v)?);
368            }
369            Ok(Value::Object(out))
370        }
371        serde_json::Value::String(ref s) => Ok(Value::String(s.clone())),
372    }
373}
374
375#[cfg(test)]
376mod test {
377    use super::*;
378
379    #[test]
380    fn write_str() {
381        let jsn = Value::String(String::from("wat"));
382        let mut out = Vec::new();
383        jsn.write(&mut out).unwrap();
384        assert_eq!(&out, b"\"wat\"");
385    }
386
387    #[test]
388    fn write_arr() {
389        let jsn = Value::Array(vec![
390            Value::String(String::from("wat")),
391            Value::String(String::from("lol")),
392            Value::String(String::from("no")),
393        ]);
394        let mut out = Vec::new();
395        jsn.write(&mut out).unwrap();
396        assert_eq!(&out, b"[\"wat\",\"lol\",\"no\"]");
397    }
398
399    #[test]
400    fn write_obj() {
401        let mut map = BTreeMap::new();
402        let arr = Value::Array(vec![
403            Value::String(String::from("haha")),
404            Value::String(String::from("new\nline")),
405        ]);
406        let _ = map.insert(String::from("lol"), arr);
407        let jsn = Value::Object(map);
408        let mut out = Vec::new();
409        jsn.write(&mut out).unwrap();
410        assert_eq!(&out, &b"{\"lol\":[\"haha\",\"new\\nline\"]}");
411    }
412}