Oregami
Repositories/oxedyne/ore

oxedyne/ore/store/src/veilkey.rs

26.9 KiB, 1 run

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

download · who wrote it · its history

1//! The key that receives a wrapped content key, and the wrap itself.
2//!
3//! A veiled repository is locked by one AES-256-GCM content key ([`crate::veil`])
4//! and, until this existed, a second replica joined by somebody reading
5//! thirty-two bytes off one screen and typing them into another. What is here is
6//! the other route: **the content key travels wrapped, one wrap per member,
7//! through the relay that is being kept from reading it**.
8//!
9//! # Two keys, and why the second one is not the first
10//!
11//! A replica already has a key: the Ed25519 pair in `.ore/key` that seals its
12//! operations. It could have received a wrap -- Ed25519 and X25519 are the same
13//! curve in two coordinate systems and `ed25519-dalek` ships the conversion --
14//! and it does not, because that library argues against this exact reuse in its
15//! own documentation, citing eprint 2021/509. So a replica holds a *veil key*
16//! beside its signing key: an X25519 pair in `.ore/veilkey`, whose only job is to
17//! receive wraps.
18//!
19//! **A veil key belongs to a replica, not to a person.** A person with three
20//! machines has three veil keys and takes three wraps, which costs a hundred odd
21//! bytes and keeps the property that a secret lives on exactly one machine, so
22//! losing a laptop revokes one thing rather than forcing a re-key. The cost, and
23//! it is a real one somebody will meet: **a relay's idea of who may read a
24//! repository is a list of replicas and not a list of people.** That is not a
25//! defect and it must not be fixed by putting a name in a wrap; a human readable
26//! membership list, if one is ever wanted, joins through the voice record and
27//! re-keys nothing.
28//!
29//! # The chain of two links
30//!
31//! An [`crate::keys::Binding`] certifies itself: the statement *this key belongs
32//! to replica 7* is signed by the very key it names, so a carrier can withhold
33//! one and cannot mint one. **An X25519 key cannot sign, so a [`VeilBinding`]
34//! cannot do that.** It is signed by the replica's Ed25519 key instead, and the
35//! result is a chain of two links, neither of which a carrier can forge:
36//!
37//! - a fabricated veil key fails the Ed25519 signature over it;
38//! - a fabricated Ed25519 binding fails its own signature.
39//!
40//! The residual risk is the one already accepted at `crate::keys`: a key that
41//! signs its own claim to be replica 7 proves control of the key and nothing
42//! about the person holding it. A second key kind does not widen it, because the
43//! second key never makes a claim of its own -- it is vouched for by the first.
44//!
45//! # A wrap travels in the open
46//!
47//! **The relay serves wraps to anybody who may pull, and this is not a leak.** A
48//! wrap is thirty-two bytes of content key encrypted to one X25519 public key,
49//! and it is useless to everybody except the holder of the matching secret, who
50//! is the one person it was made for. This sentence is here because a reviewer
51//! meeting a route that hands out key material will otherwise read it as a
52//! defect and close the hole that is the mechanism.
53//!
54//! What the wraps do disclose is how many replicas can read a repository, and
55//! which keys they are. The access list beside them already says that, so it is
56//! one more line in a list the design has conceded rather than a step across it.
57//!
58//! # What the ephemeral sender key does and does not buy
59//!
60//! Every wrap is made under a fresh sender key, so a member's leaked secret does
61//! not retrospectively open wraps an attacker only has a copy of. **It protects
62//! the wrap and not the repository.** A member whose secret leaks has every
63//! current wrap opened, because those wraps are sitting on the relay where the
64//! attacker can fetch them. Anybody describing this to a user must not let the
65//! phrase forward secrecy do more work than that.
66
67use crate::keys::{
68 Binding,
69 Signing,
70 bytes_of,
71 text_of,
72 write_private,
73};
74
75use oxedyne_fe2o3_core::prelude::*;
76use oxedyne_fe2o3_crypto::agree::AgreementScheme;
77use oxedyne_fe2o3_crypto::enc::EncryptionScheme;
78use oxedyne_fe2o3_iop_crypto::enc::Encrypter;
79use oxedyne_fe2o3_iop_crypto::kem::KeyExchanger;
80use oxedyne_fe2o3_iop_crypto::keys::KeyManager;
81use oxedyne_fe2o3_jdat::prelude::*;
82use oxedyne_fe2o3_ore::id::ReplicaId;
83
84use std::fs;
85use std::path::{
86 Path,
87 PathBuf,
88};
89
90
91/// Name of the veil key file, within the `.ore` directory.
92pub const VEIL_KEY_FILE: &str = "veilkey";
93
94/// Version of the veil key file this tool writes.
95pub const VEIL_KEY_VERSION: u64 = 1;
96
97/// The name the veil key file records its scheme under.
98pub const VEIL_KEY_SCHEME: &str = "X25519";
99
100/// Length of a veil public key, in bytes.
101pub const VEIL_PK_LEN: usize = AgreementScheme::X25519_PK_LEN;
102
103/// What a replica's Ed25519 key signs to say which veil key is its own.
104///
105/// Part of the format, exactly as [`crate::keys::BIND_TAG`] is: a change here
106/// invalidates every veil binding already written down, so the tag carries a
107/// version of its own.
108pub const VEIL_BIND_TAG: &str = "ORE-VEIL-1";
109
110/// What the veil key file says about itself.
111pub const VEIL_KEY_COMMENT: &str = "This veil key is NOT encrypted. Anyone who can read this \
112 file can open every wrap addressed to this replica, and so can read any repository this \
113 replica has been let into, so the file is written readable only by its owner (mode 0600). \
114 The public half is published; the secret half never leaves this machine.";
115
116
117/// Returns the bytes a replica's signing key signs to vouch for its veil key.
118///
119/// Shaped on [`crate::keys::bind_statement`]: the replica in decimal, the key
120/// after its own line feed, so no run of bytes can be read as two different
121/// statements. The tag differs, so nothing signed for one purpose verifies for
122/// the other.
123pub fn veil_bind_statement(replica: u64, public: &[u8]) -> Vec<u8> {
124 let mut out = fmt!("{}\n{}\n", VEIL_BIND_TAG, replica).into_bytes();
125 out.extend_from_slice(public);
126 out
127}
128
129
130/// One replica's veil key, the replica it belongs to, and its signing key's word
131/// for it.
132#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
133pub struct VeilBinding {
134 /// The replica the veil key belongs to.
135 pub replica: u64,
136 /// The veil key, as X25519 encodes it.
137 pub public: Vec<u8>,
138 /// The Ed25519 key that vouched for it, which is the first link of the chain.
139 pub signer: Vec<u8>,
140 /// The signature over [`veil_bind_statement`], made by `signer`.
141 pub sig: Vec<u8>,
142}
143
144impl VeilBinding {
145
146 /// Is the binding signed by the key it names as having vouched for it?
147 ///
148 /// The first of the two links, and the only one checkable from the binding
149 /// alone. It says the holder of `signer` made this statement; it says nothing
150 /// yet about whether `signer` is really the replica's key.
151 pub fn is_vouched(&self) -> bool {
152 let scheme = match crate::keys::algorithm().clone_with_keys(Some(&self.signer), None) {
153 Ok(s) => s,
154 Err(_) => return false,
155 };
156 use oxedyne_fe2o3_iop_crypto::sign::Signer;
157 match scheme.verify(&veil_bind_statement(self.replica, &self.public), &self.sig) {
158 Ok(ok) => ok,
159 Err(_) => false,
160 }
161 }
162
163 /// Do both links hold?
164 ///
165 /// The veil binding is signed by `signer`, and `signer` is the key of a
166 /// self-certified Ed25519 binding for the same replica. A carrier can forge
167 /// neither, which is what keeps it from being promoted to a trusted
168 /// introducer of reading keys, exactly as self-certification keeps it from
169 /// being one for signing keys.
170 pub fn is_chained(&self, bindings: &[Binding]) -> bool {
171 if !self.is_vouched() {
172 return false;
173 }
174 bindings.iter().any(|b|
175 b.replica == self.replica
176 && b.public == self.signer
177 && b.is_certified()
178 )
179 }
180
181 /// Serialises the binding to a [`Dat`], as `[replica, key, signer, signature]`.
182 pub fn to_dat(&self) -> Dat {
183 Dat::List(vec![
184 Dat::U64(self.replica),
185 Dat::Str(text_of(&self.public)),
186 Dat::Str(text_of(&self.signer)),
187 Dat::Str(text_of(&self.sig)),
188 ])
189 }
190
191 /// Reconstructs a veil binding from a [`Dat`].
192 pub fn from_dat(dat: &Dat)
193 -> Outcome<Self>
194 {
195 let v = match dat {
196 Dat::List(v) if v.len() == 4 => v,
197 other => return Err(err!(
198 "A veil binding expects a replica, a veil key, the signing key that \
199 vouched for it and that key's signature; got {:?}.", other;
200 Decode, Input, Mismatch)),
201 };
202 let replica = match &v[0] {
203 Dat::U64(n) => *n,
204 Dat::U32(n) => *n as u64,
205 other => return Err(err!(
206 "A veil binding's replica expects a number, got {:?}.", other;
207 Decode, Input, Mismatch)),
208 };
209 let text = |at: usize, what: &str| -> Outcome<Vec<u8>> {
210 match &v[at] {
211 Dat::Str(s) => bytes_of(s),
212 other => Err(err!(
213 "A veil binding's {} expects a string, got {:?}.", what, other;
214 Decode, Input, Mismatch)),
215 }
216 };
217 Ok(Self {
218 replica,
219 public: res!(text(1, "veil key")),
220 signer: res!(text(2, "signing key")),
221 sig: res!(text(3, "signature")),
222 })
223 }
224}
225
226
227/// One content key, encrypted to one veil key.
228#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
229pub struct Wrap {
230 /// The veil public key this wrap is addressed to, which is also how it is
231 /// found: a replica looks for the wrap whose address is its own key.
232 pub to: Vec<u8>,
233 /// The ephemeral public key minted for this wrap and no other.
234 pub eph: Vec<u8>,
235 /// The content key, encrypted under what `to` and `eph` agree on.
236 pub wrapped: Vec<u8>,
237}
238
239impl Wrap {
240
241 /// Wraps a content key for the holder of a veil key.
242 ///
243 /// The sender needs no veil key of their own: an ephemeral pair is minted for
244 /// this wrap alone and thrown away, and the recipient recovers the agreement
245 /// from the ephemeral public key carried beside the ciphertext.
246 pub fn mint(to: &[u8], content: &[u8])
247 -> Outcome<Self>
248 {
249 if to.len() != VEIL_PK_LEN {
250 return Err(err!(
251 "A veil key is {} bytes and this one is {}, so there is nothing to \
252 wrap a content key to.", VEIL_PK_LEN, to.len();
253 Invalid, Input, Key));
254 }
255 let addressed = res!(<[u8; VEIL_PK_LEN]>::try_from(to));
256 let (session, eph) = res!(AgreementScheme::empty_x25519()
257 .encap::<
258 {AgreementScheme::X25519_PK_LEN},
259 {AgreementScheme::X25519_SESSION_KEY_LEN},
260 {AgreementScheme::X25519_CIPHERTEXT_LEN},
261 >(addressed));
262 let cipher = res!(EncryptionScheme::new_aes_256_gcm_with_key(&session));
263 Ok(Self {
264 to: to.to_vec(),
265 eph: eph.to_vec(),
266 wrapped: res!(cipher.encrypt(content)),
267 })
268 }
269
270 /// Takes the content key out of a wrap addressed to this replica.
271 pub fn open(&self, key: &VeilKey)
272 -> Outcome<Vec<u8>>
273 {
274 if self.to != key.public() {
275 return Err(err!(
276 "This wrap is addressed to another veil key, so nothing here can open \
277 it. A wrap is opened only by the replica it was made for.";
278 Invalid, Input, Mismatch, Key));
279 }
280 let eph = res!(<[u8; AgreementScheme::X25519_CIPHERTEXT_LEN]>::try_from(&self.eph[..]));
281 let session = res!(key.scheme.decap::<
282 {AgreementScheme::X25519_SESSION_KEY_LEN},
283 {AgreementScheme::X25519_CIPHERTEXT_LEN},
284 >(eph));
285 let cipher = res!(EncryptionScheme::new_aes_256_gcm_with_key(&session));
286 match cipher.decrypt(&self.wrapped) {
287 Ok(content) => Ok(content),
288 Err(e) => Err(err!(e,
289 "The wrap addressed to this replica's veil key would not open. It is \
290 addressed correctly, so either the wrap has been altered since it was \
291 made or the key that made it agreed with something else.";
292 Invalid, Input, Security, Mismatch)),
293 }
294 }
295
296 /// Serialises the wrap to a [`Dat`], as a map of its three fields.
297 pub fn to_dat(&self) -> Dat {
298 let mut map = DaticleMap::new();
299 map.insert(Dat::Str(fmt!("to")), Dat::Str(text_of(&self.to)));
300 map.insert(Dat::Str(fmt!("eph")), Dat::Str(text_of(&self.eph)));
301 map.insert(Dat::Str(fmt!("wrapped")), Dat::Str(text_of(&self.wrapped)));
302 Dat::Map(map)
303 }
304
305 /// Reconstructs a wrap from a [`Dat`].
306 pub fn from_dat(dat: &Dat)
307 -> Outcome<Self>
308 {
309 let map = match dat {
310 Dat::Map(m) => m,
311 other => return Err(err!(
312 "A wrap expects a map of \"to\", \"eph\" and \"wrapped\"; got {:?}.", other;
313 Decode, Input, Mismatch)),
314 };
315 let field = |key: &str| -> Outcome<Vec<u8>> {
316 match map.get(&Dat::Str(fmt!("{}", key))) {
317 Some(Dat::Str(s)) => bytes_of(s),
318 Some(other) => Err(err!(
319 "A wrap's {:?} expects a string, got {:?}.", key, other;
320 Decode, Input, Mismatch)),
321 None => Err(err!(
322 "A wrap has no {:?}. A wrap is \"to\", \"eph\" and \"wrapped\".", key;
323 Decode, Input, Missing)),
324 }
325 };
326 Ok(Self {
327 to: res!(field("to")),
328 eph: res!(field("eph")),
329 wrapped: res!(field("wrapped")),
330 })
331 }
332}
333
334
335/// This replica's veil key, as `.ore/veilkey` holds it.
336#[derive(Clone, Debug)]
337pub struct VeilKey {
338 /// The scheme, holding both halves.
339 scheme: AgreementScheme,
340 /// The public half, kept beside the scheme because it is asked for often.
341 public: Vec<u8>,
342 /// The replica the key was minted for.
343 pub replica: ReplicaId,
344}
345
346impl VeilKey {
347
348 /// Returns the path of the veil key file within the `.ore` directory `dir`.
349 pub fn path_of(dir: &Path) -> PathBuf {
350 dir.join(VEIL_KEY_FILE)
351 }
352
353 /// Mints a fresh veil key for a replica.
354 pub fn mint(replica: ReplicaId)
355 -> Outcome<Self>
356 {
357 let scheme = AgreementScheme::new_x25519();
358 let public = match res!(scheme.get_public_key()) {
359 Some(pk) => pk.to_vec(),
360 None => return Err(err!(
361 "A freshly minted {} pair has no public key, which cannot happen and \
362 means the scheme has changed under this tool.", VEIL_KEY_SCHEME;
363 Bug, Missing, Key)),
364 };
365 Ok(Self { scheme, public, replica })
366 }
367
368 /// Returns the public half, as the scheme encodes it.
369 pub fn public(&self) -> &[u8] {
370 &self.public
371 }
372
373 /// Returns the public half in the form the file writes it and `ore key
374 /// --veil-key` prints it.
375 pub fn public_text(&self) -> String {
376 text_of(&self.public)
377 }
378
379 /// Returns the binding that publishes this veil key, signed by the replica's
380 /// own signing key.
381 ///
382 /// The signing key must be the same replica's. A key vouching for another
383 /// replica's veil key would be a statement nobody could act on: the reader
384 /// checks the signer against the Ed25519 binding of the replica the veil key
385 /// claims, and the two would never meet.
386 pub fn binding(&self, signer: &Signing)
387 -> Outcome<VeilBinding>
388 {
389 if signer.replica != self.replica {
390 return Err(err!(
391 "The veil key of replica {} cannot be vouched for by the signing key \
392 of replica {}. A veil binding says \"this replica's veil key is mine\", \
393 and one replica does not say that about another.",
394 self.replica, signer.replica;
395 Invalid, Input, Mismatch, Key));
396 }
397 let statement = veil_bind_statement(self.replica.inner(), &self.public);
398 Ok(VeilBinding {
399 replica: self.replica.inner(),
400 public: self.public.clone(),
401 signer: signer.public().to_vec(),
402 sig: res!(signer.sign(&statement)),
403 })
404 }
405
406 /// Reads the veil key file, which a repository is not obliged to have.
407 ///
408 /// Absence is `None`, because a repository nobody has been let into needs no
409 /// veil key; a file that is there and will not read is an error, because the
410 /// alternative is a replica quietly failing to find the wrap that was made
411 /// for it and reporting only that it cannot read what arrived.
412 pub fn read(dir: &Path)
413 -> Outcome<Option<Self>>
414 {
415 let path = Self::path_of(dir);
416 if !path.is_file() {
417 return Ok(None);
418 }
419 let text = match fs::read_to_string(&path) {
420 Ok(t) => t,
421 Err(e) => return Err(err!(e,
422 "The veil key {:?} could not be read.", path;
423 IO, File, Read)),
424 };
425 let dat = match Dat::decode_string(text) {
426 Ok(d) => d,
427 Err(e) => return Err(err!(e,
428 "The veil key {:?} is not readable JDAT.", path;
429 Decode, Input)),
430 };
431 let map = match &dat {
432 Dat::Map(m) => m,
433 other => return Err(err!(
434 "The veil key {:?} expects a map, got {:?}.", path, other;
435 Decode, Input, Mismatch)),
436 };
437 let field = |key: &str| -> Outcome<Dat> {
438 match map.get(&Dat::Str(fmt!("{}", key))) {
439 Some(d) => Ok(d.clone()),
440 None => Err(err!(
441 "The veil key {:?} has no field {:?}.", path, key;
442 Decode, Input, Missing)),
443 }
444 };
445 let number = |d: Dat, what: &str| -> Outcome<u64> {
446 match d {
447 Dat::U64(n) => Ok(n),
448 Dat::U32(n) => Ok(n as u64),
449 Dat::U8(n) => Ok(n as u64),
450 other => Err(err!(
451 "The veil key {:?} declares a {} of {:?} rather than a number.",
452 path, what, other;
453 Decode, Input, Mismatch)),
454 }
455 };
456 let string = |d: Dat, what: &str| -> Outcome<String> {
457 match d {
458 Dat::Str(s) => Ok(s),
459 other => Err(err!(
460 "The veil key {:?} holds a {} of {:?} rather than a string.",
461 path, what, other;
462 Decode, Input, Mismatch)),
463 }
464 };
465 let version = res!(number(res!(field("format")), "format"));
466 if version != VEIL_KEY_VERSION {
467 return Err(err!(
468 "The veil key {:?} declares format version {}, and this tool knows only \
469 version {}.", path, version, VEIL_KEY_VERSION;
470 Decode, Input, Version, Mismatch));
471 }
472 let named = res!(string(res!(field("scheme")), "scheme"));
473 if named != VEIL_KEY_SCHEME {
474 return Err(err!(
475 "The veil key {:?} is a {} key, and this tool wraps with {}.",
476 path, named, VEIL_KEY_SCHEME;
477 Invalid, Input, Mismatch));
478 }
479 let replica = ReplicaId::new(res!(number(res!(field("replica")), "replica")));
480 let public = res!(bytes_of(&res!(string(res!(field("public")), "public key"))));
481 let secret = res!(bytes_of(&res!(string(res!(field("secret")), "secret key"))));
482 let scheme = match AgreementScheme::x25519_with_secret(&secret) {
483 Ok(s) => s,
484 Err(e) => return Err(err!(e,
485 "The veil key {:?} holds a {} byte secret key, which {} does not accept.",
486 path, secret.len(), VEIL_KEY_SCHEME;
487 Invalid, Input, Key)),
488 };
489 // Derived rather than believed: a file whose public half does not belong
490 // to its secret would publish a binding nobody could ever wrap to, and the
491 // failure would surface as a wrap that never arrives.
492 let derived = res!(AgreementScheme::x25519_public_of(&secret));
493 if public != derived {
494 return Err(err!(
495 "The veil key {:?} names a public key that does not belong to the \
496 secret beside it. Nothing could open a wrap addressed to what it \
497 publishes.", path;
498 Invalid, Input, Mismatch, Key));
499 }
500 Ok(Some(Self { scheme, public, replica }))
501 }
502
503 /// Writes the veil key file, readable only by its owner.
504 pub fn write(&self, dir: &Path)
505 -> Outcome<PathBuf>
506 {
507 let secret = match res!(self.scheme.get_secret_key()) {
508 Some(sk) => sk.to_vec(),
509 None => return Err(err!(
510 "This veil key holds no secret half, so there is nothing to write.";
511 Missing, Key)),
512 };
513 let mut map = DaticleMap::new();
514 map.insert(Dat::Str(fmt!("comment")), Dat::Str(fmt!("{}", VEIL_KEY_COMMENT)));
515 map.insert(Dat::Str(fmt!("format")), Dat::U64(VEIL_KEY_VERSION));
516 map.insert(Dat::Str(fmt!("scheme")), Dat::Str(fmt!("{}", VEIL_KEY_SCHEME)));
517 map.insert(Dat::Str(fmt!("replica")), Dat::U64(self.replica.inner()));
518 map.insert(Dat::Str(fmt!("public")), Dat::Str(text_of(&self.public)));
519 map.insert(Dat::Str(fmt!("secret")), Dat::Str(text_of(&secret)));
520 let text = res!(Dat::Map(map).jdat_to_lines(" "));
521 let path = Self::path_of(dir);
522 res!(write_private(&path, fmt!("{}\n", text).as_bytes()));
523 Ok(path)
524 }
525}
526
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 use std::time::{
533 SystemTime,
534 UNIX_EPOCH,
535 };
536
537 /// A directory that removes itself however the test ends.
538 struct Scratch {
539 /// Where it is.
540 path: PathBuf,
541 }
542
543 impl Scratch {
544 fn new(what: &str)
545 -> Outcome<Self>
546 {
547 let stamp = res!(SystemTime::now().duration_since(UNIX_EPOCH));
548 let path = std::env::temp_dir().join(fmt!(
549 "ore_veilkey_{}_{}_{}", what, std::process::id(), stamp.as_nanos(),
550 ));
551 res!(fs::create_dir_all(&path));
552 Ok(Self { path })
553 }
554 }
555
556 impl Drop for Scratch {
557 fn drop(&mut self) {
558 let _ = fs::remove_dir_all(&self.path);
559 }
560 }
561
562 /// A veil binding holds only when both links do, and every way of breaking
563 /// either of them stops it holding.
564 ///
565 /// This is the whole of what stops a carrier handing out a reading key of its
566 /// own. It cannot sign the first link, because it does not hold the replica's
567 /// signing key; it cannot fabricate the second, because an Ed25519 binding
568 /// certifies itself.
569 #[test]
570 fn a_veil_binding_holds_by_two_links_and_not_by_one() -> Outcome<()> {
571 let signer = res!(Signing::mint(ReplicaId::new(7)));
572 let key = res!(VeilKey::mint(ReplicaId::new(7)));
573 let binding = res!(key.binding(&signer));
574 let known = vec![signer.binding()];
575
576 assert_eq!(binding.replica, 7);
577 assert_eq!(binding.public, key.public().to_vec());
578 assert_eq!(binding.signer, signer.public().to_vec());
579 assert!(binding.is_vouched(), "a minted veil binding is signed by its replica's key");
580 assert!(binding.is_chained(&known), "and that key is the replica's own");
581
582 // Claiming another replica breaks the first link: the statement covers
583 // the number.
584 let mut lying = binding.clone();
585 lying.replica = 8;
586 assert!(!lying.is_vouched(), "a relabelled veil binding is not vouched for");
587
588 // So does putting another veil key under the same signature.
589 let other = res!(VeilKey::mint(ReplicaId::new(7)));
590 let mut swapped = binding.clone();
591 swapped.public = other.public().to_vec();
592 assert!(!swapped.is_vouched(), "a signature does not travel to another veil key");
593
594 // And so does altering the signature itself, which fails rather than
595 // raising.
596 let mut broken = binding.clone();
597 let last = broken.sig.len() - 1;
598 broken.sig[last] ^= 0x01;
599 assert!(!broken.is_vouched());
600
601 // The second link is separate: a stranger's key can vouch perfectly well
602 // for its own statement, and it is still not replica 7's key.
603 let stranger = res!(Signing::mint(ReplicaId::new(7)));
604 let forged = VeilBinding {
605 replica: 7,
606 public: key.public().to_vec(),
607 signer: stranger.public().to_vec(),
608 sig: res!(stranger.sign(&veil_bind_statement(7, key.public()))),
609 };
610 assert!(forged.is_vouched(), "the forgery is internally consistent");
611 assert!(!forged.is_chained(&known),
612 "and is refused anyway, because that key is not replica 7's");
613
614 // An Ed25519 binding that does not certify itself carries nobody.
615 let mut bare = signer.binding();
616 bare.sig = None;
617 assert!(!binding.is_chained(&[bare]),
618 "an uncertified signing binding cannot be the second link");
619
620 // A signing key may not vouch for another replica's veil key at all.
621 let elsewhere = res!(Signing::mint(ReplicaId::new(9)));
622 if key.binding(&elsewhere).is_ok() {
623 return Err(err!(
624 "Replica 9's key vouched for replica 7's veil key."; Test, Invalid));
625 }
626
627 // It survives the round trip through its wire form, both links intact.
628 let back = res!(VeilBinding::from_dat(&binding.to_dat()));
629 assert_eq!(back, binding);
630 assert!(back.is_chained(&known));
631 Ok(())
632 }
633
634 /// A wrap gives the content key to the replica it is addressed to, and to
635 /// nothing else.
636 ///
637 /// The wrap is asserted to carry no plaintext of the key, because that is
638 /// what makes it safe to hand to a relay: the whole design rests on a wrap
639 /// being useless to everybody but its addressee.
640 #[test]
641 fn a_wrap_opens_for_one_veil_key_and_no_other() -> Outcome<()> {
642 let mine = res!(VeilKey::mint(ReplicaId::new(1)));
643 let yours = res!(VeilKey::mint(ReplicaId::new(2)));
644 let content = res!(crate::veil::Veil::mint());
645 let secret = res!(bytes_of(&content.text()));
646
647 let wrap = res!(Wrap::mint(yours.public(), &secret));
648 assert_eq!(wrap.to, yours.public().to_vec(), "a wrap says who it is for");
649 assert_eq!(res!(wrap.open(&yours)), secret, "and the one it is for opens it");
650
651 // The bytes that cross carry nothing of the key they carry.
652 let encoded = res!(wrap.to_dat().jdat_to_lines(" ")).into_bytes();
653 assert!(
654 !encoded.windows(secret.len()).any(|w| w == &secret[..]),
655 "the wrap on the wire carries the content key in clear",
656 );
657
658 // Addressed to somebody else, and refused by address rather than by a
659 // failed decryption, so the sentence says which.
660 let refused = match wrap.open(&mine) {
661 Ok(_) => return Err(err!(
662 "Another replica's wrap opened."; Test, Invalid, Security)),
663 Err(e) => fmt!("{}", e.plain()),
664 };
665 assert!(refused.contains("addressed to another veil key"),
666 "the refusal does not say why: {}", refused);
667
668 // A wrap readdressed to a key that did not agree it does not open either,
669 // which is the check that the address is not merely a label.
670 let mut moved = wrap.clone();
671 moved.to = mine.public().to_vec();
672 assert!(moved.open(&mine).is_err(), "a readdressed wrap opened");
673
674 // An altered ciphertext is refused rather than yielding rubbish.
675 let mut tampered = wrap.clone();
676 let last = tampered.wrapped.len() - 1;
677 tampered.wrapped[last] ^= 0x01;
678 assert!(tampered.open(&yours).is_err(), "an altered wrap opened");
679
680 // The sender's key is ephemeral, so two wraps of one key to one recipient
681 // share no bytes and each opens on its own.
682 let again = res!(Wrap::mint(yours.public(), &secret));
683 assert_ne!(again.eph, wrap.eph, "the sender's key is minted per wrap");
684 assert_ne!(again.wrapped, wrap.wrapped);
685 assert_eq!(res!(again.open(&yours)), secret);
686
687 // And it survives the round trip through its wire form.
688 let back = res!(Wrap::from_dat(&wrap.to_dat()));
689 assert_eq!(back, wrap);
690 assert_eq!(res!(back.open(&yours)), secret);
691 Ok(())
692 }
693
694 /// A veil key survives the file it is written in, and a file that does not
695 /// hold one is said to be that rather than guessed at.
696 #[test]
697 fn a_veil_key_survives_its_own_file() -> Outcome<()> {
698 let scratch = res!(Scratch::new("file"));
699 let dir = scratch.path.as_path();
700
701 // A repository nobody has been let into holds no veil key, and that is
702 // not a failure.
703 assert!(res!(VeilKey::read(dir)).is_none());
704
705 let key = res!(VeilKey::mint(ReplicaId::new(3)));
706 let path = res!(key.write(dir));
707 assert_eq!(path, VeilKey::path_of(dir));
708 let back = match res!(VeilKey::read(dir)) {
709 Some(k) => k,
710 None => return Err(err!("The veil key just written was not read back."; Test)),
711 };
712 assert_eq!(back.public(), key.public());
713 assert_eq!(back.replica, key.replica);
714
715 // The key that came back is the key that went in, which is asserted
716 // through what it is for rather than by reading the secret out of it.
717 let secret = b"a content key of exactly 32 byts".to_vec();
718 let wrap = res!(Wrap::mint(key.public(), &secret));
719 assert_eq!(res!(wrap.open(&back)), secret);
720
721 // It is written for its owner alone, like the signing key beside it.
722 #[cfg(unix)]
723 {
724 use std::os::unix::fs::PermissionsExt;
725 let mode = res!(fs::metadata(&path)).permissions().mode() & 0o777;
726 assert_eq!(mode, 0o600, "the veil key is readable by more than its owner");
727 }
728
729 // A file whose public half does not belong to its secret publishes a key
730 // nothing could ever wrap to, and is refused where it is read.
731 let text = res!(fs::read_to_string(&path));
732 let stranger = res!(VeilKey::mint(ReplicaId::new(3)));
733 let swapped = text.replace(&key.public_text(), &stranger.public_text());
734 assert_ne!(swapped, text, "the fixture did not replace the public key");
735 res!(fs::write(&path, swapped));
736 let refused = match VeilKey::read(dir) {
737 Ok(_) => return Err(err!(
738 "A veil key whose halves do not match was accepted."; Test, Invalid)),
739 Err(e) => fmt!("{}", e.plain()),
740 };
741 assert!(refused.contains("does not belong to the secret"),
742 "the refusal does not say what is wrong: {}", refused);
743 Ok(())
744 }
745}