Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_sbj/src/doc.rs

56.3 KiB, 66 runs

created by r1870400018:22210, 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//! Reading and writing whole documents: the verification order of `SPEC.md` §2.
2//!
3//! A document is verified before it is parsed, and content that fails is never parsed at all. The
4//! header says what the file is, the envelope says what the tree region should hash to and who
5//! vouches for that hash, and only a tree region whose bytes hash to what the author signed is
6//! handed to a decoder. Steps 1 to 5 touch no content, so a caller may run them and stop, which is
7//! what [`verify_only`] is for.
8//!
9//! [`write`] is the inverse, and refuses to sign what [`read`] would reject: the tree is validated
10//! against its schema, encoded canonically (§3), hashed, and the hash signed, before a byte of it
11//! reaches a file.
12
13use crate::{
14 canon,
15 card::Card,
16 envelope::{
17 self,
18 Envelope,
19 },
20 index,
21 kinds::Schema,
22 limit,
23 post::Post,
24 share::Share,
25 validate,
26 HEADER_LEN,
27 SCHEMA_CARD,
28 SCHEMA_POST,
29 SCHEMA_SHARE,
30};
31
32use oxedyne_fe2o3_core::prelude::*;
33use oxedyne_fe2o3_crypto::sign::SignatureScheme;
34use oxedyne_fe2o3_hash::hash::HashScheme;
35use oxedyne_fe2o3_iop_crypto::{
36 keys::KeyManager,
37 sign::Signer,
38};
39use oxedyne_fe2o3_iop_hash::api::Hasher;
40use oxedyne_fe2o3_jdat::prelude::*;
41
42/// A verified document: holding one *is* holding a document whose header, envelope, hash, signature,
43/// canonical encoding and schema all checked out, because [`read`] is the only way to obtain one.
44///
45/// The guarantee is the type's, not the caller's. The fields are private and there is no public
46/// constructor, so a `Doc` cannot be minted by a cache, a test helper, an application host or a
47/// refactor: every one of them must go through [`read`], which verifies. Anything downstream that
48/// renders, indexes, stores or attributes a `Doc` may therefore say so without qualification, and
49/// nothing here may be weakened without taking that claim away from all of them at once.
50///
51/// The fields are read-only, for the same reason. Handing out `&mut` to the tree would let a
52/// verified envelope be paired with a tree its author never signed, which is the same hole reached
53/// by a different door.
54///
55/// A `Doc` is read from a file, and cannot be made any other way:
56///
57/// ```no_run
58/// use oxedyne_fe2o3_core::prelude::*;
59///
60/// fn show(bytes: &[u8]) -> Outcome<()> {
61/// let doc = res!(oxedyne_fe2o3_sbj::doc::read(bytes)); // The only route.
62/// let _tree = doc.tree();
63/// let _author = &doc.env().author;
64/// Ok(())
65/// }
66/// ```
67///
68/// An unverified one cannot be assembled out of its parts (E0451, the fields are private):
69///
70/// ```compile_fail
71/// use oxedyne_fe2o3_jdat::prelude::Dat;
72/// use oxedyne_fe2o3_sbj::{doc::Doc, envelope::Envelope};
73///
74/// fn forge(env: Envelope, tree: Dat) -> Doc {
75/// Doc { env, tree }
76/// }
77/// ```
78///
79/// Nor can a verified one have its tree swapped for one nobody signed (E0616, the same):
80///
81/// ```compile_fail
82/// use oxedyne_fe2o3_jdat::prelude::Dat;
83/// use oxedyne_fe2o3_sbj::doc::Doc;
84///
85/// fn tamper(doc: &mut Doc, tree: Dat) {
86/// doc.tree = tree;
87/// }
88/// ```
89#[derive(Clone, Debug)]
90pub struct Doc {
91 /// The signed envelope.
92 env: Envelope,
93 /// The decoded node tree.
94 tree: Dat,
95}
96
97impl Doc {
98
99 /// The envelope the author signed: the schema, the author, the schemes, the time, the address.
100 pub fn env(&self) -> &Envelope {
101 &self.env
102 }
103
104 /// The node tree, which hashes to the address the envelope carries.
105 pub fn tree(&self) -> &Dat {
106 &self.tree
107 }
108
109 /// Takes the tree, for a caller that owns the document and wants only what is in it.
110 pub fn into_tree(self) -> Dat {
111 self.tree
112 }
113
114 /// Takes the document apart, for a caller that owns it and wants both halves.
115 ///
116 /// The parts carry no guarantee once separated, which is why they are only ever handed out to a
117 /// caller that already held the whole: an `Envelope` and a `Dat` cannot be made into a `Doc`.
118 pub fn into_parts(self) -> (Envelope, Dat) {
119 (self.env, self.tree)
120 }
121}
122
123/// What the payload region of an artefact holds, and the one place a schema name chooses a
124/// validator.
125///
126/// The container carries any schema (§1.2), and the schemas it carries are no longer one shape. An
127/// oxeweb document is a tree of typed nodes, so it is validated by walking that tree against the
128/// vocabulary its schema admits; a post and a card are flat canonical maps whose whole validity is
129/// their own field rules, and the node vocabulary has nothing to say about either. Putting them
130/// through [`validate::validate`] would mean teaching a tree walker two schemas with no tree in
131/// them, and teaching [`Schema`] — whose stated job is to fix a vocabulary of node kinds and a
132/// vocabulary of style properties — two members that have neither.
133///
134/// An enum instead, so that the dispatch is exhaustive: a sixth schema cannot be added without the
135/// compiler naming every place that must learn about it. The variant carries the schema for a tree,
136/// where three names share one shape, and fixes it for a post and a card, where the name and the
137/// shape are the same fact.
138#[derive(Clone, Debug, PartialEq)]
139pub enum Payload {
140 /// An oxeweb node tree, under whichever of the three `oxeweb/*` schemas admits its vocabulary.
141 Tree {
142 /// The schema the envelope declares.
143 schema: Schema,
144 /// The node tree.
145 tree: Dat,
146 },
147 /// A `daimond/post/0` message.
148 Post(Post),
149 /// A `daimond/card/0` identity card.
150 Card(Card),
151 /// A `daimond/share/0` copy of something one person is sending another.
152 Share(Share),
153}
154
155impl Payload {
156
157 /// The schema name the envelope declares for this payload.
158 pub fn schema(&self) -> &'static str {
159 match self {
160 Self::Tree { schema, .. } => schema.name(),
161 Self::Post(_) => SCHEMA_POST,
162 Self::Card(_) => SCHEMA_CARD,
163 Self::Share(_) => SCHEMA_SHARE,
164 }
165 }
166
167 /// Validates this payload against its own rules, then encodes it canonically.
168 ///
169 /// Nothing this crate would refuse to read is ever given a signature and an address, which for
170 /// a tree means the schema walk of §4 and for a record means its own field rules. Each arm
171 /// validates before it encodes.
172 pub fn encode(&self) -> Outcome<Vec<u8>> {
173 let bytes = match self {
174 Self::Tree { schema, tree } => res!(encode_tree(tree, schema.name())),
175 Self::Post(p) => res!(p.encode()),
176 Self::Card(c) => res!(c.encode()),
177 Self::Share(s) => res!(s.encode()),
178 };
179 // Checked here as well as inside the arms that check it, so that the container's own limit
180 // is not something a payload kind added later can be written without meeting.
181 if bytes.len() > limit::TREE_BYTES {
182 return Err(err!(
183 "The {} payload encodes to {} bytes, exceeding the limit of {} bytes (SPEC.md §5).",
184 self.schema(), bytes.len(), limit::TREE_BYTES;
185 Invalid, Input, TooBig, LimitReached));
186 }
187 Ok(bytes)
188 }
189
190 /// Decodes and validates a payload region under the schema the envelope declared.
191 ///
192 /// The bytes must already have been hashed and the hash found to be the one the author signed,
193 /// which is why this is not public: the only caller is [`read_artefact`], and reaching it any
194 /// other way would be parsing content nobody vouched for.
195 fn decode(
196 schema: &str,
197 bytes: &[u8],
198 )
199 -> Outcome<Self>
200 {
201 match schema {
202 SCHEMA_POST => Ok(Self::Post(res!(Post::decode(bytes)))),
203 SCHEMA_CARD => Ok(Self::Card(res!(Card::decode(bytes)))),
204 SCHEMA_SHARE => Ok(Self::Share(res!(Share::decode(bytes)))),
205 other => {
206 let schema = res!(Schema::from_name(other));
207 // `canon::decode` enforces the depth limit as it descends, and checks every rule of
208 // §3 that survives a decode, so `canon::check` is not repeated here.
209 let tree = res!(canon::decode(bytes));
210 res!(validate::validate(&tree, schema.name()));
211 Ok(Self::Tree {
212 schema,
213 tree,
214 })
215 },
216 }
217 }
218}
219
220/// A verified artefact: holding one *is* holding a file whose header, envelope, hash, signature,
221/// canonical encoding and schema all checked out, because [`read_artefact`] is the only way to
222/// obtain one.
223///
224/// The same guarantee [`Doc`] carries, over the whole set of schemas rather than the three that are
225/// node trees, and for the same reason: the fields are private, there is no public constructor, and
226/// no `&mut` is handed out, so a verified envelope cannot be paired with a payload its author never
227/// signed.
228#[derive(Clone, Debug)]
229pub struct Artefact {
230 /// The signed envelope.
231 env: Envelope,
232 /// The decoded, validated payload.
233 payload: Payload,
234}
235
236impl Artefact {
237
238 /// The envelope the author signed: the schema, the author, the schemes, the time, the address.
239 pub fn env(&self) -> &Envelope {
240 &self.env
241 }
242
243 /// The payload, which hashes to the address the envelope carries.
244 pub fn payload(&self) -> &Payload {
245 &self.payload
246 }
247
248 /// Takes the payload, for a caller that owns the artefact and wants only what is in it.
249 pub fn into_payload(self) -> Payload {
250 self.payload
251 }
252
253 /// Takes the artefact apart, for a caller that owns it and wants both halves.
254 ///
255 /// The parts carry no guarantee once separated, which is why they are only ever handed out to a
256 /// caller that already held the whole.
257 pub fn into_parts(self) -> (Envelope, Payload) {
258 (self.env, self.payload)
259 }
260}
261
262/// The regions of a file, located by the header and the envelope but not yet trusted.
263///
264/// The tree region is exactly the `tree_len` bytes the envelope declares. Whatever follows it is
265/// the optional index of §1.4, which lies outside the hash, is derived from the tree, and is never
266/// trusted, so nothing here reads it.
267#[derive(Clone, Copy, Debug)]
268struct Regions<'a> {
269 /// The tree region, exactly as long as the envelope declares.
270 tree: &'a [u8],
271 /// Whatever trails the tree region: the optional index, or nothing.
272 rest: &'a [u8],
273}
274
275/// Reads a document: header, envelope, hash, signature, decode, validate, in that order.
276///
277/// The tree is decoded only once its bytes have been hashed and the hash found to be the one the
278/// author signed. Decoding enforces the depth limit of §5 as it descends, and rejects bytes that
279/// are not the canonical encoding of the tree they decode to (§3). The decoded tree is then
280/// validated against the schema the envelope declares.
281pub fn read(buf: &[u8]) -> Outcome<Doc> {
282 let (env, tree_bytes) = res!(verify(buf));
283 // Steps 6 and 7. `canon::decode` enforces the depth limit during decoding, and checks the tree
284 // against every rule of §3 that survives a decode, which is what `canon::check` does, so the
285 // check is not repeated here.
286 let tree = res!(canon::decode(tree_bytes));
287 res!(validate::validate(&tree, &env.schema));
288 Ok(Doc {
289 env,
290 tree,
291 })
292}
293
294/// Verifies a document without decoding its tree: steps 1 to 5 of §2, which touch no content.
295pub fn verify_only(buf: &[u8]) -> Outcome<Envelope> {
296 let (env, _) = res!(verify(buf));
297 Ok(env)
298}
299
300/// Verifies a document and returns its envelope and the tree region the envelope vouches for.
301///
302/// The bytes returned have been hashed with the scheme the envelope names, found to hash to what
303/// the envelope declares, and that declaration found to have been signed by the author. They have
304/// not been decoded, and nothing yet knows whether they are a tree at all.
305pub fn verify<'a>(buf: &'a [u8]) -> Outcome<(Envelope, &'a [u8])> {
306
307 // Steps 1 to 3.
308 let (env, regions) = res!(locate(buf));
309
310 // Step 4: the tree region hashes to what the envelope declares.
311 let hash = res!(hash_tree(env.hash_scheme, regions.tree));
312 if hash != env.hash {
313 return Err(err!(
314 "The {} byte tree region hashes to {}, but the envelope declares the hash {}. The \
315 hash is the document's address, so a tree that does not hash to it is not this \
316 document.", regions.tree.len(), hex(&hash), hex(&env.hash);
317 Invalid, Input, Mismatch));
318 }
319
320 // Step 5: the author signed that hash. The width is checked first, because a signature of the
321 // wrong width is not a failed verification but a malformed field, and the signing crate answers
322 // one with an error carrying nothing about this format in it.
323 let verifier = res!(verifier(env.sig_scheme, &env.author));
324 res!(check_sig_len(env.sig_scheme, env.sig.len()));
325 let input = env.signing_input();
326 if !res!(verifier.verify(&input, &env.sig)) {
327 return Err(err!(
328 "The signature in the envelope is not a signature by the author {} over the signing \
329 input of this document (SPEC.md §1.3): schema '{}', scheme ids {:#010X} and {:#010X}, \
330 time {}, hash {}.",
331 hex(&env.author), env.schema, env.sig_scheme, env.hash_scheme, env.time,
332 hex(&env.hash);
333 Invalid, Input, Security));
334 }
335
336 Ok((env, regions.tree))
337}
338
339/// Returns the region trailing the tree, which holds the optional index of §1.4, if any.
340///
341/// The bytes are derived data lying outside the hash, and are not trusted by anything here: what
342/// they say is checked against the tree by `index::check` before it is believed.
343pub fn index_region<'a>(buf: &'a [u8]) -> Outcome<&'a [u8]> {
344 let (_, regions) = res!(locate(buf));
345 Ok(regions.rest)
346}
347
348/// Reads an artefact of any schema this build carries: header, envelope, hash, signature, then the
349/// payload's own decoder and validator.
350///
351/// [`read`] is this for the three `oxeweb/*` schemas, and returns a [`Doc`] because a caller that
352/// asked for a document wants a tree rather than a match. A caller that will take whatever the file
353/// turns out to be asks here.
354pub fn read_artefact(buf: &[u8]) -> Outcome<Artefact> {
355 let (env, payload_bytes) = res!(verify(buf));
356 // Steps 6 and 7, dispatched on the schema the author signed. Nothing here runs until the bytes
357 // have hashed to the address in the envelope and that address has been found to be signed.
358 let payload = res!(Payload::decode(&env.schema, payload_bytes));
359 Ok(Artefact {
360 env,
361 payload,
362 })
363}
364
365/// Writes an artefact of any schema this build carries: validate, canonical encode, hash, sign,
366/// assemble.
367///
368/// The payload is validated against its own rules first, so that nothing this crate would refuse to
369/// read is ever given a signature and an address.
370pub fn write_artefact(
371 payload: &Payload,
372 signer: &SignatureScheme,
373 time: u64,
374)
375 -> Outcome<Vec<u8>>
376{
377 let bytes = res!(payload.encode());
378 let env = res!(seal(&bytes, payload.schema(), signer, time));
379 assemble(&env, &bytes)
380}
381
382/// Writes a document: validate, canonical encode, hash, sign, assemble.
383///
384/// The tree is validated against the schema first, so that nothing this crate would refuse to read
385/// is ever given a signature and an address. The schema must be one of the three that are node
386/// trees; a post or a card is written by [`write_artefact`], which takes the payload rather than a
387/// tree because neither is one.
388pub fn write(
389 tree: &Dat,
390 schema: &str,
391 signer: &SignatureScheme,
392 time: u64,
393)
394 -> Outcome<Vec<u8>>
395{
396 // Written out rather than routed through `write_artefact`, which would have to be handed an
397 // owned tree: a `Dat` clone recurses as deep as the tree goes, which is the cost `validate`
398 // walks by reference to avoid. Each step below is the same function `write_artefact` calls.
399 let tree_bytes = res!(encode_tree(tree, schema));
400 let env = res!(seal(&tree_bytes, schema, signer, time));
401 assemble(&env, &tree_bytes)
402}
403
404/// Writes a document, and appends the optional index of §1.4.
405///
406/// The index lies outside the hash, so the document has the same address, and is the same document,
407/// whether it is written with an index or without one.
408pub fn write_with_index(
409 tree: &Dat,
410 schema: &str,
411 signer: &SignatureScheme,
412 time: u64,
413)
414 -> Outcome<Vec<u8>>
415{
416 let tree_bytes = res!(encode_tree(tree, schema));
417 let env = res!(seal(&tree_bytes, schema, signer, time));
418 let mut buf = res!(assemble(&env, &tree_bytes));
419 buf.extend_from_slice(&res!(index::build(&tree_bytes)));
420 Ok(buf)
421}
422
423/// Hashes a tree region with the scheme the envelope names.
424pub fn hash_tree(
425 scheme: u32,
426 bytes: &[u8],
427)
428 -> Outcome<Vec<u8>>
429{
430 let hasher = res!(hasher(scheme));
431 Ok(hasher.hash(&[bytes], [0u8; 0]).as_vec())
432}
433
434/// Locates the regions of a file: steps 1 to 3 of §2.
435///
436/// The header is checked, the envelope decoded, and the tree region measured against the bytes
437/// available. A tree region shorter than the envelope declares is a rejection rather than a
438/// truncation, and one longer than the limit of §5 is refused before a byte of it is read.
439fn locate<'a>(buf: &'a [u8]) -> Outcome<(Envelope, Regions<'a>)> {
440
441 // Step 1: the header. Magic, major version, and the envelope length, which is checked against
442 // the limit of §5 before it is believed.
443 let hdr = res!(envelope::read_header(buf));
444 let env_end = HEADER_LEN + hdr.env_len as usize;
445 if buf.len() < env_end {
446 return Err(err!(
447 "The header declares an envelope of {} bytes, which would end at byte {}, but the \
448 file is {} bytes.", hdr.env_len, env_end, buf.len();
449 Invalid, Input, Decode));
450 }
451
452 // Step 2: the envelope, whose every key must be present and correctly typed.
453 let env = res!(Envelope::decode(&buf[HEADER_LEN..env_end]));
454
455 // Step 3: the tree region, against the limit and against the bytes there are.
456 let tree_len = try_into!(usize, env.tree_len);
457 if tree_len > limit::TREE_BYTES {
458 return Err(err!(
459 "The envelope declares a tree region of {} bytes, exceeding the limit of {} bytes \
460 (SPEC.md §5). The limit is enforced before decoding, so an envelope claiming a tree \
461 larger than this is never believed.", tree_len, limit::TREE_BYTES;
462 Invalid, Input, TooBig, LimitReached));
463 }
464 let avail = buf.len() - env_end;
465 if avail < tree_len {
466 return Err(err!(
467 "The envelope declares a tree region of {} bytes, but only {} bytes follow the \
468 envelope. A tree region shorter than declared is a rejection, not a truncation \
469 (SPEC.md §2).", tree_len, avail;
470 Invalid, Input, Decode));
471 }
472 let tree_end = env_end + tree_len;
473
474 Ok((env, Regions {
475 tree: &buf[env_end..tree_end],
476 rest: &buf[tree_end..],
477 }))
478}
479
480/// Validates a tree against its schema, then encodes it canonically, refusing one too large for §5.
481fn encode_tree(
482 tree: &Dat,
483 schema: &str,
484)
485 -> Outcome<Vec<u8>>
486{
487 res!(validate::validate(tree, schema));
488 let bytes = res!(canon::encode(tree));
489 if bytes.len() > limit::TREE_BYTES {
490 return Err(err!(
491 "The tree encodes to {} bytes, exceeding the limit of {} bytes (SPEC.md §5).",
492 bytes.len(), limit::TREE_BYTES;
493 Invalid, Input, TooBig, LimitReached));
494 }
495 Ok(bytes)
496}
497
498/// Builds the envelope for an encoded payload, up to but not including the signature.
499///
500/// The half of sealing that holds no key material, so that a signer living somewhere this code
501/// cannot reach — a browser's non-extractable `CryptoKey`, a hardware token — can still produce an
502/// artefact. The caller names the author's public key, takes [`Envelope::signing_input`] away,
503/// signs it wherever the secret is, puts the signature in `sig`, and hands the envelope to
504/// [`assemble`]. The secret never crosses this boundary in either direction.
505///
506/// The returned envelope carries an empty `sig` and is not yet a sealed envelope. `assemble` will
507/// happily write one with an empty signature, and [`read`] will refuse it, which is the correct
508/// order: an unsigned artefact is a rejection at step 5 and not a special case anywhere earlier.
509pub fn envelope_for(
510 payload_bytes: &[u8],
511 schema: &str,
512 author: &[u8],
513 time: u64,
514)
515 -> Outcome<Envelope>
516{
517 // The author key is checked at its width here rather than at verification time, because a key
518 // of the wrong width names no signer and the artefact it would produce is unreadable by
519 // everybody including its writer.
520 if author.len() != SignatureScheme::ED25519_PK_LEN {
521 return Err(err!(
522 "The v0 envelope names the Ed25519 signature scheme, whose public key is {} bytes, \
523 but the author key supplied is {} bytes.",
524 SignatureScheme::ED25519_PK_LEN, author.len();
525 Invalid, Input, Mismatch));
526 }
527 let hash_scheme = envelope::HASH_SCHEME_SHA3_256;
528 Ok(Envelope {
529 schema: schema.to_string(),
530 author: author.to_vec(),
531 sig_scheme: envelope::SIG_SCHEME_ED25519,
532 hash_scheme,
533 time,
534 hash: res!(hash_tree(hash_scheme, payload_bytes)),
535 sig: Vec::new(),
536 tree_len: try_into!(u64, payload_bytes.len()),
537 })
538}
539
540/// Builds the envelope for an encoded payload: hash the bytes, then sign the hash.
541///
542/// Signing the hash rather than the payload is what binds the artefact's permanent address to its
543/// author, and the schema and the scheme ids go into the signing input so that neither can be
544/// re-labelled afterwards.
545pub fn seal(
546 payload_bytes: &[u8],
547 schema: &str,
548 signer: &SignatureScheme,
549 time: u64,
550)
551 -> Outcome<Envelope>
552{
553 // Refused before anything is hashed: a signer whose scheme v0 cannot name in an envelope must
554 // not produce bytes at all.
555 let sig_scheme = res!(sig_scheme_id(signer));
556 let author = match res!(signer.get_public_key()) {
557 Some(pk) => pk.to_vec(),
558 None => return Err(err!(
559 "The signer holds no public key, so there is no author to name in the envelope.";
560 Missing, Configuration)),
561 };
562 let mut env = res!(envelope_for(payload_bytes, schema, &author, time));
563 env.sig_scheme = sig_scheme;
564 env.sig = res!(signer.sign(&env.signing_input()));
565 Ok(env)
566}
567
568/// Assembles a file: header, envelope, payload region.
569///
570/// The envelope's signature is written as it stands and is not checked here, since this is the
571/// half of writing that a caller signing elsewhere reaches after [`envelope_for`]. What makes an
572/// artefact sound is that [`read`] accepts it, and nothing else.
573pub fn assemble(
574 env: &Envelope,
575 tree_bytes: &[u8],
576)
577 -> Outcome<Vec<u8>>
578{
579 let env_bytes = res!(env.encode());
580 let mut buf = res!(envelope::write_header(env_bytes.len()));
581 buf.reserve(env_bytes.len() + tree_bytes.len());
582 buf.extend_from_slice(&env_bytes);
583 buf.extend_from_slice(tree_bytes);
584 Ok(buf)
585}
586
587/// The scheme id a signer signs under, refusing one v0 cannot name in an envelope.
588fn sig_scheme_id(signer: &SignatureScheme) -> Outcome<u32> {
589 match signer {
590 SignatureScheme::Ed25519(..) => Ok(envelope::SIG_SCHEME_ED25519),
591 other => Err(err!(
592 "The v0 envelope names the signature scheme Ed25519 only, but the signer is a {:?}.",
593 other;
594 Invalid, Input, Unimplemented)),
595 }
596}
597
598/// The hash scheme a scheme id names, refusing an id this version does not implement.
599///
600/// A signature scheme may be replaced freely, since a signature is checked once and discarded, but
601/// a hash scheme may not, because the hash is the address.
602fn hasher(scheme: u32) -> Outcome<HashScheme> {
603 match scheme {
604 envelope::HASH_SCHEME_SHA3_256 => Ok(HashScheme::new_sha3_256()),
605 _ => Err(err!(
606 "The envelope names the hash scheme {:#010X}, which this version does not implement. \
607 v0 hashes with SHA3-256, whose scheme id is {:#010X}.",
608 scheme, envelope::HASH_SCHEME_SHA3_256;
609 Invalid, Input, Unimplemented)),
610 }
611}
612
613/// Checks a signature's width against the scheme that wrote it.
614///
615/// An unsigned envelope is the case that matters: `envelope_for` builds one with an empty `sig` so
616/// that a caller signing elsewhere has something to fill in, and an artefact assembled around one
617/// before the signature arrives must be refused here, saying so, rather than deep inside a
618/// signature library that has never heard of this format.
619fn check_sig_len(
620 scheme: u32,
621 len: usize,
622)
623 -> Outcome<()>
624{
625 let want = match scheme {
626 envelope::SIG_SCHEME_ED25519 => envelope::SIG_LEN_ED25519,
627 // Any other scheme id was already refused by `verifier`, which runs first.
628 _ => return Ok(()),
629 };
630 if len != want {
631 return Err(err!(
632 "The envelope carries a signature of {} bytes, but the scheme it names writes \
633 signatures of {} bytes. {}",
634 len, want,
635 if len == 0 {
636 "The signature is empty, so this artefact was assembled before it was signed."
637 } else {
638 "A signature of the wrong width is a malformed field, not a failed check."
639 };
640 Invalid, Input, Mismatch));
641 }
642 Ok(())
643}
644
645/// The verifier for a scheme id and an author's public key, refusing an id v0 does not implement.
646fn verifier(
647 scheme: u32,
648 author: &[u8],
649)
650 -> Outcome<SignatureScheme>
651{
652 match scheme {
653 envelope::SIG_SCHEME_ED25519 => {
654 if author.len() != SignatureScheme::ED25519_PK_LEN {
655 return Err(err!(
656 "The envelope names the Ed25519 signature scheme, whose public key is {} \
657 bytes, but the author key it carries is {} bytes.",
658 SignatureScheme::ED25519_PK_LEN, author.len();
659 Invalid, Input, Mismatch));
660 }
661 Ok(res!(SignatureScheme::empty_ed25519().set_public_key(Some(author))))
662 },
663 _ => Err(err!(
664 "The envelope names the signature scheme {:#010X}, which this version does not \
665 implement. v0 signs with Ed25519, whose scheme id is {:#010X}.",
666 scheme, envelope::SIG_SCHEME_ED25519;
667 Invalid, Input, Unimplemented)),
668 }
669}
670
671/// Renders bytes as hexadecimal, for an error message that must name what it rejected.
672fn hex(byts: &[u8]) -> String {
673 let mut s = String::new();
674 for b in byts {
675 s.push_str(&fmt!("{:02x}", b));
676 }
677 s
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683 use crate::{
684 kinds::NodeKind,
685 MAGIC,
686 SCHEMA_DOC,
687 };
688
689 use oxedyne_fe2o3_jdat::usr::UsrKindId;
690
691 /// A fixed authoring time, so that a document written twice is written the same.
692 const TIME: u64 = 1_752_000_000_000;
693
694 /// Wraps a payload as a node of the given kind.
695 fn node(kind: NodeKind, payload: Dat) -> Dat {
696 Dat::Usr(
697 UsrKindId::new(kind.code(), Some(kind.label()), None),
698 Some(Box::new(payload)),
699 )
700 }
701
702 /// A text run, whose payload is a bare string.
703 fn text(s: &str) -> Dat {
704 node(NodeKind::Text, dat!(s))
705 }
706
707 /// A small but complete document: a heading, a paragraph, and emphasis inside it.
708 fn sample_tree() -> Dat {
709 node(NodeKind::Doc, mapdat!{
710 "title" => dat!("Style without a cascade"),
711 "lang" => dat!("en"),
712 "children" => Dat::List(vec![
713 node(NodeKind::Heading, mapdat!{
714 "level" => dat!(2u8),
715 "children" => Dat::List(vec![text("Style without a cascade")]),
716 }),
717 node(NodeKind::Para, mapdat!{
718 "children" => Dat::List(vec![
719 text("A paragraph, and some "),
720 node(NodeKind::Emph, mapdat!{
721 "strong" => dat!(true),
722 "children" => Dat::List(vec![text("emphasis")]),
723 }),
724 ]),
725 }),
726 ]),
727 })
728 }
729
730 /// A file carrying the sample document, signed by a fresh key.
731 fn sample_file() -> Outcome<(SignatureScheme, Vec<u8>)> {
732 let signer = SignatureScheme::new_ed25519();
733 let buf = res!(write(&sample_tree(), SCHEMA_DOC, &signer, TIME));
734 Ok((signer, buf))
735 }
736
737 /// The offset at which the tree region of a file starts.
738 fn tree_start(buf: &[u8]) -> Outcome<usize> {
739 let hdr = res!(envelope::read_header(buf));
740 Ok(HEADER_LEN + hdr.env_len as usize)
741 }
742
743 /// Asserts that reading fails, and that the message says why.
744 fn rejects(buf: &[u8], what: &str, says: &str) -> Outcome<()> {
745 match read(buf) {
746 Ok(_) => Err(err!(
747 "Expected a rejection of {}, but the document was read.", what;
748 Test, Invalid)),
749 Err(e) => {
750 let msg = fmt!("{}", e);
751 assert!(msg.contains(says),
752 "The rejection of {} should say '{}', but says: {}", what, says, msg);
753 Ok(())
754 },
755 }
756 }
757
758 #[test]
759 fn test_signed_round_trip_00() -> Outcome<()> {
760 let (signer, buf) = res!(sample_file());
761 let doc = res!(read(&buf));
762 assert_eq!(doc.tree, sample_tree(), "The tree did not survive a signed round trip.");
763 assert_eq!(doc.env.schema, SCHEMA_DOC);
764 assert_eq!(doc.env.time, TIME);
765 assert_eq!(doc.env.sig_scheme, envelope::SIG_SCHEME_ED25519);
766 assert_eq!(doc.env.hash_scheme, envelope::HASH_SCHEME_SHA3_256);
767 match res!(signer.get_public_key()) {
768 Some(pk) => assert_eq!(&doc.env.author[..], pk, "The author is not the signer."),
769 None => return Err(err!("The signer holds no public key."; Test, Invalid)),
770 }
771 // The envelope's hash is the hash of the tree region, and the region is what it declares.
772 let start = res!(tree_start(&buf));
773 let tree_bytes = &buf[start..];
774 assert_eq!(doc.env.tree_len as usize, tree_bytes.len());
775 assert_eq!(doc.env.hash, res!(hash_tree(doc.env.hash_scheme, tree_bytes)));
776 // Writing the same tree twice writes the same bytes: one document, one address.
777 let again = res!(write(&sample_tree(), SCHEMA_DOC, &signer, TIME));
778 assert_eq!(again, buf, "A document written twice is not the same document.");
779 Ok(())
780 }
781
782 #[test]
783 fn test_verify_only_never_decodes_01() -> Outcome<()> {
784 let (signer, buf) = res!(sample_file());
785 let env = res!(verify_only(&buf));
786 assert_eq!(env, res!(read(&buf)).env);
787
788 // A tree region that is signed but is not a tree at all passes every step that touches no
789 // content, and fails the moment one does. This is the whole point of the ordering.
790 let rubbish = vec![0xFF; 64];
791 let env = res!(seal(&rubbish, SCHEMA_DOC, &signer, TIME));
792 let buf = res!(assemble(&env, &rubbish));
793 res!(verify_only(&buf));
794 assert!(read(&buf).is_err(), "Rubbish under a good signature was decoded as a tree.");
795 Ok(())
796 }
797
798 #[test]
799 fn test_corrupt_tree_byte_02() -> Outcome<()> {
800 let (_, buf) = res!(sample_file());
801 let start = res!(tree_start(&buf));
802 // Every byte of the tree region, flipped one at a time, is caught by the hash.
803 for i in start..buf.len() {
804 let mut bad = buf.clone();
805 bad[i] ^= 0x01;
806 res!(rejects(&bad, "a corrupted tree byte", "hashes to"));
807 assert!(verify_only(&bad).is_err(), "A corrupted tree byte survived verification.");
808 }
809 Ok(())
810 }
811
812 #[test]
813 fn test_corrupt_signature_03() -> Outcome<()> {
814 let (signer, buf) = res!(sample_file());
815 let start = res!(tree_start(&buf));
816 let tree_bytes = buf[start..].to_vec();
817 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
818 env.sig[0] ^= 0x01;
819 let bad = res!(assemble(&env, &tree_bytes));
820 res!(rejects(&bad, "a corrupted signature", "not a signature by the author"));
821 Ok(())
822 }
823
824 #[test]
825 fn test_signature_by_another_key_04() -> Outcome<()> {
826 let (signer, buf) = res!(sample_file());
827 let start = res!(tree_start(&buf));
828 let tree_bytes = buf[start..].to_vec();
829 // The document is signed correctly, but by a key that is not the author it names.
830 let other = SignatureScheme::new_ed25519();
831 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &other, TIME));
832 env.author = match res!(signer.get_public_key()) {
833 Some(pk) => pk.to_vec(),
834 None => return Err(err!("The signer holds no public key."; Test, Invalid)),
835 };
836 let bad = res!(assemble(&env, &tree_bytes));
837 res!(rejects(&bad, "a signature by another key", "not a signature by the author"));
838 // And the reverse: the right signature, an author who did not make it.
839 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
840 env.author = match res!(other.get_public_key()) {
841 Some(pk) => pk.to_vec(),
842 None => return Err(err!("The signer holds no public key."; Test, Invalid)),
843 };
844 let bad = res!(assemble(&env, &tree_bytes));
845 res!(rejects(&bad, "an author who did not sign", "not a signature by the author"));
846 Ok(())
847 }
848
849 #[test]
850 fn test_truncated_tree_05() -> Outcome<()> {
851 let (_, buf) = res!(sample_file());
852 // One byte short of the tree region the envelope declares.
853 let short = &buf[..buf.len() - 1];
854 res!(rejects(short, "a truncated tree", "shorter than declared"));
855 // And truncated to nothing at all.
856 let start = res!(tree_start(&buf));
857 res!(rejects(&buf[..start], "an absent tree", "shorter than declared"));
858 // A file truncated inside its envelope, and inside its header.
859 res!(rejects(&buf[..start - 1], "a truncated envelope", "envelope"));
860 assert!(read(&buf[..4]).is_err(), "A file shorter than its header was read.");
861 Ok(())
862 }
863
864 #[test]
865 fn test_tree_longer_than_tree_len_06() -> Outcome<()> {
866 let (signer, buf) = res!(sample_file());
867 let start = res!(tree_start(&buf));
868 let tree_bytes = buf[start..].to_vec();
869 let short = tree_bytes.len() - 1;
870
871 // The strongest form: the tree is one byte longer than `tree_len`, and the author has
872 // hashed and signed the shortened region, so the hash and the signature both pass. The
873 // declared region holds a tree cut off one byte from its end, and the decoder says so.
874 let mut env = res!(seal(&tree_bytes[..short], SCHEMA_DOC, &signer, TIME));
875 env.tree_len = short as u64;
876 let bad = res!(assemble(&env, &tree_bytes));
877 res!(verify_only(&bad)); // Steps 1 to 5 pass: the bytes are what the author signed.
878 assert!(read(&bad).is_err(), "A tree longer than tree_len was decoded.");
879
880 // The plain form: `tree_len` understates the tree by one byte, and the hash covers the tree
881 // the author meant. The hash of the declared region is not the hash the envelope carries.
882 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
883 env.tree_len = short as u64;
884 env.sig = res!(signer.sign(&env.signing_input()));
885 let bad = res!(assemble(&env, &tree_bytes));
886 res!(rejects(&bad, "a tree longer than tree_len", "hashes to"));
887
888 // A byte appended to the tree region without the envelope being told is a trailing byte,
889 // which is where an index would sit. It is outside the hash, so the document reads, and it
890 // is the same document at the same address.
891 let mut padded = buf.clone();
892 padded.push(0x00);
893 let doc = res!(read(&padded));
894 assert_eq!(doc.env, res!(read(&buf)).env, "A trailing byte changed the document.");
895 assert_eq!(res!(index_region(&padded)), &[0x00][..]);
896 Ok(())
897 }
898
899 #[test]
900 fn test_tree_shorter_than_tree_len_07() -> Outcome<()> {
901 let (signer, buf) = res!(sample_file());
902 let start = res!(tree_start(&buf));
903 let mut tree_bytes = buf[start..].to_vec();
904 // The tree region carries the tree and one byte more, all of it hashed and signed. The
905 // canonical encoding of a tree is the tree and nothing else (§3).
906 tree_bytes.push(0x00);
907 let env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
908 let bad = res!(assemble(&env, &tree_bytes));
909 res!(verify_only(&bad));
910 res!(rejects(&bad, "a byte trailing the tree inside the tree region", "canonical"));
911 Ok(())
912 }
913
914 #[test]
915 fn test_unknown_magic_08() -> Outcome<()> {
916 let (_, buf) = res!(sample_file());
917 for i in 0..MAGIC.len() {
918 let mut bad = buf.clone();
919 bad[i] ^= 0xFF;
920 res!(rejects(&bad, "an unknown magic", "Not an SBJ file"));
921 }
922 // An empty file, and a file that is valid BDAT but not an SBJ file at all.
923 assert!(read(&[]).is_err(), "An empty file was read.");
924 let bdat = res!(dat!("not a document").to_bytes(Vec::new()));
925 assert!(read(&bdat).is_err(), "A bare daticle was read as a document.");
926 Ok(())
927 }
928
929 #[test]
930 fn test_unknown_major_version_09() -> Outcome<()> {
931 let (_, buf) = res!(sample_file());
932 let mut bad = buf.clone();
933 bad[5] = 1; // Major version 1.
934 res!(rejects(&bad, "an unknown major version", "not implemented here"));
935 let mut bad = buf.clone();
936 bad[4] = 0xFF; // Major version 65280 and up.
937 res!(rejects(&bad, "an unknown major version", "not implemented here"));
938 Ok(())
939 }
940
941 #[test]
942 fn test_unknown_schemes_10() -> Outcome<()> {
943 let (signer, buf) = res!(sample_file());
944 let start = res!(tree_start(&buf));
945 let tree_bytes = buf[start..].to_vec();
946
947 // A hash scheme this version does not implement is refused before the tree is hashed.
948 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
949 env.hash_scheme = 0xDEAD_BEEF;
950 env.sig = res!(signer.sign(&env.signing_input()));
951 let bad = res!(assemble(&env, &tree_bytes));
952 res!(rejects(&bad, "an unknown hash scheme", "does not implement"));
953
954 // And a signature scheme, before the signature is checked.
955 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
956 env.sig_scheme = 0xDEAD_BEEF;
957 env.sig = res!(signer.sign(&env.signing_input()));
958 let bad = res!(assemble(&env, &tree_bytes));
959 res!(rejects(&bad, "an unknown signature scheme", "does not implement"));
960 Ok(())
961 }
962
963 #[test]
964 fn test_tree_region_limit_11() -> Outcome<()> {
965 let (signer, buf) = res!(sample_file());
966 let start = res!(tree_start(&buf));
967 let tree_bytes = buf[start..].to_vec();
968 // A tree region larger than the limit is refused on the envelope's word alone, without the
969 // bytes ever being supplied, let alone read.
970 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
971 env.tree_len = (limit::TREE_BYTES + 1) as u64;
972 env.sig = res!(signer.sign(&env.signing_input()));
973 let bad = res!(assemble(&env, &tree_bytes));
974 res!(rejects(&bad, "a tree region over the limit", "exceeding the limit"));
975 Ok(())
976 }
977
978 #[test]
979 fn test_foreign_schema_12() -> Outcome<()> {
980 // The container carries any schema, but this build validates one, so a payload declaring
981 // another is rejected rather than read as though it were a document.
982 let (signer, buf) = res!(sample_file());
983 let start = res!(tree_start(&buf));
984 let tree_bytes = buf[start..].to_vec();
985 let env = res!(seal(&tree_bytes, "oxeweb/cmd/0", &signer, TIME));
986 let bad = res!(assemble(&env, &tree_bytes));
987 res!(verify_only(&bad)); // The envelope is sound; it is the payload that is foreign.
988 res!(rejects(&bad, "a foreign schema", "oxeweb/cmd/0"));
989 // And write refuses to sign a tree it cannot validate.
990 assert!(write(&sample_tree(), "oxeweb/cmd/0", &signer, TIME).is_err(),
991 "A tree was signed under a schema this build cannot validate.");
992 Ok(())
993 }
994
995 #[test]
996 fn test_write_refuses_an_invalid_tree_13() -> Outcome<()> {
997 let signer = SignatureScheme::new_ed25519();
998 // A heading level outside 1..=6 never reaches a file, so it never gets an address.
999 let tree = node(NodeKind::Doc, mapdat!{
1000 "title" => dat!("T"),
1001 "lang" => dat!("en"),
1002 "children" => Dat::List(vec![
1003 node(NodeKind::Heading, mapdat!{
1004 "level" => dat!(7u8),
1005 "children" => Dat::List(vec![text("Too deep")]),
1006 }),
1007 ]),
1008 });
1009 match write(&tree, SCHEMA_DOC, &signer, TIME) {
1010 Ok(_) => return Err(err!("A heading of level 7 was signed."; Test, Invalid)),
1011 Err(e) => {
1012 let msg = fmt!("{}", e);
1013 assert!(msg.contains("1..=6"), "The refusal should name the range: {}", msg);
1014 },
1015 }
1016 // And a tree that is not canonical: an empty children list has two encodings.
1017 let tree = node(NodeKind::Doc, mapdat!{
1018 "title" => dat!("T"),
1019 "lang" => dat!("en"),
1020 "children" => Dat::List(vec![
1021 node(NodeKind::Para, mapdat!{
1022 "children" => Dat::List(Vec::new()),
1023 }),
1024 ]),
1025 });
1026 assert!(write(&tree, SCHEMA_DOC, &signer, TIME).is_err(),
1027 "A non-canonical tree was signed.");
1028 Ok(())
1029 }
1030
1031 /// A chain of boxes `depth` nodes deep, the doc at its head and the deepest box childless.
1032 fn chain(depth: usize) -> Dat {
1033 let mut inner = node(NodeKind::Boxx, mapdat!{});
1034 for _ in 0..(depth - 2) {
1035 inner = node(NodeKind::Boxx, mapdat!{
1036 "children" => Dat::List(vec![inner]),
1037 });
1038 }
1039 node(NodeKind::Doc, mapdat!{
1040 "title" => dat!("A deep document"),
1041 "lang" => dat!("en"),
1042 "children" => Dat::List(vec![inner]),
1043 })
1044 }
1045
1046 /// A document at the depth limit, and one past it.
1047 fn nesting_at_the_limit_and_past_it() -> Outcome<()> {
1048 let signer = SignatureScheme::new_ed25519();
1049
1050 // At the limit, a document is written and read like any other.
1051 let buf = res!(write(&chain(limit::DEPTH), SCHEMA_DOC, &signer, TIME));
1052 let doc = res!(read(&buf));
1053 assert_eq!(doc.tree, chain(limit::DEPTH), "A tree at the depth limit did not survive.");
1054
1055 // One past it, nothing is signed, and a file assembled by hand around such a tree is
1056 // refused. The bytes are what the author signed, so the refusal comes from the reading of
1057 // the tree, which is where the depth limit belongs: the tree is never validated, because
1058 // it is never built.
1059 let deep = chain(limit::DEPTH + 1);
1060 assert!(write(&deep, SCHEMA_DOC, &signer, TIME).is_err(),
1061 "A tree past the depth limit was signed.");
1062 let tree_bytes = res!(deep.to_bytes(Vec::new()));
1063 let env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
1064 let bad = res!(assemble(&env, &tree_bytes));
1065 res!(verify_only(&bad));
1066 res!(rejects(&bad, "a tree past the depth limit", "depth"));
1067 Ok(())
1068 }
1069
1070 #[test]
1071 fn test_nesting_at_the_limit_and_past_it_15() -> Outcome<()> {
1072 // A node costs three daticle levels, so a document at the node depth limit of 256 nests
1073 // daticles 770 deep, and a recursive decoder spends a frame on each. A release build
1074 // spends about half a kilobyte a level, so the deepest legal document costs it some 400
1075 // KiB, well inside the two megabytes a thread is given by default. A debug build spends
1076 // about eight times that, since it gives every arm of a match its own slot in the frame,
1077 // so the test runs on a thread with a stack that can hold the deepest document the format
1078 // permits. The limit is the format's, and it does not move to suit a test.
1079 let thread = match std::thread::Builder::new()
1080 .name("sbj_depth".to_string())
1081 .stack_size(16 * 1024 * 1024)
1082 .spawn(nesting_at_the_limit_and_past_it)
1083 {
1084 Ok(thread) => thread,
1085 Err(e) => return Err(err!(e,
1086 "Could not spawn the thread the deepest legal document is read on.";
1087 Test, Init)),
1088 };
1089 match thread.join() {
1090 Ok(outcome) => outcome,
1091 Err(_) => Err(err!(
1092 "The thread reading the deepest legal document did not return.";
1093 Test, Panic)),
1094 }
1095 }
1096
1097 #[test]
1098 fn test_a_doc_is_a_verified_document_16() -> Outcome<()> {
1099 // The claim the type makes is that holding a `Doc` means the document verified. That a `Doc`
1100 // cannot be constructed any other way is a fact about compilation, not about execution, so it
1101 // is asserted by the `compile_fail` doctests on `Doc` rather than here: no test that runs can
1102 // observe code that does not compile. What runs here is the other half of the claim: that
1103 // what `read` hands back has in fact been verified, checked from the outside, against the
1104 // document's own bytes rather than against anything the reader remembers.
1105 let (signer, buf) = res!(sample_file());
1106 let doc = res!(read(&buf));
1107
1108 // The tree is the tree the envelope vouches for: it re-encodes to the region that was hashed,
1109 // and that region hashes to the address the envelope carries.
1110 let tree_bytes = res!(canon::encode(doc.tree()));
1111 assert_eq!(doc.env().tree_len as usize, tree_bytes.len(),
1112 "The tree of a Doc is not the length the envelope declares.");
1113 assert_eq!(doc.env().hash, res!(hash_tree(doc.env().hash_scheme, &tree_bytes)),
1114 "The tree of a Doc does not hash to the address of its envelope.");
1115
1116 // And the author signed that address.
1117 let verifier = res!(verifier(doc.env().sig_scheme, &doc.env().author));
1118 assert!(res!(verifier.verify(&doc.env().signing_input(), &doc.env().sig)),
1119 "The envelope of a Doc carries a signature the author did not make.");
1120 match res!(signer.get_public_key()) {
1121 Some(pk) => assert_eq!(&doc.env().author[..], pk, "The author is not the signer."),
1122 None => return Err(err!("The signer holds no public key."; Test, Invalid)),
1123 }
1124
1125 // The accessors hand out no way to undo any of that: `env` and `tree` borrow, and the `into_`
1126 // pair consumes the document rather than opening it.
1127 let (env, tree) = res!(read(&buf)).into_parts();
1128 assert_eq!(env, *doc.env());
1129 assert_eq!(tree, *doc.tree());
1130 assert_eq!(res!(read(&buf)).into_tree(), *doc.tree());
1131
1132 // Every document that fails at any step fails to become a `Doc` at all, so there is no state
1133 // in which one exists and its verification did not happen.
1134 let start = res!(tree_start(&buf));
1135 let mut bad = buf.clone();
1136 bad[start] ^= 0x01; // A tree byte the author did not sign.
1137 res!(rejects(&bad, "a tampered tree", "hashes to"));
1138 let tree_bytes = buf[start..].to_vec();
1139 let mut env = res!(seal(&tree_bytes, SCHEMA_DOC, &signer, TIME));
1140 env.sig[0] ^= 0x01; // A signature the author did not make.
1141 let bad = res!(assemble(&env, &tree_bytes));
1142 res!(rejects(&bad, "a tampered signature", "not a signature by the author"));
1143 Ok(())
1144 }
1145
1146 #[test]
1147 fn test_index_is_outside_the_document_14() -> Outcome<()> {
1148 let signer = SignatureScheme::new_ed25519();
1149 let plain = res!(write(&sample_tree(), SCHEMA_DOC, &signer, TIME));
1150 let indexed = res!(write_with_index(&sample_tree(), SCHEMA_DOC, &signer, TIME));
1151 assert!(indexed.len() > plain.len(), "The index added nothing.");
1152
1153 // Two files carrying the same tree are the same document at the same address, whether or
1154 // not either carries an index.
1155 let a = res!(read(&plain));
1156 let b = res!(read(&indexed));
1157 assert_eq!(a.env, b.env, "The index changed the document's envelope.");
1158 assert_eq!(a.tree, b.tree, "The index changed the document's tree.");
1159 assert_eq!(a.env.hash, b.env.hash, "The index changed the document's address.");
1160
1161 // The index that was appended describes the tree it was built from.
1162 assert_eq!(res!(index_region(&plain)).len(), 0);
1163 let idx = res!(index::parse(res!(index_region(&indexed))));
1164 let (_, tree_bytes) = res!(verify(&indexed));
1165 res!(index::check(tree_bytes, &idx));
1166 // doc, heading, the heading's text, para, its text, the emph, and the emph's text.
1167 assert_eq!(idx.len(), 7);
1168 Ok(())
1169 }
1170
1171 /// A post, for the artefact tests.
1172 fn sample_post() -> Post {
1173 Post {
1174 body: fmt!("The crop is in, and the second field can wait."),
1175 to: vec![0xA1; crate::post::limit::KEY_BYTES],
1176 nonce: vec![0xB2; crate::post::limit::NONCE_BYTES],
1177 reply_to: None,
1178 refs: Vec::new(),
1179 }
1180 }
1181
1182 /// A whole file carrying a post can be written and read back.
1183 ///
1184 /// This is the gap [`Payload`] closes. Before it, [`write`] was the only writer and it routes
1185 /// every schema through the node-tree validator, which admits the three `oxeweb/*` names alone,
1186 /// so no path in the crate could produce or read a file carrying a message.
1187 #[test]
1188 fn test_a_post_is_a_whole_artefact_17() -> Outcome<()> {
1189 let signer = SignatureScheme::new_ed25519();
1190 let post = sample_post();
1191 let buf = res!(write_artefact(&Payload::Post(post.clone()), &signer, TIME));
1192 let back = res!(read_artefact(&buf));
1193 assert_eq!(back.env().schema, SCHEMA_POST);
1194 assert_eq!(*back.payload(), Payload::Post(post));
1195
1196 // The verification order is the container's, not the payload's: a payload byte the author
1197 // did not sign fails at the hash, before a decoder sees it.
1198 let start = res!(tree_start(&buf));
1199 let mut bad = buf.clone();
1200 bad[start] ^= 0x01;
1201 match read_artefact(&bad) {
1202 Ok(_) => return Err(err!("A tampered post was read."; Test, Invalid)),
1203 Err(e) => assert!(fmt!("{}", e).contains("hashes to")),
1204 }
1205 Ok(())
1206 }
1207
1208 /// A whole file carrying a card can be written and read back.
1209 #[test]
1210 fn test_a_card_is_a_whole_artefact_18() -> Outcome<()> {
1211 let signer = SignatureScheme::new_ed25519();
1212 let card = Card {
1213 label: fmt!("Jason"),
1214 enc: vec![0xE1; crate::card::limit::KEY_BYTES],
1215 role: crate::card::Role::Root,
1216 prev: None,
1217 };
1218 let buf = res!(write_artefact(&Payload::Card(card.clone()), &signer, TIME));
1219 let back = res!(read_artefact(&buf));
1220 assert_eq!(back.env().schema, SCHEMA_CARD);
1221 assert_eq!(*back.payload(), Payload::Card(card));
1222 Ok(())
1223 }
1224
1225 /// A payload cannot be re-labelled into another schema after signing.
1226 ///
1227 /// The attack the length prefix of §1.3 closes, run over the two schemas that made it possible
1228 /// to attempt: a post and a card are both flat maps, so the bytes of one can be handed to the
1229 /// other's decoder, and only the envelope says which it is. Re-labelling the envelope is what
1230 /// the signature refuses.
1231 #[test]
1232 fn test_a_payload_cannot_be_relabelled_19() -> Outcome<()> {
1233 let signer = SignatureScheme::new_ed25519();
1234 let bytes = res!(sample_post().encode());
1235 let env = res!(seal(&bytes, SCHEMA_POST, &signer, TIME));
1236 res!(read_artefact(&res!(assemble(&env, &bytes))));
1237
1238 // The same bytes, the same hash, the same signature, one word changed in the envelope.
1239 let mut relabelled = env.clone();
1240 relabelled.schema = SCHEMA_CARD.to_string();
1241 match read_artefact(&res!(assemble(&relabelled, &bytes))) {
1242 Ok(_) => Err(err!(
1243 "A post was read as a card, so the schema is not inside the signature.";
1244 Test, Invalid)),
1245 Err(e) => {
1246 assert!(fmt!("{}", e).contains("not a signature by the author"));
1247 Ok(())
1248 },
1249 }
1250 }
1251
1252 /// A caller may build an envelope, sign it elsewhere, and assemble the artefact, without the
1253 /// secret key ever reaching this crate.
1254 ///
1255 /// The seam the browser uses: `envelope_for` holds no key material, `signing_input` says what
1256 /// to sign, and `assemble` takes the signature back. The signer here stands in for the one that
1257 /// lives outside.
1258 #[test]
1259 fn test_signing_happens_elsewhere_20() -> Outcome<()> {
1260 let signer = SignatureScheme::new_ed25519();
1261 let author = match res!(signer.get_public_key()) {
1262 Some(pk) => pk.to_vec(),
1263 None => return Err(err!("A fresh signer holds no public key."; Test, Bug)),
1264 };
1265 let bytes = res!(sample_post().encode());
1266 let mut env = res!(envelope_for(&bytes, SCHEMA_POST, &author, TIME));
1267
1268 // Unsigned, and refused as such rather than by a special case.
1269 assert!(env.sig.is_empty());
1270 match read_artefact(&res!(assemble(&env, &bytes))) {
1271 Ok(_) => return Err(err!("An unsigned artefact was read."; Test, Invalid)),
1272 Err(e) => assert!(fmt!("{}", e).contains("assembled before it was signed")),
1273 }
1274
1275 env.sig = res!(signer.sign(&env.signing_input()));
1276 let back = res!(read_artefact(&res!(assemble(&env, &bytes))));
1277 assert_eq!(back.env().schema, SCHEMA_POST);
1278
1279 // The same artefact the all-in-one path writes, byte for byte.
1280 assert_eq!(
1281 res!(assemble(&env, &bytes)),
1282 res!(write_artefact(&Payload::Post(sample_post()), &signer, TIME)),
1283 );
1284 Ok(())
1285 }
1286
1287 /// A schema this build does not implement is refused, and named.
1288 ///
1289 /// This was `daimond/share/0` while that schema was reserved and nothing read one. It is now
1290 /// implemented (see [`crate::share`]), so the claim is carried by a name that really is
1291 /// unimplemented: an artefact declaring one is REFUSED rather than read as whichever schema is
1292 /// nearest, and the refusal says which schema it was handed.
1293 #[test]
1294 fn test_an_unimplemented_schema_is_refused_22() -> Outcome<()> {
1295 /// A schema no validator in this build reads, matching the `foreign_schema` fixture.
1296 const SCHEMA_FOREIGN: &'static str = "oxeweb/cmd/0";
1297
1298 let signer = SignatureScheme::new_ed25519();
1299 let bytes = res!(sample_post().encode());
1300 let env = res!(seal(&bytes, SCHEMA_FOREIGN, &signer, TIME));
1301 let buf = res!(assemble(&env, &bytes));
1302
1303 // Steps 1 to 5 touch no content, so the container is sound and the signature is the
1304 // author's: the refusal can only be the schema's.
1305 res!(verify_only(&buf));
1306 match read_artefact(&buf) {
1307 Ok(_) => Err(err!(
1308 "An artefact declaring the unimplemented schema '{}' was read.", SCHEMA_FOREIGN;
1309 Test, Invalid)),
1310 Err(e) => {
1311 assert!(fmt!("{}", e).contains(SCHEMA_FOREIGN),
1312 "The refusal does not name the schema it refused: {}", e);
1313 Ok(())
1314 },
1315 }
1316 }
1317
1318 /// A whole file carrying a share can be written and read back.
1319 ///
1320 /// The third payload schema through the same container, which is the claim worth making: the
1321 /// envelope, the address, the signature and every rule of §3 are the container's and did not
1322 /// change to admit it.
1323 #[test]
1324 fn test_a_share_is_a_whole_artefact_24() -> Outcome<()> {
1325 let signer = SignatureScheme::new_ed25519();
1326 let share = crate::share::Share::new(
1327 fmt!("Life log"),
1328 vec![0xA1; crate::share::limit::KEY_BYTES],
1329 vec![0xB2; crate::share::limit::NONCE_BYTES],
1330 None,
1331 vec![
1332 crate::share::File { path: fmt!("crystal.html"), body: b"<p>hi</p>".to_vec() },
1333 crate::share::File { path: fmt!("crystal.json"), body: b"{}".to_vec() },
1334 ],
1335 );
1336 let buf = res!(write_artefact(&Payload::Share(share.clone()), &signer, TIME));
1337 let back = res!(read_artefact(&buf));
1338 assert_eq!(back.env().schema, SCHEMA_SHARE);
1339 assert_eq!(*back.payload(), Payload::Share(share));
1340
1341 // The consent bit survives the round trip, which is the whole reason it is in the payload
1342 // rather than in a wrapper: the receiver checks the SENDER's mark.
1343 match back.payload() {
1344 Payload::Share(s) => assert!(s.code,
1345 "A share carrying a page came back saying it carries no code."),
1346 other => return Err(err!(
1347 "A share read back as a {}.", other.schema(); Test, Invalid)),
1348 }
1349
1350 // THE CONSENT BIT CANNOT BE SET BY WHATEVER CARRIES THE SHARE. The payload is what the
1351 // hash covers and the signature commits to, so a relay that set `code` on a share whose
1352 // author said there was none would have to forge a signature to go with it. Built by hand,
1353 // because a payload whose bit disagrees with its files is one the schema refuses to WRITE
1354 // and a carrier is not held to the schema.
1355 let plain = crate::share::Share::new(
1356 fmt!("Sourdough"),
1357 vec![0xA1; crate::share::limit::KEY_BYTES],
1358 vec![0xB2; crate::share::limit::NONCE_BYTES],
1359 None,
1360 vec![crate::share::File { path: fmt!("crystal.json"), body: b"{}".to_vec() }],
1361 );
1362 assert!(!plain.code, "A share of one data file claims to carry code.");
1363 let good = res!(plain.encode());
1364 let signed = res!(write_artefact(&Payload::Share(plain.clone()), &signer, TIME));
1365 let mut m = match res!(plain.to_dat()) {
1366 Dat::Map(m) => m,
1367 other => return Err(err!(
1368 "A share encodes as a map, and this is a {:?}.", other.kind(); Test, Bug)),
1369 };
1370 m.insert(dat!(crate::share::KEY_CODE), Dat::Bool(true));
1371 let lied = res!(Dat::Map(m).to_bytes(Vec::new()));
1372 assert_eq!(lied.len(), good.len(),
1373 "The tampered payload is a different length, so this would be testing the length \
1374 rather than the bit.");
1375 let mut carried = signed.clone();
1376 let start = carried.len() - good.len();
1377 carried[start..].copy_from_slice(&lied);
1378 match read_artefact(&carried) {
1379 Ok(_) => return Err(err!(
1380 "A share whose consent bit was set after signing was read, so the bit is \
1381 something its carrier can decide."; Test, Invalid)),
1382 Err(e) => assert!(fmt!("{}", e).contains("hashes to"),
1383 "The refusal is not the address's: {}", e),
1384 }
1385
1386 // And a share cannot be re-labelled as a post after signing, for the same reason a post
1387 // cannot be re-labelled as a card: the schema is inside the signing input.
1388 let bytes = res!(res!(match back.payload() {
1389 Payload::Share(s) => Ok(s.clone()),
1390 _ => Err(err!("Not a share."; Test, Bug)),
1391 }).encode());
1392 let mut env = res!(seal(&bytes, SCHEMA_SHARE, &signer, TIME));
1393 env.schema = SCHEMA_POST.to_string();
1394 match read_artefact(&res!(assemble(&env, &bytes))) {
1395 Ok(_) => Err(err!("A share re-labelled as a post was read."; Test, Invalid)),
1396 Err(_) => Ok(()),
1397 }
1398 }
1399
1400 /// The signing input begins with the schema's length, and each name gives a distinct preimage.
1401 ///
1402 /// The §18 property, and the reason a third schema name could be added without touching a byte
1403 /// of anything already signed: the schema reaches the signing input length-prefixed (§1.3), so
1404 /// a name that did not exist when a post was signed cannot change how that post's preimage is
1405 /// read.
1406 ///
1407 /// **What this test does and does not prove.** It proves the prefix is written, and written
1408 /// correctly, which is falsifiable: remove the prefix from `signing_input` and this fails. It
1409 /// does NOT exhibit a collision, and an honest note about that is worth more than a test that
1410 /// pretends to. In v0 the schema and the hash are not adjacent — the two scheme ids and the
1411 /// time sit between them — and a schema is a `String`, so for a schema to swallow the bytes
1412 /// that follow it, those bytes would have to be valid UTF-8. The v0 signature scheme id begins
1413 /// `0xF5`, which is not. So the ambiguity is unreachable in v0 by accident of the constants,
1414 /// and the prefix is what makes it unreachable by design: change a scheme id, add a schema
1415 /// whose name is a prefix of another, or move a field, and the accident evaporates while the
1416 /// prefix does not.
1417 #[test]
1418 fn test_the_schema_is_length_prefixed_23() -> Outcome<()> {
1419 let env = |schema: &str| Envelope {
1420 schema: schema.to_string(),
1421 author: vec![0u8; 32],
1422 sig_scheme: envelope::SIG_SCHEME_ED25519,
1423 hash_scheme: envelope::HASH_SCHEME_SHA3_256,
1424 time: TIME,
1425 hash: vec![b'A'; 32],
1426 sig: Vec::new(),
1427 tree_len: 1,
1428 };
1429 for schema in [SCHEMA_POST, SCHEMA_CARD, crate::SCHEMA_SHARE, crate::SCHEMA_DOC] {
1430 let input = env(schema).signing_input();
1431 assert!(input.len() > 4, "The signing input of '{}' is too short to hold a length.",
1432 schema);
1433 let declared = u32::from_be_bytes([input[0], input[1], input[2], input[3]]);
1434 assert_eq!(declared as usize, schema.len(),
1435 "The signing input of '{}' does not begin with the schema's length. Without it, \
1436 `schema` and `hash` are two variable-length fields with only fixed-width ones \
1437 between them, and where a field ends stops being a fact about the bytes.", schema);
1438 assert_eq!(&input[4..4 + schema.len()], schema.as_bytes(),
1439 "The schema does not follow its own length.");
1440 }
1441
1442 // And the reserved name is a distinct preimage from the two that are implemented, so an
1443 // artefact of a schema that does not exist yet cannot be read as one that does.
1444 let post = env(SCHEMA_POST).signing_input();
1445 for other in [SCHEMA_CARD, crate::SCHEMA_SHARE, crate::SCHEMA_DOC] {
1446 assert_ne!(post, env(other).signing_input());
1447 }
1448 Ok(())
1449 }
1450
1451 /// An author key of the wrong width is refused before an artefact is built around it.
1452 #[test]
1453 fn test_an_author_key_has_one_width_21() -> Outcome<()> {
1454 let bytes = res!(sample_post().encode());
1455 match envelope_for(&bytes, SCHEMA_POST, &[0u8; 31], TIME) {
1456 Ok(_) => Err(err!("A 31 byte author key was accepted."; Test, Invalid)),
1457 Err(e) => {
1458 assert!(fmt!("{}", e).contains("31 bytes"));
1459 Ok(())
1460 },
1461 }
1462 }
1463}