| 1 | //! Frames a peer in a notebook's room sends once through the relay, which copies them to |
| 2 | //! everyone or to the peers named (`relay::GROUP`): presence, hellos, and a host's news of |
| 3 | //! its files. Each is sealed under a key of its sender's own, derived from the room's secret |
| 4 | //! and an id the sender picks for each connection, so the relay sees only ciphertext. |
| 5 | //! |
| 6 | //! A frame carries its number, which is its nonce, and the count of broadcasts its sender has |
| 7 | //! sent: a frame numbered no later than the last, or a broadcast missing before it, means the |
| 8 | //! relay lost, repeated, reordered or forged one, and this end meets the room again. A frame |
| 9 | //! to some peers goes unseen by the others, so only what can tell itself stale is sent so. |
| 10 | //! The room's members hold one key, so a frame proves its sender is in the room, not which |
| 11 | //! member it is; members are trusted alike, as each may change the notebook itself. |
| 12 | |
| 13 | use super::{Hello, Peer}; |
| 14 | use aes_gcm::{ |
| 15 | Aes256Gcm, KeyInit, |
| 16 | aead::{Aead, Payload}, |
| 17 | }; |
| 18 | use hmac::{Hmac, Mac}; |
| 19 | use sha2::Sha256; |
| 20 | use std::io; |
| 21 | |
| 22 | /// A frame's sender id, number, broadcasts, and whether it is a broadcast. |
| 23 | const HEADER: usize = 16 + 8 + 8 + 1; |
| 24 | |
| 25 | /// The room's group key. |
| 26 | pub(super) struct Keys([u8; 32]); |
| 27 | |
| 28 | impl Keys { |
| 29 | pub(super) fn new(secret: &[u8]) -> Self { |
| 30 | Self(mac(secret, b"Snowbound live v2 group")) |
| 31 | } |
| 32 | |
| 33 | fn cipher(&self, sender: &[u8; 16]) -> Aes256Gcm { |
| 34 | Aes256Gcm::new_from_slice(&mac(&self.0, sender)).expect("a 32-byte key") |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | fn mac(key: &[u8], message: &[u8]) -> [u8; 32] { |
| 39 | let mut mac = <Hmac<Sha256> as hmac::KeyInit>::new_from_slice(key).expect("any key length"); |
| 40 | mac.update(message); |
| 41 | mac.finalize().into_bytes().into() |
| 42 | } |
| 43 | |
| 44 | /// Seals this end's frames for one connection to the relay. |
| 45 | pub(super) struct Sealer { |
| 46 | id: [u8; 16], |
| 47 | cipher: Aes256Gcm, |
| 48 | number: u64, |
| 49 | broadcasts: u64, |
| 50 | } |
| 51 | |
| 52 | impl Sealer { |
| 53 | pub(super) fn new(keys: &Keys) -> io::Result<Self> { |
| 54 | let mut id = [0; 16]; |
| 55 | getrandom::fill(&mut id).map_err(|_| io::Error::other("System random source failed"))?; |
| 56 | Ok(Self { |
| 57 | cipher: keys.cipher(&id), |
| 58 | id, |
| 59 | number: 0, |
| 60 | broadcasts: 0, |
| 61 | }) |
| 62 | } |
| 63 | |
| 64 | /// Message `kind` holding `body`, to everyone where `broadcast`. |
| 65 | pub(super) fn seal(&mut self, kind: u16, body: &[u8], broadcast: bool) -> io::Result<Vec<u8>> { |
| 66 | self.number += 1; |
| 67 | self.broadcasts += u64::from(broadcast); |
| 68 | let mut header = Vec::with_capacity(HEADER); |
| 69 | header.extend_from_slice(&self.id); |
| 70 | header.extend_from_slice(&self.number.to_be_bytes()); |
| 71 | header.extend_from_slice(&self.broadcasts.to_be_bytes()); |
| 72 | header.push(u8::from(broadcast)); |
| 73 | let clear = [&kind.to_be_bytes()[..], body].concat(); |
| 74 | let sealed = self |
| 75 | .cipher |
| 76 | .encrypt( |
| 77 | &nonce(self.number).into(), |
| 78 | Payload { |
| 79 | msg: &clear, |
| 80 | aad: &header, |
| 81 | }, |
| 82 | ) |
| 83 | .map_err(|_| io::Error::other("A frame could not be sealed"))?; |
| 84 | Ok([header, sealed].concat()) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | fn nonce(number: u64) -> [u8; 12] { |
| 89 | let mut nonce = [0; 12]; |
| 90 | nonce[4..].copy_from_slice(&number.to_be_bytes()); |
| 91 | nonce |
| 92 | } |
| 93 | |
| 94 | /// A peer in the room as its frames say: the id its frames are sealed under, how far they |
| 95 | /// have come, and the peer once its hello has. |
| 96 | pub(super) struct Member { |
| 97 | id: [u8; 16], |
| 98 | cipher: Aes256Gcm, |
| 99 | number: u64, |
| 100 | broadcasts: u64, |
| 101 | pub(super) peer: Option<Peer>, |
| 102 | } |
| 103 | |
| 104 | impl Member { |
| 105 | /// The member whose first frame is `frame`. |
| 106 | pub(super) fn new(keys: &Keys, frame: &[u8]) -> io::Result<Self> { |
| 107 | let id: [u8; 16] = frame |
| 108 | .get(..16) |
| 109 | .and_then(|id| id.try_into().ok()) |
| 110 | .ok_or_else(|| broken("A group frame without its sender"))?; |
| 111 | Ok(Self { |
| 112 | cipher: keys.cipher(&id), |
| 113 | id, |
| 114 | number: 0, |
| 115 | broadcasts: 0, |
| 116 | peer: None, |
| 117 | }) |
| 118 | } |
| 119 | |
| 120 | /// The kind and body of `frame`, the next from this member; an error of kind |
| 121 | /// `InvalidData` means the relay tampered with its frames. |
| 122 | pub(super) fn open(&mut self, frame: &[u8]) -> io::Result<(u16, Vec<u8>)> { |
| 123 | let (header, sealed) = frame |
| 124 | .split_at_checked(HEADER) |
| 125 | .ok_or_else(|| broken("A group frame cut short"))?; |
| 126 | let field = |at: usize| u64::from_be_bytes(header[at..at + 8].try_into().unwrap()); |
| 127 | let (number, broadcasts, broadcast) = (field(16), field(24), header[32] == 1); |
| 128 | if header[..16] != self.id { |
| 129 | return Err(broken("A group frame from another sender in its slot")); |
| 130 | } |
| 131 | let mut clear = self |
| 132 | .cipher |
| 133 | .decrypt( |
| 134 | &nonce(number).into(), |
| 135 | Payload { |
| 136 | msg: sealed, |
| 137 | aad: header, |
| 138 | }, |
| 139 | ) |
| 140 | .map_err(|_| broken("A group frame that does not open"))?; |
| 141 | // The first frame heard from a member is where its count starts. |
| 142 | let first = self.number == 0; |
| 143 | let due = self.broadcasts + u64::from(broadcast); |
| 144 | if !first && (number <= self.number || broadcasts != due) { |
| 145 | return Err(broken("A group frame lost, repeated or out of order")); |
| 146 | } |
| 147 | (self.number, self.broadcasts) = (number, broadcasts); |
| 148 | let body = clear.split_off(2.min(clear.len())); |
| 149 | let kind = u16::from_be_bytes(clear.try_into().map_err(|_| broken("An empty frame"))?); |
| 150 | Ok((kind, body)) |
| 151 | } |
| 152 | |
| 153 | pub(super) fn hello(&self) -> Option<&std::sync::Arc<Hello>> { |
| 154 | self.peer.as_ref().map(|peer| &peer.hello) |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | fn broken(message: &'static str) -> io::Error { |
| 159 | io::Error::new(io::ErrorKind::InvalidData, message) |
| 160 | } |
| 161 | |
| 162 | #[cfg(test)] |
| 163 | mod tests { |
| 164 | use super::*; |
| 165 | |
| 166 | #[test] |
| 167 | fn group_frames_out_of_place_are_caught() { |
| 168 | let keys = Keys::new(b"room secret"); |
| 169 | let mut ada = Sealer::new(&keys).unwrap(); |
| 170 | let frames: Vec<_> = (0..4) |
| 171 | .map(|n| ada.seal(16, &[n], n != 1).unwrap()) |
| 172 | .collect(); |
| 173 | let member = || Member::new(&keys, &frames[0]).unwrap(); |
| 174 | |
| 175 | let mut grace = member(); |
| 176 | for (n, frame) in frames.iter().enumerate() { |
| 177 | assert_eq!(grace.open(frame).unwrap(), (16, vec![n as u8])); |
| 178 | } |
| 179 | // A frame to others alone goes unseen without a break. |
| 180 | let mut unaddressed = member(); |
| 181 | unaddressed.open(&frames[0]).unwrap(); |
| 182 | assert_eq!(unaddressed.open(&frames[2]).unwrap(), (16, vec![2])); |
| 183 | // A repeat, a frame reordered, a lost broadcast, an altered frame, another key. |
| 184 | let mut repeated = member(); |
| 185 | repeated.open(&frames[0]).unwrap(); |
| 186 | assert!(repeated.open(&frames[0]).is_err()); |
| 187 | let mut reordered = member(); |
| 188 | reordered.open(&frames[1]).unwrap(); |
| 189 | assert!(reordered.open(&frames[0]).is_err()); |
| 190 | let mut lost = member(); |
| 191 | lost.open(&frames[0]).unwrap(); |
| 192 | assert!(lost.open(&frames[3]).is_err()); |
| 193 | let mut altered = frames[3].clone(); |
| 194 | *altered.last_mut().unwrap() ^= 1; |
| 195 | assert!(member().open(&altered).is_err()); |
| 196 | let stranger = Keys::new(b"another secret"); |
| 197 | assert!( |
| 198 | Member::new(&stranger, &frames[0]) |
| 199 | .unwrap() |
| 200 | .open(&frames[0]) |
| 201 | .is_err() |
| 202 | ); |
| 203 | } |
| 204 | } |