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 | |
| 67 | use crate::keys::{ |
| 68 | Binding, |
| 69 | Signing, |
| 70 | bytes_of, |
| 71 | text_of, |
| 72 | write_private, |
| 73 | }; |
| 74 | |
| 75 | use oxedyne_fe2o3_core::prelude::*; |
| 76 | use oxedyne_fe2o3_crypto::agree::AgreementScheme; |
| 77 | use oxedyne_fe2o3_crypto::enc::EncryptionScheme; |
| 78 | use oxedyne_fe2o3_iop_crypto::enc::Encrypter; |
| 79 | use oxedyne_fe2o3_iop_crypto::kem::KeyExchanger; |
| 80 | use oxedyne_fe2o3_iop_crypto::keys::KeyManager; |
| 81 | use oxedyne_fe2o3_jdat::prelude::*; |
| 82 | use oxedyne_fe2o3_ore::id::ReplicaId; |
| 83 | |
| 84 | use std::fs; |
| 85 | use std::path::{ |
| 86 | Path, |
| 87 | PathBuf, |
| 88 | }; |
| 89 | |
| 90 | |
| 91 | /// Name of the veil key file, within the `.ore` directory. |
| 92 | pub const VEIL_KEY_FILE: &str = "veilkey"; |
| 93 | |
| 94 | /// Version of the veil key file this tool writes. |
| 95 | pub const VEIL_KEY_VERSION: u64 = 1; |
| 96 | |
| 97 | /// The name the veil key file records its scheme under. |
| 98 | pub const VEIL_KEY_SCHEME: &str = "X25519"; |
| 99 | |
| 100 | /// Length of a veil public key, in bytes. |
| 101 | pub 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. |
| 108 | pub const VEIL_BIND_TAG: &str = "ORE-VEIL-1"; |
| 109 | |
| 110 | /// What the veil key file says about itself. |
| 111 | pub 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. |
| 123 | pub 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)] |
| 133 | pub 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 | |
| 144 | impl 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)] |
| 229 | pub 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 | |
| 239 | impl 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)] |
| 337 | pub 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 | |
| 346 | impl 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)] |
| 529 | mod 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 | } |