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}