Oregami
Repositories/oxedyne/ore

oxedyne/ore/store/src/keys.rs

31.5 KiB, 30 runs

created by r2848102244:155, 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//! Key material, and what a signature is worth once it checks out.
2//!
3//! The engine holds the cryptography: [`Envelope`] seals a whole record --
4//! the operation, the identifier that names it and the parents that place it --
5//! so an operation lifted out, relabelled or re-parented does not verify. What
6//! the engine does not do is choose an algorithm, keep a key or decide who is
7//! trusted. Those three are this module's business.
8//!
9//! # The scheme
10//!
11//! Ed25519, from `oxedyne_fe2o3_crypto`. It is pure Rust, so the tool builds
12//! wherever Rust does and not only where a C toolchain does; its keys are 32
13//! bytes and its signatures 64, which is small enough that sealing every
14//! operation costs little beside the operation itself; and it is the scheme the
15//! rest of Hematite reaches for first. The name is written into `.ore/key`, so
16//! a repository says which scheme its key belongs to rather than leaving a
17//! reader to infer it from a length.
18//!
19//! # The key file is not encrypted
20//!
21//! `.ore/key` holds the secret key in the clear. Anyone who can read the file
22//! can author operations that verify as this replica, so the file is written
23//! with mode 0600 and says as much in its own comment field. Passphrase
24//! protection is future work; until it exists, `.ore` must not be
25//! world-readable and a repository must not be copied anywhere its owner would
26//! not put a private key.
27//!
28//! # What trust means here, stated plainly
29//!
30//! There is no certificate authority, no web of trust and no revocation. A
31//! public key becomes known one of two ways: this repository minted it, or a
32//! sync with another repository handed it over and it was written down. That is
33//! trust on first use and nothing more. It gives one property, which is worth
34//! having: once a key is known, everything signed by it is attributable and
35//! nothing signed by anyone else can be passed off as its work. It gives no
36//! protection against a first meeting with an impostor.
37//!
38//! A binding records which replica published a key, not which replica each
39//! operation it signed was labelled with. The two differ where a repository
40//! authors operations on somebody else's behalf, which is exactly what `ore
41//! import` does: a git history's operations carry each author's replica and are
42//! sealed by the key of whoever ran the import, because that person is who
43//! actually vouches for the bytes.
44//!
45//! # A binding certifies itself
46//!
47//! A binding carries a signature over its own statement, made by the very key it
48//! binds. That matters the moment a key travels by a route neither end wrote:
49//! through a relay, a puller receives operations sealed by keys it has never
50//! seen, and the natural source of the binding would be the relay -- which would
51//! quietly make the relay a trusted introducer. A self-certified binding cannot
52//! be minted by a carrier, because a fabricated one fails its own signature, so
53//! the worst a carrier can do is withhold one and leave the operations it would
54//! have explained marked [`Prov::Unknown`].
55//!
56//! What self-certification does not fix is the trust-on-first-use risk above: a
57//! key that signs its own claim to be replica 7 proves control of the key and
58//! nothing about the person holding it.
59
60use crate::store::Keep;
61
62use oxedyne_fe2o3_core::prelude::*;
63use oxedyne_fe2o3_crypto::sign::SignatureScheme;
64use oxedyne_fe2o3_iop_crypto::keys::KeyManager;
65use oxedyne_fe2o3_iop_crypto::sign::Signer;
66use oxedyne_fe2o3_jdat::prelude::*;
67use oxedyne_fe2o3_ore::envelope::Envelope;
68use oxedyne_fe2o3_ore::id::{
69 OpId,
70 ReplicaId,
71};
72use oxedyne_fe2o3_ore::op::Record;
73use oxedyne_fe2o3_ore::segment::Entry;
74use oxedyne_fe2o3_text::base2x::HEMATITE64;
75
76use std::collections::BTreeMap;
77use std::fs;
78use std::path::{
79 Path,
80 PathBuf,
81};
82
83
84/// Name of the key file, within the `.ore` directory.
85pub const KEY_FILE: &str = "key";
86
87/// Version of the key file this tool writes.
88pub const KEY_VERSION: u64 = 1;
89
90/// The name the key file records the signature scheme under.
91pub const SCHEME: &str = "Ed25519";
92
93/// What the key file says about itself, so that a person who opens it is told
94/// what they are holding.
95pub const KEY_COMMENT: &str = "This secret key is NOT encrypted. Anyone who can read this file \
96 can author operations that verify as this replica, so the file is written readable only by \
97 its owner (mode 0600) and .ore must not be made world readable. Passphrase protection is \
98 future work.";
99
100/// The legend that goes with the provenance marks, written once per listing.
101pub const LEGEND: &str = "provenance + signed and verified, ? signed by a key this repository \
102 does not know, - unsigned";
103
104
105/// Writes bytes as text, in the encoding the rest of Hematite writes bytes in.
106pub fn text_of(bytes: &[u8]) -> String {
107 HEMATITE64.to_string(bytes)
108}
109
110/// Reads bytes back from [`text_of`].
111pub fn bytes_of(text: &str)
112 -> Outcome<Vec<u8>>
113{
114 HEMATITE64.from_str(text)
115}
116
117
118/// What is known about the provenance of one operation.
119///
120/// The three are exhaustive and none of them is "refused": an entry whose
121/// signature does not check out never reaches a value of this type, because
122/// [`check`] fails instead.
123#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
124pub enum Prov {
125 /// Sealed, the signature checks out, and the key is one this repository
126 /// knows.
127 Verified,
128 /// Sealed and the signature checks out, but nobody here has seen the key
129 /// before. The operation is attributable to whoever holds that key, and this
130 /// repository cannot say who that is.
131 Unknown,
132 /// Written down bare, with no provenance to check.
133 Bare,
134}
135
136impl Prov {
137 /// Returns the one character that stands for the state in a listing.
138 pub fn mark(&self) -> char {
139 match self {
140 Self::Verified => '+',
141 Self::Unknown => '?',
142 Self::Bare => '-',
143 }
144 }
145}
146
147
148/// What a key signs to say which replica it belongs to.
149///
150/// Part of the format: a change here invalidates every binding already written
151/// down, so the tag carries a version of its own.
152pub const BIND_TAG: &str = "ORE-BIND-1";
153
154/// Returns the bytes a key signs to bind itself to a replica.
155///
156/// The replica is written in decimal and the key follows its own line feed, so
157/// no run of bytes can be read as two different statements.
158pub fn bind_statement(replica: u64, public: &[u8]) -> Vec<u8> {
159 let mut out = fmt!("{}\n{}\n", BIND_TAG, replica).into_bytes();
160 out.extend_from_slice(public);
161 out
162}
163
164
165/// One public key, the replica that published it, and that key's own word for
166/// it.
167#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
168pub struct Binding {
169 /// The replica that minted the key.
170 pub replica: u64,
171 /// The public key, as the scheme encodes it.
172 pub public: Vec<u8>,
173 /// The signature over [`bind_statement`], made by the secret half of
174 /// `public`. Absent in a binding written before bindings certified
175 /// themselves, and in one a carrier stripped.
176 pub sig: Option<Vec<u8>>,
177}
178
179impl Binding {
180
181 /// Reports whether the binding is signed by the key it binds.
182 ///
183 /// A binding with no signature is not a failure -- it is what a repository
184 /// made before this existed holds, and it still names a key this repository
185 /// will accept a signature from. It is simply not something to pass on to a
186 /// stranger, since nothing in it proves the replica did not misspeak.
187 pub fn is_certified(&self) -> bool {
188 match &self.sig {
189 None => false,
190 Some(sig) => {
191 let scheme = match algorithm().clone_with_keys(Some(&self.public), None) {
192 Ok(s) => s,
193 Err(_) => return false,
194 };
195 match scheme.verify(&bind_statement(self.replica, &self.public), sig) {
196 Ok(ok) => ok,
197 Err(_) => false,
198 }
199 },
200 }
201 }
202
203 /// Serialises the binding to a [`Dat`]. The shape is `[replica, key]`, with
204 /// the self-signature appended where there is one.
205 pub fn to_dat(&self) -> Dat {
206 let mut v = vec![
207 Dat::U64(self.replica),
208 Dat::Str(text_of(&self.public)),
209 ];
210 if let Some(sig) = &self.sig {
211 v.push(Dat::Str(text_of(sig)));
212 }
213 Dat::List(v)
214 }
215
216 /// Reconstructs a binding from a [`Dat`].
217 pub fn from_dat(dat: &Dat)
218 -> Outcome<Self>
219 {
220 let v = match dat {
221 Dat::List(v) if v.len() == 2 || v.len() == 3 => v,
222 other => return Err(err!(
223 "A key binding expects a replica and a key, and may carry the key's \
224 signature over both; got {:?}.", other;
225 Decode, Input, Mismatch)),
226 };
227 let replica = match &v[0] {
228 Dat::U64(n) => *n,
229 Dat::U32(n) => *n as u64,
230 other => return Err(err!(
231 "A key binding's replica expects a number, got {:?}.", other;
232 Decode, Input, Mismatch)),
233 };
234 let public = match &v[1] {
235 Dat::Str(s) => res!(bytes_of(s)),
236 other => return Err(err!(
237 "A key binding's public key expects a string, got {:?}.", other;
238 Decode, Input, Mismatch)),
239 };
240 let sig = match v.get(2) {
241 None => None,
242 Some(Dat::Str(s)) => Some(res!(bytes_of(s))),
243 Some(other) => return Err(err!(
244 "A key binding's signature expects a string, got {:?}.", other;
245 Decode, Input, Mismatch)),
246 };
247 Ok(Self { replica, public, sig })
248 }
249}
250
251
252/// Every public key this repository will accept a signature from.
253///
254/// It is a set of keys and not a mapping from replica to key, because that is
255/// what verification actually asks: an envelope carries the key it was signed
256/// with, and the question is whether this repository has seen that key before.
257/// Which replica published it is kept for the account a person reads, not for
258/// the check.
259#[derive(Clone, Debug, Default)]
260pub struct Trust {
261 /// Public key to the replica that published it.
262 known: BTreeMap<Vec<u8>, u64>,
263}
264
265impl Trust {
266
267 /// Builds the trust set from the bindings a configuration holds.
268 pub fn of(bindings: &[Binding]) -> Self {
269 let mut known = BTreeMap::new();
270 for b in bindings {
271 known.insert(b.public.clone(), b.replica);
272 }
273 Self { known }
274 }
275
276 /// Reports whether the key is one this repository knows.
277 pub fn knows(&self, public: &[u8]) -> bool {
278 self.known.contains_key(public)
279 }
280}
281
282
283/// Reads a public key from the text form, refusing one the scheme will not take.
284///
285/// A typed key that lost a character, or a shell that expanded an empty
286/// variable, would otherwise be written down as an owner nobody can ever be.
287pub fn public_of(text: &str)
288 -> Outcome<Vec<u8>>
289{
290 let bytes = res!(bytes_of(text.trim()));
291 match algorithm().clone_with_keys(Some(&bytes), None) {
292 Ok(_) => Ok(bytes),
293 Err(e) => Err(err!(e,
294 "{:?} is {} bytes, which {} does not accept as a public key.",
295 text, bytes.len(), SCHEME;
296 Invalid, Input, Key)),
297 }
298}
299
300
301/// The algorithm alone, holding no keys.
302///
303/// [`Envelope::verify`] sets a scheme's own keys aside and uses the one the
304/// envelope carries, so what is wanted here is the algorithm and nothing else.
305pub fn algorithm() -> SignatureScheme {
306 SignatureScheme::empty_ed25519()
307}
308
309
310/// This replica's key pair, as `.ore/key` holds it.
311#[derive(Clone, Debug)]
312pub struct Signing {
313 /// The scheme, holding both keys.
314 scheme: SignatureScheme,
315 /// The public key, kept beside the scheme because it is asked for often.
316 public: Vec<u8>,
317 /// The replica the key was minted for.
318 pub replica: ReplicaId,
319}
320
321impl Signing {
322
323 /// Returns the path of the key file within the `.ore` directory `dir`.
324 pub fn path_of(dir: &Path) -> PathBuf {
325 dir.join(KEY_FILE)
326 }
327
328 /// Mints a fresh key pair for a replica.
329 pub fn mint(replica: ReplicaId)
330 -> Outcome<Self>
331 {
332 let scheme = SignatureScheme::new_ed25519();
333 let public = match res!(scheme.get_public_key()) {
334 Some(pk) => pk.to_vec(),
335 None => return Err(err!(
336 "A freshly minted {} key pair has no public key, which cannot happen \
337 and means the scheme has changed under this tool.", SCHEME;
338 Bug, Missing, Key)),
339 };
340 Ok(Self { scheme, public, replica })
341 }
342
343 /// Returns the public key in the form the configuration writes it and `ore
344 /// key` prints it.
345 pub fn public_text(&self) -> String {
346 text_of(&self.public)
347 }
348
349 /// Returns the binding this key adds to a configuration, signed by the key
350 /// itself so that it can be handed to a stranger.
351 ///
352 /// A key that cannot sign its own statement still binds -- the signature is
353 /// what lets the binding travel, not what makes it true here -- so the
354 /// failure is dropped rather than raised.
355 pub fn binding(&self) -> Binding {
356 let statement = bind_statement(self.replica.inner(), &self.public);
357 Binding {
358 replica: self.replica.inner(),
359 public: self.public.clone(),
360 sig: self.scheme.sign(&statement).ok(),
361 }
362 }
363
364 /// Seals a record, so that what is written down carries who wrote it.
365 pub fn seal(&self, rec: &Record)
366 -> Outcome<Envelope>
367 {
368 Envelope::seal_record(&self.scheme, rec)
369 }
370
371 /// Signs arbitrary bytes with this replica's key.
372 ///
373 /// For statements that are not operations: a request to a relay, and a
374 /// binding's word for which replica the key belongs to. An operation is
375 /// sealed by [`Signing::seal`] instead, which covers the identifier and the
376 /// parents along with the edit.
377 pub fn sign(&self, msg: &[u8])
378 -> Outcome<Vec<u8>>
379 {
380 self.scheme.sign(msg)
381 }
382
383 /// Returns the public key, as the scheme encodes it.
384 pub fn public(&self) -> &[u8] {
385 &self.public
386 }
387
388 /// Reads the key file, which a repository is not obliged to have.
389 ///
390 /// A repository with no key file authors bare records and is none the worse
391 /// for it, so absence is `None` rather than an error; a file that is there
392 /// and will not read is an error, because the alternative is quietly
393 /// authoring unsigned operations in a repository that means to sign.
394 pub fn read(dir: &Path)
395 -> Outcome<Option<Self>>
396 {
397 let path = Self::path_of(dir);
398 if !path.is_file() {
399 return Ok(None);
400 }
401 let text = match fs::read_to_string(&path) {
402 Ok(t) => t,
403 Err(e) => return Err(err!(e,
404 "The key {:?} could not be read.", path;
405 IO, File, Read)),
406 };
407 let dat = match Dat::decode_string(text) {
408 Ok(d) => d,
409 Err(e) => return Err(err!(e,
410 "The key {:?} is not readable JDAT.", path;
411 Decode, Input)),
412 };
413 let map = match &dat {
414 Dat::Map(m) => m,
415 other => return Err(err!(
416 "The key {:?} expects a map, got {:?}.", path, other;
417 Decode, Input, Mismatch)),
418 };
419 let version = res!(field_u64(map, "format"));
420 if version != KEY_VERSION {
421 return Err(err!(
422 "The key {:?} declares format version {}, and this tool knows only \
423 version {}.", path, version, KEY_VERSION;
424 Decode, Input, Version, Mismatch));
425 }
426 let named = res!(field_str(map, "scheme"));
427 if named != SCHEME {
428 return Err(err!(
429 "The key {:?} is a {} key, and this tool signs with {}.",
430 path, named, SCHEME;
431 Invalid, Input, Mismatch));
432 }
433 let replica = ReplicaId::new(res!(field_u64(map, "replica")));
434 let public = res!(bytes_of(&res!(field_str(map, "public"))));
435 let secret = res!(bytes_of(&res!(field_str(map, "secret"))));
436 let scheme = match algorithm().clone_with_keys(Some(&public), Some(&secret)) {
437 Ok(s) => s,
438 Err(e) => return Err(err!(e,
439 "The key {:?} holds a {} byte public key and a {} byte secret key, \
440 which {} does not accept.", path, public.len(), secret.len(), SCHEME;
441 Invalid, Input, Key)),
442 };
443 Ok(Some(Self { scheme, public, replica }))
444 }
445
446 /// Writes the key file, readable only by its owner.
447 ///
448 /// The mode is set before anything is written, so the secret never sits on
449 /// disk under a wider one, and the file is replaced rather than appended to.
450 pub fn write(&self, dir: &Path)
451 -> Outcome<PathBuf>
452 {
453 let secret = match res!(self.scheme.get_secret_key()) {
454 Some(sk) => sk.to_vec(),
455 None => return Err(err!(
456 "This key pair holds no secret key, so there is nothing to write.";
457 Missing, Key)),
458 };
459 let mut map = DaticleMap::new();
460 map.insert(Dat::Str(fmt!("comment")), Dat::Str(fmt!("{}", KEY_COMMENT)));
461 map.insert(Dat::Str(fmt!("format")), Dat::U64(KEY_VERSION));
462 map.insert(Dat::Str(fmt!("scheme")), Dat::Str(fmt!("{}", SCHEME)));
463 map.insert(Dat::Str(fmt!("replica")), Dat::U64(self.replica.inner()));
464 map.insert(Dat::Str(fmt!("public")), Dat::Str(text_of(&self.public)));
465 map.insert(Dat::Str(fmt!("secret")), Dat::Str(text_of(&secret)));
466 let text = res!(Dat::Map(map).jdat_to_lines(" "));
467 let path = Self::path_of(dir);
468 res!(write_private(&path, fmt!("{}\n", text).as_bytes()));
469 Ok(path)
470 }
471}
472
473
474/// Creates or replaces a file that only its owner may read.
475///
476/// The permissions are given at creation on a Unix, so there is no window in
477/// which the bytes are on disk under the process umask's mode. Elsewhere the
478/// file is written as any other and the caller is told, because silently
479/// writing a secret world readable is worse than saying so.
480pub(crate) fn write_private(path: &Path, bytes: &[u8])
481 -> Outcome<()>
482{
483 // A previous key is removed first: opening with create_new is what makes the
484 // mode apply to a file that did not exist a moment ago.
485 if path.exists() {
486 match fs::remove_file(path) {
487 Ok(()) => (),
488 Err(e) => return Err(err!(e,
489 "The key {:?} is there and could not be replaced.", path;
490 IO, File, Write)),
491 }
492 }
493 let mut opts = fs::OpenOptions::new();
494 opts.create_new(true).write(true);
495 #[cfg(unix)]
496 {
497 use std::os::unix::fs::OpenOptionsExt;
498 opts.mode(0o600);
499 }
500 #[cfg(not(unix))]
501 {
502 println!("note: {:?} holds an unencrypted secret key and this platform has no \
503 file mode for the tool to set; restrict it yourself", path);
504 }
505 let mut file = match opts.open(path) {
506 Ok(f) => f,
507 Err(e) => return Err(err!(e,
508 "The key {:?} could not be created.", path;
509 IO, File, Write)),
510 };
511 use std::io::Write;
512 match file.write_all(bytes) {
513 Ok(()) => (),
514 Err(e) => return Err(err!(e,
515 "The key {:?} could not be written.", path;
516 IO, File, Write)),
517 }
518 match file.flush() {
519 Ok(()) => Ok(()),
520 Err(e) => Err(err!(e,
521 "The key {:?} could not be flushed.", path;
522 IO, File, Write)),
523 }
524}
525
526/// Reads a numeric field of a key file map.
527fn field_u64(map: &DaticleMap, key: &str)
528 -> Outcome<u64>
529{
530 match map.get(&Dat::Str(fmt!("{}", key))) {
531 Some(Dat::U64(n)) => Ok(*n),
532 Some(Dat::U32(n)) => Ok(*n as u64),
533 Some(Dat::U16(n)) => Ok(*n as u64),
534 Some(Dat::U8(n)) => Ok(*n as u64),
535 Some(other) => Err(err!(
536 "The key file field {:?} expects a number, got {:?}.", key, other;
537 Decode, Input, Mismatch)),
538 None => Err(err!(
539 "The key file has no field {:?}.", key;
540 Decode, Input, Missing)),
541 }
542}
543
544/// Reads a string field of a key file map.
545fn field_str(map: &DaticleMap, key: &str)
546 -> Outcome<String>
547{
548 match map.get(&Dat::Str(fmt!("{}", key))) {
549 Some(Dat::Str(s)) => Ok(s.clone()),
550 Some(other) => Err(err!(
551 "The key file field {:?} expects a string, got {:?}.", key, other;
552 Decode, Input, Mismatch)),
553 None => Err(err!(
554 "The key file has no field {:?}.", key;
555 Decode, Input, Missing)),
556 }
557}
558
559
560/// What one entry turned out to be.
561pub struct Checked {
562 pub rec: Record, // the operation it carries
563 pub prov: Prov, // what is known about who wrote it
564 pub env: Option<Envelope>, // the seal, where one was kept; see `Keep`
565}
566
567/// Verifies an entry, and says what it is worth.
568///
569/// A sealed entry whose signature does not check out is refused here and named,
570/// and `where` says where it was found -- a segment file, or the replica that
571/// sent it. Nothing about it is placed, dropped or ignored: the caller is told
572/// which operation is not what it claims and stops.
573///
574/// A signature that checks out under a key nobody here has seen is not a
575/// failure. It is attributable to whoever holds that key and this repository
576/// cannot say who that is, which is what [`Prov::Unknown`] means and what the
577/// `?` in a listing says.
578///
579/// A veiled entry is refused too, and this is where a working copy meets one: a
580/// veil is put on for a carrier and taken off on arrival, so an entry that is
581/// still veiled by the time it is being checked is one nothing here can read.
582/// Saying so by name is the point -- the alternative is a repository quietly
583/// holding operations it will never be able to render.
584pub fn check(entry: &Entry, trust: &Trust, whence: &str)
585 -> Outcome<Checked>
586{
587 match entry {
588 Entry::Bare(rec) => Ok(Checked {
589 rec: rec.clone(),
590 prov: Prov::Bare,
591 env: None,
592 }),
593 Entry::Veiled(v) => Err(err!(
594 "The operation {} in {} is veiled, and this repository holds no content \
595 key to read it with. A veil comes off where it arrives; one still on here \
596 means the key never did. Run `ore key --veil <key>` with the key the \
597 repository was veiled under, which is handed over by whoever holds it and \
598 never by a relay.", v.head.id(), whence;
599 Invalid, Input, Missing, Key)),
600 Entry::Sealed(env) => {
601 // Peeked first so that a failure can name the operation rather than
602 // leaving a reader to work out which of them it was.
603 let rec = res!(peeked(env, whence));
604 let scheme = algorithm();
605 let ok = match env.verify(&scheme) {
606 Ok(v) => v,
607 Err(e) => return Err(err!(e,
608 "The signature on {} in {} could not be checked: its {} byte public \
609 key and {} byte signature are not a {} pair. The operation is \
610 refused; it is not what it claims to be.",
611 rec.id(), whence, env.signer().len(), env.signature().len(), SCHEME;
612 Invalid, Input, Security, Mismatch)),
613 };
614 if !ok {
615 return Err(err!(
616 "The signature on {} in {} does not verify against the public key it \
617 carries. The operation has been altered since it was signed, or it \
618 was never signed by the holder of that key. It is refused rather \
619 than being taken on trust.", rec.id(), whence;
620 Invalid, Input, Security, Mismatch));
621 }
622 let prov = if trust.knows(env.signer()) {
623 Prov::Verified
624 } else {
625 Prov::Unknown
626 };
627 Ok(Checked {
628 rec,
629 prov,
630 env: Some(env.clone()),
631 })
632 },
633 }
634}
635
636
637/// Opens a sealed entry's record without checking its signature, naming where it
638/// was found if the payload is not a record at all.
639///
640/// Shared by [`check`] and [`check_all`] so that the two report a payload that
641/// is not a record in the same words, whichever of them met it.
642fn peeked(env: &Envelope, whence: &str)
643 -> Outcome<Record>
644{
645 match env.peek_record() {
646 Ok(r) => Ok(r),
647 Err(e) => Err(err!(e,
648 "A sealed entry in {} carries {} bytes that are not a record.",
649 whence, env.payload().len();
650 Invalid, Input, Decode)),
651 }
652}
653
654/// Verifies a run of entries together, and says what each is worth.
655///
656/// What [`check`] does one entry at a time, for a run of them. The result is the
657/// same: every entry in the same order, refusing the whole run and naming the
658/// operation if any signature does not hold.
659///
660/// # Why the signatures are checked together
661///
662/// Ed25519 can check a set of signatures for far less than the sum of the parts,
663/// and it need decompress a signer's public key only once however many times
664/// that signer appears. Replaying a repository means verifying every operation
665/// in it, which is the largest single cost of opening one, so the saving is the
666/// whole point of this function existing beside [`check`].
667///
668/// # A failure is still named
669///
670/// A batch that does not hold says only that something in it is wrong; it never
671/// says what, because it never checked the members separately. So the batch is
672/// used to answer "is everything here sound?", and the moment the answer is no
673/// -- or the moment the batch could not be attempted at all -- every entry is
674/// put to [`check`] one at a time, which names the operation and the file it was
675/// found in exactly as it always did. Nothing is dropped and nothing is taken on
676/// trust: the run is refused either way, and the only question the fallback
677/// answers is which operation to name.
678///
679/// `keep` decides whether the envelopes come back with the verdicts. [`check`]
680/// has no such argument because it answers about one entry, and one envelope is
681/// not what this was costing.
682pub fn check_all(entries: &[Entry], trust: &Trust, whence: &str, keep: Keep)
683 -> Outcome<Vec<Checked>>
684{
685 let scheme = algorithm();
686 let sealed: Vec<&Envelope> = entries.iter()
687 .filter_map(|entry| match entry {
688 Entry::Sealed(env) => Some(env),
689 // Nothing to check: the signature is inside the ciphertext, and the
690 // loop below refuses the entry by name for having got this far.
691 Entry::Bare(_) => None,
692 Entry::Veiled(_) => None,
693 })
694 .collect();
695 // An error is treated as the batch not holding, rather than raised here,
696 // because an error raised here could not say which envelope caused it. The
697 // fallback below meets the same bytes one at a time and reports it properly.
698 let held = match Envelope::verify_all(&scheme, &sealed) {
699 Ok(held) => held,
700 Err(_) => false,
701 };
702 if !held {
703 for entry in entries {
704 res!(check(entry, trust, whence));
705 }
706 // Every signature checked out on its own, which cannot follow from a
707 // batch that did not hold. Something is wrong with the check itself, and
708 // the entries are refused rather than accepted on the strength of the
709 // weaker of two disagreeing answers.
710 return Err(err!(
711 "The signatures on the {} entries in {} did not hold when they were \
712 checked together, and checking them one at a time found nothing wrong \
713 with any of them. The two checks disagree, which they cannot, so the \
714 entries are refused rather than accepted on the strength of one of them.",
715 entries.len(), whence;
716 Bug, Invalid, Security, Mismatch));
717 }
718 attribute_all(entries, trust, whence, keep)
719}
720
721
722/// Says what each of a run of entries is worth, checking no signature.
723///
724/// The tail of [`check_all`], and it is reachable on its own for one caller and
725/// one reason: [`crate::verdict`] records that a run of bytes verified here,
726/// under a digest of those exact bytes, and a signature that verified over bytes
727/// that have not changed verifies again. Checking it a second time is
728/// re-deriving a constant.
729///
730/// **An entry may be brought here only where its bytes carry such a verdict.**
731/// Everything this returns says the operation is attributable, and nothing in
732/// here establishes that; the digest does, at the caller.
733///
734/// Nothing else about an entry is taken on trust. A veiled entry is still
735/// refused by name, and a signer nobody here has seen is still
736/// [`Prov::Unknown`]: neither answer was ever the signature's to give.
737pub(crate) fn attribute_all(entries: &[Entry], trust: &Trust, whence: &str, keep: Keep)
738 -> Outcome<Vec<Checked>>
739{
740 let mut out = Vec::with_capacity(entries.len());
741 for entry in entries {
742 match entry {
743 Entry::Bare(rec) => out.push(Checked {
744 rec: rec.clone(),
745 prov: Prov::Bare,
746 env: None,
747 }),
748 // Named one at a time, in the words `check` uses, so a run holding one
749 // is refused for the reason it is refused and not for the batch.
750 Entry::Veiled(_) => { res!(check(entry, trust, whence)); },
751 Entry::Sealed(env) => {
752 let rec = res!(peeked(env, whence));
753 let prov = if trust.knows(env.signer()) {
754 Prov::Verified
755 } else {
756 Prov::Unknown
757 };
758 // An envelope holds the whole encoded record a second time, so a
759 // caller that will drop it is not made a copy of it. This is the
760 // same `Keep` the replay carries, asked one batch earlier: 47.8 MB
761 // over a 44,629 operation history, and 2,000 envelopes of it live
762 // at once inside a batch.
763 let env = match keep {
764 Keep::Envelopes => Some(env.clone()),
765 Keep::Nothing => None,
766 };
767 out.push(Checked {
768 rec,
769 prov,
770 env,
771 });
772 },
773 }
774 }
775 Ok(out)
776}
777
778
779/// What a listing needs to put a mark against an operation.
780///
781/// An operation the map does not name is one the log holds and nothing was
782/// recorded about, which happens where a record reached the log without passing
783/// through a segment; it is reported as bare, because bare is what a reader can
784/// safely assume about an operation nothing is known of.
785pub fn mark_of(prov: &BTreeMap<OpId, Prov>, id: OpId) -> char {
786 prov.get(&id).copied().unwrap_or(Prov::Bare).mark()
787}
788
789
790#[cfg(test)]
791mod tests {
792 use super::*;
793
794 /// A caller that will drop the envelopes is not made copies of them, and one
795 /// that will hand them on still gets them.
796 ///
797 /// The verdict is the same either way and the seal is the only difference,
798 /// which is the assertion: `prov` is read on both, so a change that lost the
799 /// checking rather than the copy would fail here too.
800 #[test]
801 fn what_will_be_dropped_is_not_copied() -> Outcome<()> {
802 use oxedyne_fe2o3_ore::op::{
803 Header,
804 Op,
805 };
806
807 let key = res!(Signing::mint(ReplicaId::new(1)));
808 let trust = Trust::of(&[key.binding()]);
809 let mut entries = Vec::new();
810 let mut parents = Vec::new();
811 for i in 1..=4u64 {
812 let rec = Record::new(
813 res!(Header::new(OpId::new(ReplicaId::new(1), i), parents.clone())),
814 Op::Mark { name: fmt!("mark {}", i), body: None, time: None },
815 );
816 parents = vec![OpId::new(ReplicaId::new(1), i)];
817 entries.push(Entry::Sealed(res!(key.seal(&rec))));
818 }
819
820 let kept = res!(check_all(&entries, &trust, "a test", Keep::Envelopes));
821 assert_eq!(kept.len(), 4);
822 assert!(kept.iter().all(|c| c.env.is_some()), "a caller that hands them on gets them");
823 assert!(kept.iter().all(|c| c.prov == Prov::Verified));
824
825 let dropped = res!(check_all(&entries, &trust, "a test", Keep::Nothing));
826 assert_eq!(dropped.len(), 4);
827 assert!(dropped.iter().all(|c| c.env.is_none()), "and a caller that will not, does not");
828 assert!(dropped.iter().all(|c| c.prov == Prov::Verified),
829 "while the checking is the same either way");
830 let said: Vec<OpId> = dropped.iter().map(|c| c.rec.id()).collect();
831 assert_eq!(said, kept.iter().map(|c| c.rec.id()).collect::<Vec<OpId>>(),
832 "and so are the operations");
833 Ok(())
834 }
835
836 /// A binding signed by the key it binds says so, and every alteration of it
837 /// stops saying so.
838 ///
839 /// This is what stops a carrier introducing a stranger. A relay that carries
840 /// bindings can withhold one, and cannot write one: the statement is signed
841 /// by the secret half of the very key it names, which the carrier does not
842 /// hold.
843 #[test]
844 fn a_binding_certifies_itself_and_nothing_else() -> Outcome<()> {
845 let key = res!(Signing::mint(ReplicaId::new(7)));
846 let binding = key.binding();
847 assert_eq!(binding.replica, 7);
848 assert!(binding.is_certified(), "a minted binding is signed by its own key");
849
850 // Claiming another replica breaks it: the statement covers the number.
851 let mut lying = binding.clone();
852 lying.replica = 8;
853 assert!(!lying.is_certified(), "a relabelled binding does not certify itself");
854
855 // So does putting the signature beside another key.
856 let other = res!(Signing::mint(ReplicaId::new(9)));
857 let mut swapped = other.binding();
858 swapped.sig = binding.sig.clone();
859 assert!(!swapped.is_certified(), "a signature does not travel to another key");
860
861 // And a binding with no signature at all is not one to pass on.
862 let mut bare = binding.clone();
863 bare.sig = None;
864 assert!(!bare.is_certified());
865
866 // A tampered signature fails rather than raising.
867 let mut broken = binding.clone();
868 if let Some(sig) = &mut broken.sig {
869 let last = sig.len() - 1;
870 sig[last] ^= 0x01;
871 }
872 assert!(!broken.is_certified());
873
874 // It survives the round trip through its file form, signature and all.
875 let back = res!(Binding::from_dat(&binding.to_dat()));
876 assert_eq!(back, binding);
877 assert!(back.is_certified());
878 // As does one written before bindings certified themselves.
879 let back = res!(Binding::from_dat(&bare.to_dat()));
880 assert_eq!(back, bare);
881 Ok(())
882 }
883
884 /// A public key of the wrong length is refused where it is read, rather than
885 /// written down as an owner nobody can ever be.
886 #[test]
887 fn a_public_key_that_is_not_one_is_refused() -> Outcome<()> {
888 let key = res!(Signing::mint(ReplicaId::new(1)));
889 let text = key.public_text();
890 assert_eq!(res!(public_of(&text)), key.public().to_vec());
891 assert_eq!(res!(public_of(&fmt!(" {} ", text))), key.public().to_vec());
892 for bad in ["", "AAAA=2"] {
893 if public_of(bad).is_ok() {
894 return Err(err!("The key {:?} was accepted.", bad; Test, Invalid));
895 }
896 }
897 Ok(())
898 }
899}