Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_sbj/src/envelope.rs

24.9 KiB, 3 runs

created by r1870400018:22212, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The SBJ header and envelope. See `SPEC.md` §1.1 to §1.3.
2//!
3//! The header is eight fixed bytes that say what the file is and how far the envelope reaches. The
4//! envelope is a BDAT-encoded map naming the payload schema, the author, the schemes used, the time,
5//! the hash of the tree region and the signature over that hash. Nothing here touches content: a
6//! caller may read the header, decode the envelope, check the hash and check the signature, and stop.
7
8use crate::{
9 HEADER_LEN,
10 MAGIC,
11 VERSION_MAJOR,
12 limit,
13};
14
15use oxedyne_fe2o3_core::prelude::*;
16use oxedyne_fe2o3_jdat::{
17 prelude::*,
18 bdat::DecodeLimits,
19};
20
21/// The Namex id of the Ed25519 signature scheme, as declared by `SignatureScheme` in `fe2o3_crypto`
22/// (base64 `9UQvATp4Zbv8IbWOivdhiQnex+ELo7sxOr8ntEZphMc=`).
23pub const NAMEX_ED25519: [u8; 32] = [
24 0xF5, 0x44, 0x2F, 0x01, 0x3A, 0x78, 0x65, 0xBB,
25 0xFC, 0x21, 0xB5, 0x8E, 0x8A, 0xF7, 0x61, 0x89,
26 0x09, 0xDE, 0xC7, 0xE1, 0x0B, 0xA3, 0xBB, 0x31,
27 0x3A, 0xBF, 0x27, 0xB4, 0x46, 0x69, 0x84, 0xC7,
28];
29
30/// The Namex id of the SHA3-256 hash scheme, as declared by `HashScheme` in `fe2o3_hash`
31/// (base64 `VybbHNWeNXeTqTrXj66TzZScbSTsEFVy0W79QnbroFA=`).
32pub const NAMEX_SHA3_256: [u8; 32] = [
33 0x57, 0x26, 0xDB, 0x1C, 0xD5, 0x9E, 0x35, 0x77,
34 0x93, 0xA9, 0x3A, 0xD7, 0x8F, 0xAE, 0x93, 0xCD,
35 0x94, 0x9C, 0x6D, 0x24, 0xEC, 0x10, 0x55, 0x72,
36 0xD1, 0x6E, 0xFD, 0x42, 0x76, 0xEB, 0xA0, 0x50,
37];
38
39/// Derives a v0 scheme id from a Namex id: the leading four bytes, big-endian.
40///
41/// A Namex id is 32 bytes and the envelope carries a `u32`, so the id on the wire is a prefix of the
42/// global name rather than a new numbering of our own. The prefix is stable, since a Namex id never
43/// changes, and it is the same value on every machine that reads the byte string the same way.
44pub const fn scheme_id(namex: [u8; 32]) -> u32 {
45 u32::from_be_bytes([namex[0], namex[1], namex[2], namex[3]])
46}
47
48/// The v0 signature scheme id, Ed25519. See `scheme_id`.
49pub const SIG_SCHEME_ED25519: u32 = scheme_id(NAMEX_ED25519);
50
51/// The width of an Ed25519 signature, which is what a v0 envelope's `sig` carries.
52///
53/// Here rather than taken from the signing crate, which publishes its key widths and not this one.
54/// The envelope names the scheme, so the envelope knows the width that scheme writes, and a `sig`
55/// of any other width is refused with a message about this format before the signing crate is asked
56/// to make sense of it.
57pub const SIG_LEN_ED25519: usize = 64;
58
59/// The v0 hash scheme id, SHA3-256. See `scheme_id`.
60pub const HASH_SCHEME_SHA3_256: u32 = scheme_id(NAMEX_SHA3_256);
61
62/// Envelope key naming the payload schema.
63pub const KEY_SCHEMA: &'static str = "schema";
64/// Envelope key carrying the author's public key.
65pub const KEY_AUTHOR: &'static str = "author";
66/// Envelope key carrying the signature scheme id.
67pub const KEY_SIG_SCHEME: &'static str = "sig_scheme";
68/// Envelope key carrying the hash scheme id.
69pub const KEY_HASH_SCHEME: &'static str = "hash_scheme";
70/// Envelope key carrying the authoring time, in Unix milliseconds.
71pub const KEY_TIME: &'static str = "time";
72/// Envelope key carrying the hash of the tree region.
73pub const KEY_HASH: &'static str = "hash";
74/// Envelope key carrying the signature over the signing input.
75pub const KEY_SIG: &'static str = "sig";
76/// Envelope key carrying the length of the tree region, in bytes.
77pub const KEY_TREE_LEN: &'static str = "tree_len";
78
79/// The daticle nesting depth an envelope reaches: the map itself, then the scalar under each key.
80///
81/// The envelope is the first thing a reader decodes, and every byte of it came from somewhere else,
82/// so it is decoded under a limit rather than on trust. Nothing in it nests, so the limit is two.
83pub const ENVELOPE_DAT_DEPTH: usize = 2;
84
85/// Every key the envelope map must carry, and no others.
86pub const KEYS: [&'static str; 8] = [
87 KEY_SCHEMA,
88 KEY_AUTHOR,
89 KEY_SIG_SCHEME,
90 KEY_HASH_SCHEME,
91 KEY_TIME,
92 KEY_HASH,
93 KEY_SIG,
94 KEY_TREE_LEN,
95];
96
97/// The fixed 8-byte header: magic, major version, envelope length.
98#[derive(Clone, Copy, Debug)]
99pub struct Header {
100 /// Format major version.
101 pub major: u16,
102 /// Length of the envelope region, in bytes.
103 pub env_len: u16,
104}
105
106/// Reads and checks the fixed header.
107pub fn read_header(buf: &[u8]) -> Outcome<Header> {
108 if buf.len() < HEADER_LEN {
109 return Err(err!(
110 "An SBJ header is {} bytes, but only {} {} available.",
111 HEADER_LEN, buf.len(), if buf.len() == 1 { "is" } else { "are" };
112 Invalid, Input, Decode));
113 }
114 if buf[0..4] != MAGIC {
115 return Err(err!(
116 "Not an SBJ file: expected the magic {}, found {}.",
117 fmt_magic(&MAGIC), fmt_magic(&buf[0..4]);
118 Invalid, Input, Decode));
119 }
120 let major = u16::from_be_bytes([buf[4], buf[5]]);
121 if major != VERSION_MAJOR {
122 return Err(err!(
123 "SBJ format major version {} is not implemented here, which reads version {}.",
124 major, VERSION_MAJOR;
125 Invalid, Input, Unimplemented));
126 }
127 let env_len = u16::from_be_bytes([buf[6], buf[7]]);
128 if env_len == 0 {
129 return Err(err!(
130 "The header declares an envelope of zero bytes, which cannot hold an envelope map.";
131 Invalid, Input, Decode));
132 }
133 if (env_len as usize) > limit::ENVELOPE_BYTES {
134 return Err(err!(
135 "The header declares an envelope of {} bytes, exceeding the limit of {} bytes.",
136 env_len, limit::ENVELOPE_BYTES;
137 Invalid, Input, LimitReached));
138 }
139 Ok(Header {
140 major,
141 env_len,
142 })
143}
144
145/// Writes the fixed header for an envelope of the given length.
146pub fn write_header(env_len: usize) -> Outcome<Vec<u8>> {
147 if env_len == 0 {
148 return Err(err!(
149 "An envelope of zero bytes cannot hold an envelope map.";
150 Invalid, Input, Encode));
151 }
152 if env_len > limit::ENVELOPE_BYTES {
153 return Err(err!(
154 "The envelope is {} bytes, exceeding the limit of {} bytes.",
155 env_len, limit::ENVELOPE_BYTES;
156 Invalid, Input, LimitReached));
157 }
158 let env_len = env_len as u16; // Safe: the limit is well below `u16::MAX`.
159 let mut buf = Vec::with_capacity(HEADER_LEN);
160 buf.extend_from_slice(&MAGIC);
161 buf.extend_from_slice(&VERSION_MAJOR.to_be_bytes());
162 buf.extend_from_slice(&env_len.to_be_bytes());
163 Ok(buf)
164}
165
166/// Renders four magic bytes as hexadecimal, for an error message.
167fn fmt_magic(byts: &[u8]) -> String {
168 let mut s = String::new();
169 for (i, b) in byts.iter().enumerate() {
170 if i > 0 {
171 s.push(' ');
172 }
173 s.push_str(&fmt!("{:02X}", b));
174 }
175 s
176}
177
178/// The signed envelope. Every field is required.
179#[derive(Clone, Debug, PartialEq, Eq)]
180pub struct Envelope {
181 /// Schema of the payload, e.g. `oxeweb/doc/0`.
182 pub schema: String,
183 /// The author's public key.
184 pub author: Vec<u8>,
185 /// Namex id of the signature scheme.
186 pub sig_scheme: u32,
187 /// Namex id of the hash scheme.
188 pub hash_scheme: u32,
189 /// Unix milliseconds.
190 pub time: u64,
191 /// Hash of the tree region.
192 pub hash: Vec<u8>,
193 /// Signature over the signing input.
194 pub sig: Vec<u8>,
195 /// Length of the tree region, in bytes.
196 pub tree_len: u64,
197}
198
199impl Envelope {
200
201 /// Encodes the envelope as a canonical daticle map.
202 pub fn to_dat(&self) -> Outcome<Dat> {
203 let mut map = DaticleMap::new();
204 map.insert(dat!(KEY_SCHEMA), Dat::Str(self.schema.clone()));
205 map.insert(dat!(KEY_AUTHOR), Dat::BU8(self.author.clone()));
206 map.insert(dat!(KEY_SIG_SCHEME), Dat::U32(self.sig_scheme));
207 map.insert(dat!(KEY_HASH_SCHEME), Dat::U32(self.hash_scheme));
208 map.insert(dat!(KEY_TIME), Dat::U64(self.time));
209 map.insert(dat!(KEY_HASH), Dat::BU8(self.hash.clone()));
210 map.insert(dat!(KEY_SIG), Dat::BU8(self.sig.clone()));
211 map.insert(dat!(KEY_TREE_LEN), Dat::C64(self.tree_len));
212 Ok(Dat::Map(map))
213 }
214
215 /// Decodes an envelope from a daticle map, checking every required key.
216 pub fn from_dat(d: &Dat) -> Outcome<Self> {
217 let map = match d {
218 Dat::Map(map) => map,
219 _ => return Err(err!(
220 "The envelope must be a {:?}, found a {:?}.", Kind::Map, d.kind();
221 Invalid, Input, Mismatch)),
222 };
223 for key in map.keys() {
224 let name = match key {
225 Dat::Str(s) => s.clone(),
226 _ => return Err(err!(
227 "Envelope map keys must be of kind {:?}, found a {:?}.",
228 Kind::Str, key.kind();
229 Invalid, Input, Mismatch)),
230 };
231 if !KEYS.contains(&name.as_str()) {
232 return Err(err!(
233 "The envelope carries the unknown key \"{}\". The v0 envelope carries \
234 exactly these keys: {:?}.", name, KEYS;
235 Invalid, Input, Excessive));
236 }
237 }
238 Ok(Self {
239 schema: res!(get_str(map, KEY_SCHEMA)),
240 author: res!(get_bu8(map, KEY_AUTHOR)),
241 sig_scheme: res!(get_u32(map, KEY_SIG_SCHEME)),
242 hash_scheme: res!(get_u32(map, KEY_HASH_SCHEME)),
243 time: res!(get_u64(map, KEY_TIME)),
244 hash: res!(get_bu8(map, KEY_HASH)),
245 sig: res!(get_bu8(map, KEY_SIG)),
246 tree_len: res!(get_c64(map, KEY_TREE_LEN)),
247 })
248 }
249
250 /// Encodes the envelope to canonical BDAT bytes.
251 pub fn encode(&self) -> Outcome<Vec<u8>> {
252 let d = res!(self.to_dat());
253 let buf = res!(d.to_bytes(Vec::new()));
254 if buf.len() > limit::ENVELOPE_BYTES {
255 return Err(err!(
256 "The encoded envelope is {} bytes, exceeding the limit of {} bytes.",
257 buf.len(), limit::ENVELOPE_BYTES;
258 Invalid, Input, LimitReached));
259 }
260 Ok(buf)
261 }
262
263 /// Decodes an envelope from BDAT bytes, which must be consumed exactly.
264 ///
265 /// The bytes are untrusted, so they are decoded under a depth limit: a header claiming a 4 KiB
266 /// envelope should not be believed for free, and neither should the bytes it points at, which
267 /// could otherwise describe a value nested deeply enough to exhaust the stack of a recursive
268 /// decoder before a single key had been looked at.
269 pub fn decode(buf: &[u8]) -> Outcome<Self> {
270 if buf.len() > limit::ENVELOPE_BYTES {
271 return Err(err!(
272 "The envelope region is {} bytes, exceeding the limit of {} bytes.",
273 buf.len(), limit::ENVELOPE_BYTES;
274 Invalid, Input, LimitReached));
275 }
276 let lims = DecodeLimits::new(ENVELOPE_DAT_DEPTH, limit::ENVELOPE_BYTES);
277 let (d, n) = res!(Dat::from_bytes_limited(buf, &lims));
278 if n != buf.len() {
279 return Err(err!(
280 "The envelope map occupies {} of the {} bytes of the envelope region, \
281 leaving {} trailing bytes.", n, buf.len(), buf.len() - n;
282 Invalid, Input, Decode));
283 }
284 // SPEC.md §1.2: the envelope obeys the §3 canonical rules, like everything the hash reaches.
285 // The envelope is not itself hashed, but it is what the hash and signature are read from, so
286 // a second encoding of the same fields must not decode to the same envelope. Re-encoding and
287 // comparing byte-for-byte, as the tree path does, refuses a duplicate key that a decoding map
288 // would silently collapse, a non-minimal length, an ordmap, or any other non-canonical form.
289 let re = res!(d.to_bytes(Vec::new()));
290 if re != buf {
291 return Err(err!(
292 "The envelope is not in canonical form: it re-encodes to {} bytes against the {} \
293 bytes supplied, so it carries a duplicate key, a non-minimal encoding, or a \
294 non-canonical map. See SPEC.md §1.2 and §3.", re.len(), buf.len();
295 Invalid, Input, Decode));
296 }
297 Self::from_dat(&d)
298 }
299
300 /// The bytes a signature covers. See `SPEC.md` §1.3.
301 ///
302 /// The schema and the scheme ids are included so that a signed payload cannot be re-labelled as
303 /// a different schema, nor claimed to have been addressed by a weaker hash function.
304 ///
305 /// The schema is preceded by its length, because it is variable-length and it is not the last
306 /// field. Without that, `schema` and `hash` are two variable-length fields separated only by
307 /// fixed-width ones, and a byte at the boundary can be read as belonging to either: two
308 /// envelopes sharing no field value can share a preimage, and so a signature. `hash` needs no
309 /// prefix, being last, since its extent is whatever remains.
310 pub fn signing_input(&self) -> Vec<u8> {
311 let schema = self.schema.as_bytes();
312 let mut buf = Vec::with_capacity(
313 4 + schema.len() + 4 + 4 + 8 + self.hash.len()
314 );
315 // Saturating rather than wrapping: a schema longer than u32 cannot occur, since the whole
316 // envelope is capped at 4 KiB by `limit::ENVELOPE_BYTES`, but a length that silently wrapped
317 // would reintroduce the ambiguity this prefix exists to remove.
318 buf.extend_from_slice(&(schema.len() as u64).min(u32::MAX as u64).to_be_bytes()[4..]);
319 buf.extend_from_slice(schema);
320 buf.extend_from_slice(&self.sig_scheme.to_be_bytes());
321 buf.extend_from_slice(&self.hash_scheme.to_be_bytes());
322 buf.extend_from_slice(&self.time.to_be_bytes());
323 buf.extend_from_slice(&self.hash);
324 buf
325 }
326}
327
328/// Returns the value for a required envelope key, or an error naming the missing key.
329fn get<'a>(map: &'a DaticleMap, key: &str) -> Outcome<&'a Dat> {
330 match map.get(&dat!(key)) {
331 Some(d) => Ok(d),
332 None => Err(err!(
333 "The envelope is missing the required key \"{}\".", key;
334 Invalid, Input, Missing)),
335 }
336}
337
338/// The error raised when a key carries the wrong kind of daticle.
339fn wrong_kind(key: &str, expected: Kind, found: Kind) -> Error<ErrTag> {
340 err!(
341 "The envelope key \"{}\" must carry a daticle of kind {:?}, found a {:?}.",
342 key, expected, found;
343 Invalid, Input, Mismatch)
344}
345
346/// Reads a required `str` key.
347fn get_str(map: &DaticleMap, key: &str) -> Outcome<String> {
348 match res!(get(map, key)) {
349 Dat::Str(s) => Ok(s.clone()),
350 d => Err(wrong_kind(key, Kind::Str, d.kind())),
351 }
352}
353
354/// Reads a required `bu8` key.
355fn get_bu8(map: &DaticleMap, key: &str) -> Outcome<Vec<u8>> {
356 match res!(get(map, key)) {
357 Dat::BU8(v) => Ok(v.clone()),
358 d => Err(wrong_kind(key, Kind::BU8, d.kind())),
359 }
360}
361
362/// Reads a required `u32` key.
363fn get_u32(map: &DaticleMap, key: &str) -> Outcome<u32> {
364 match res!(get(map, key)) {
365 Dat::U32(n) => Ok(*n),
366 d => Err(wrong_kind(key, Kind::U32, d.kind())),
367 }
368}
369
370/// Reads a required `u64` key.
371fn get_u64(map: &DaticleMap, key: &str) -> Outcome<u64> {
372 match res!(get(map, key)) {
373 Dat::U64(n) => Ok(*n),
374 d => Err(wrong_kind(key, Kind::U64, d.kind())),
375 }
376}
377
378/// Reads a required `c64` key.
379fn get_c64(map: &DaticleMap, key: &str) -> Outcome<u64> {
380 match res!(get(map, key)) {
381 Dat::C64(n) => Ok(*n),
382 d => Err(wrong_kind(key, Kind::C64, d.kind())),
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use crate::SCHEMA_DOC;
390
391 /// A plausible envelope, with fixed contents.
392 fn sample() -> Envelope {
393 Envelope {
394 schema: SCHEMA_DOC.to_string(),
395 author: vec![0xAA; 32],
396 sig_scheme: SIG_SCHEME_ED25519,
397 hash_scheme: HASH_SCHEME_SHA3_256,
398 time: 1_752_000_000_000,
399 hash: vec![0xBB; 32],
400 sig: vec![0xCC; 64],
401 tree_len: 4096,
402 }
403 }
404
405 #[test]
406 fn test_scheme_ids() -> Outcome<()> {
407 // The ids are the leading four bytes of the Namex ids that `fe2o3_crypto` and `fe2o3_hash`
408 // declare, read big-endian.
409 assert_eq!(SIG_SCHEME_ED25519, 0xF544_2F01);
410 assert_eq!(HASH_SCHEME_SHA3_256, 0x5726_DB1C);
411 assert_eq!(scheme_id(NAMEX_ED25519), SIG_SCHEME_ED25519);
412 assert_eq!(scheme_id(NAMEX_SHA3_256), HASH_SCHEME_SHA3_256);
413 Ok(())
414 }
415
416 #[test]
417 fn test_header_round_trip() -> Outcome<()> {
418 let buf = res!(write_header(1234));
419 assert_eq!(buf.len(), HEADER_LEN);
420 assert_eq!(&buf[0..4], &MAGIC[..]);
421 let hdr = res!(read_header(&buf));
422 assert_eq!(hdr.major, VERSION_MAJOR);
423 assert_eq!(hdr.env_len, 1234);
424 // Trailing bytes are ignored: the header is the first eight.
425 let mut long = buf.clone();
426 long.extend_from_slice(&[0x00; 16]);
427 let hdr = res!(read_header(&long));
428 assert_eq!(hdr.env_len, 1234);
429 Ok(())
430 }
431
432 #[test]
433 fn test_header_bad_magic() -> Outcome<()> {
434 let mut buf = res!(write_header(64));
435 buf[1] = b'X';
436 assert!(read_header(&buf).is_err());
437 Ok(())
438 }
439
440 #[test]
441 fn test_header_bad_version() -> Outcome<()> {
442 let mut buf = res!(write_header(64));
443 buf[5] = 1; // Major version 1.
444 match read_header(&buf) {
445 Ok(hdr) => return Err(err!(
446 "Expected a rejection of major version 1, decoded {:?}.", hdr;
447 Test, Invalid)),
448 Err(e) => {
449 let msg = fmt!("{}", e);
450 assert!(msg.contains("1"), "Error should name the version found: {}", msg);
451 },
452 }
453 Ok(())
454 }
455
456 #[test]
457 fn test_header_too_short() -> Outcome<()> {
458 let buf = res!(write_header(64));
459 assert!(read_header(&buf[0..7]).is_err());
460 Ok(())
461 }
462
463 #[test]
464 fn test_header_envelope_limit() -> Outcome<()> {
465 // The writer refuses an over-large envelope.
466 assert!(write_header(limit::ENVELOPE_BYTES + 1).is_err());
467 assert!(write_header(0).is_err());
468 // The reader refuses a header claiming one, without believing it.
469 let mut buf = res!(write_header(limit::ENVELOPE_BYTES));
470 assert!(read_header(&buf).is_ok());
471 let over = (limit::ENVELOPE_BYTES + 1) as u16;
472 buf[6] = (over >> 8) as u8;
473 buf[7] = (over & 0xFF) as u8;
474 assert!(read_header(&buf).is_err());
475 Ok(())
476 }
477
478 #[test]
479 fn test_envelope_round_trip() -> Outcome<()> {
480 let env = sample();
481 let buf = res!(env.encode());
482 let dec = res!(Envelope::decode(&buf));
483 assert_eq!(dec, env);
484 // And through the daticle alone.
485 let d = res!(env.to_dat());
486 assert_eq!(res!(Envelope::from_dat(&d)), env);
487 // The encoding is deterministic.
488 assert_eq!(res!(dec.encode()), buf);
489 Ok(())
490 }
491
492 #[test]
493 fn test_envelope_missing_key() -> Outcome<()> {
494 for key in KEYS {
495 let mut map = match res!(sample().to_dat()) {
496 Dat::Map(map) => map,
497 d => return Err(err!(
498 "Expected a map, found a {:?}.", d.kind();
499 Test, Invalid)),
500 };
501 map.remove(&dat!(key));
502 match Envelope::from_dat(&Dat::Map(map)) {
503 Ok(_) => return Err(err!(
504 "Expected a rejection of an envelope missing the key \"{}\".", key;
505 Test, Invalid)),
506 Err(e) => {
507 let msg = fmt!("{}", e);
508 assert!(msg.contains(key), "Error should name the key \"{}\": {}", key, msg);
509 },
510 }
511 }
512 Ok(())
513 }
514
515 #[test]
516 fn test_envelope_wrong_typed_key() -> Outcome<()> {
517 // A `time` promoted to `u128`, and a `tree_len` written as a `u64` rather than a `c64`, are
518 // both rejections: the schema fixes the width.
519 let cases: [(&str, Dat); 4] = [
520 (KEY_TIME, Dat::U128(1)),
521 (KEY_TREE_LEN, Dat::U64(4096)),
522 (KEY_SCHEMA, Dat::BU8(vec![1, 2, 3])),
523 (KEY_SIG_SCHEME, Dat::U16(1)),
524 ];
525 for (key, val) in cases {
526 let mut map = match res!(sample().to_dat()) {
527 Dat::Map(map) => map,
528 d => return Err(err!(
529 "Expected a map, found a {:?}.", d.kind();
530 Test, Invalid)),
531 };
532 map.insert(dat!(key), val.clone());
533 match Envelope::from_dat(&Dat::Map(map)) {
534 Ok(_) => return Err(err!(
535 "Expected a rejection of the key \"{}\" carrying a {:?}.", key, val.kind();
536 Test, Invalid)),
537 Err(e) => {
538 let msg = fmt!("{}", e);
539 assert!(msg.contains(key), "Error should name the key \"{}\": {}", key, msg);
540 },
541 }
542 }
543 Ok(())
544 }
545
546 #[test]
547 fn test_envelope_unknown_key() -> Outcome<()> {
548 let mut map = match res!(sample().to_dat()) {
549 Dat::Map(map) => map,
550 d => return Err(err!(
551 "Expected a map, found a {:?}.", d.kind();
552 Test, Invalid)),
553 };
554 map.insert(dat!("extra"), dat!(1u8));
555 match Envelope::from_dat(&Dat::Map(map)) {
556 Ok(_) => Err(err!(
557 "Expected a rejection of an envelope carrying an unknown key.";
558 Test, Invalid)),
559 Err(e) => {
560 let msg = fmt!("{}", e);
561 assert!(msg.contains("extra"), "Error should name the key: {}", msg);
562 Ok(())
563 },
564 }
565 }
566
567 #[test]
568 fn test_envelope_not_a_map() -> Outcome<()> {
569 assert!(Envelope::from_dat(&dat!("oxeweb/doc/0")).is_err());
570 assert!(Envelope::from_dat(&Dat::List(Vec::new())).is_err());
571 Ok(())
572 }
573
574 #[test]
575 fn test_envelope_trailing_bytes() -> Outcome<()> {
576 let mut buf = res!(sample().encode());
577 buf.push(0x00);
578 assert!(Envelope::decode(&buf).is_err());
579 Ok(())
580 }
581
582 #[test]
583 fn test_envelope_nesting_is_bounded() -> Outcome<()> {
584 // An envelope region is the first untrusted thing a reader decodes, and a few hundred bytes
585 // of it can describe a value nested hundreds deep. The depth limit refuses it as it
586 // descends, rather than after it has descended.
587 let mut buf = Vec::new();
588 for _ in 0..512 {
589 let mut lvl = vec![Dat::LIST_CODE];
590 lvl = res!(Dat::C64(buf.len() as u64).to_bytes(lvl));
591 lvl.extend_from_slice(&buf);
592 buf = lvl;
593 }
594 assert!(buf.len() <= limit::ENVELOPE_BYTES, "The nest outgrew the envelope limit.");
595 match Envelope::decode(&buf) {
596 Ok(_) => Err(err!(
597 "A deeply nested envelope region was accepted.";
598 Test, Invalid)),
599 Err(e) => {
600 let msg = fmt!("{}", e);
601 assert!(msg.contains("nesting depth"),
602 "The rejection should name the depth limit, but says: {}", msg);
603 Ok(())
604 },
605 }
606 }
607
608 #[test]
609 fn test_envelope_non_canonical_duplicate_key() -> Outcome<()> {
610 // A decoding BTreeMap silently collapses a duplicate key, so hand-built envelope bytes
611 // carrying "time" twice would decode to a valid-looking envelope. SPEC.md §1.2 forbids it,
612 // and the re-encode-and-compare gate in decode catches it: the bytes decode to eight
613 // entries, which re-encode to fewer bytes than were supplied.
614 let env = sample();
615 let map = match res!(env.to_dat()) {
616 Dat::Map(map) => map,
617 d => return Err(err!("Expected a map, found a {:?}.", d.kind(); Test, Invalid)),
618 };
619 let mut inner = Vec::new();
620 for (k, v) in &map {
621 inner = res!(k.to_bytes(inner));
622 inner = res!(v.to_bytes(inner));
623 }
624 // Append the "time" pair a second time.
625 let dup_key = dat!(KEY_TIME);
626 let dup_val = match map.get(&dup_key) {
627 Some(v) => v.clone(),
628 None => return Err(err!("The sample envelope carries a time key."; Test, Bug)),
629 };
630 inner = res!(dup_key.to_bytes(inner));
631 inner = res!(dup_val.to_bytes(inner));
632 let mut bytes = vec![Dat::MAP_CODE];
633 bytes = res!(Dat::C64(inner.len() as u64).to_bytes(bytes));
634 bytes.extend_from_slice(&inner);
635 match Envelope::decode(&bytes) {
636 Ok(_) => Err(err!(
637 "A non-canonical envelope with a duplicate key was accepted."; Test, Invalid)),
638 Err(_) => Ok(()),
639 }
640 }
641
642 #[test]
643 fn test_signing_input() -> Outcome<()> {
644 let env = sample();
645 let input = env.signing_input();
646 let schema = SCHEMA_DOC.as_bytes();
647 assert_eq!(input.len(), 4 + schema.len() + 4 + 4 + 8 + 32);
648 let mut i = 0;
649 assert_eq!(&input[i..i + 4], &(schema.len() as u32).to_be_bytes()[..]);
650 i += 4;
651 assert_eq!(&input[i..i + schema.len()], schema);
652 i += schema.len();
653 assert_eq!(&input[i..i + 4], &SIG_SCHEME_ED25519.to_be_bytes()[..]);
654 i += 4;
655 assert_eq!(&input[i..i + 4], &HASH_SCHEME_SHA3_256.to_be_bytes()[..]);
656 i += 4;
657 assert_eq!(&input[i..i + 8], &env.time.to_be_bytes()[..]);
658 i += 8;
659 assert_eq!(&input[i..], &env.hash[..]);
660 // Re-labelling the schema, or the scheme, changes what was signed.
661 let mut other = env.clone();
662 other.schema = "oxeweb/cmd/0".to_string();
663 assert_ne!(other.signing_input(), input);
664 let mut other = env.clone();
665 other.hash_scheme = SIG_SCHEME_ED25519;
666 assert_ne!(other.signing_input(), input);
667 // A schema of a DIFFERENT length, which the two checks above cannot reach: both re-label
668 // `oxeweb/doc/0` to a string of the same width, so neither would notice a preimage that
669 // could be split two ways. See `test_the_preimage_cannot_be_split_two_ways`.
670 let mut other = env.clone();
671 other.schema = fmt!("oxeweb/administrative-command/0");
672 assert_ne!(other.signing_input(), input);
673 Ok(())
674 }
675
676 /// Two envelopes agreeing on no field must not share a signing input.
677 ///
678 /// Written from the collision the unprefixed preimage admitted. With `schema` variable-length
679 /// and not the last field, the byte at its boundary can be read as the end of the schema or as
680 /// the first byte of the fixed-width run after it, and the same shift at the far end is
681 /// absorbed by `hash`, which is variable-length too. The two values below produced identical
682 /// bytes before the length prefix existed.
683 ///
684 /// Removing the prefix from `signing_input` turns this test red, which is the only reason to
685 /// believe it is testing anything.
686 #[test]
687 fn test_the_preimage_cannot_be_split_two_ways() -> Outcome<()> {
688 let a = Envelope {
689 schema: fmt!("a"),
690 author: vec![0xAA; 32],
691 sig_scheme: 0x0102_0304,
692 hash_scheme: 0x0506_0708,
693 time: 0x090A_0B0C_0D0E_0F10,
694 hash: vec![0x11; 32],
695 sig: Vec::new(),
696 tree_len: 0,
697 };
698 let b = Envelope {
699 schema: fmt!("a\u{1}"), // the same byte, read as schema rather than as scheme
700 author: vec![0xAA; 32],
701 sig_scheme: 0x0203_0405,
702 hash_scheme: 0x0607_0809,
703 time: 0x0A0B_0C0D_0E0F_1011,
704 hash: vec![0x11; 31],
705 sig: Vec::new(),
706 tree_len: 0,
707 };
708 // Not one field in common.
709 assert_ne!(a.schema, b.schema);
710 assert_ne!(a.sig_scheme, b.sig_scheme);
711 assert_ne!(a.hash_scheme, b.hash_scheme);
712 assert_ne!(a.time, b.time);
713 assert_ne!(a.hash, b.hash);
714 // The old, unprefixed reading of both is the same 49 bytes. Asserted rather than assumed:
715 // if the collision ever stops holding, this test must say so rather than pass by comparing
716 // two things that were never confusable in the first place.
717 let unprefixed = |e: &Envelope| -> Vec<u8> {
718 let mut v = Vec::new();
719 v.extend_from_slice(e.schema.as_bytes());
720 v.extend_from_slice(&e.sig_scheme.to_be_bytes());
721 v.extend_from_slice(&e.hash_scheme.to_be_bytes());
722 v.extend_from_slice(&e.time.to_be_bytes());
723 v.extend_from_slice(&e.hash);
724 v
725 };
726 assert_eq!(unprefixed(&a), unprefixed(&b),
727 "The collision this test is built on no longer holds, so it proves nothing.");
728 assert_eq!(unprefixed(&a).len(), 49);
729 // With the length in front of the schema, the two readings are different bytes.
730 assert_ne!(a.signing_input(), b.signing_input());
731 Ok(())
732 }
733}