1use anyhow::{bail, ensure, Result};
6use openssl::{
7 hash::MessageDigest,
8 memcmp,
9 pkey::{Id, PKey},
10 sign::Signer,
11 symm::{Cipher, Crypter, Mode},
12};
13use std::collections::BTreeMap;
14use zeroize::Zeroizing;
15
16const HEADER: usize = 19;
17const MAX_PACKET: usize = 1519;
18const MAX_CONF: usize = 1024;
19const WINDOW: u16 = 64;
20const MAX_LEGACY_AUDIO_START: u16 = u16::MAX - 59_999;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct MediaEpoch {
25 pub account_generation: u64,
26 pub logout_epoch: u64,
27 pub call_generation: u64,
28 pub key_generation: u64,
29}
30impl MediaEpoch {
31 fn validate(self) -> Result<()> {
32 ensure!(
33 self.account_generation != 0 && self.call_generation != 0 && self.key_generation != 0,
34 "Inactive media generation"
35 );
36 Ok(())
37 }
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct MediaContext {
42 pub room_id: u64,
43 pub own_member: u16,
44 pub peer_member: u16,
45 pub connection_id: u16,
46}
47
48pub enum TransmitScope {
52 VerifiedGeneration {
53 key_generation: u64,
54 },
55 LegacyRelayAudio {
56 key_generation: u64,
57 },
58 IsolatedCompatibilityTest {
59 intent_id: [u8; 32],
60 deadline_ms: u64,
61 allow_known_audio_overlap: bool,
62 },
63}
64
65pub struct OpenedPayload {
69 pub packed_kind: u8,
70 pub sequence: u16,
71 payload: Zeroizing<Vec<u8>>,
72 epoch: MediaEpoch,
73 context: MediaContext,
74}
75impl OpenedPayload {
76 pub fn payload(&self) -> &[u8] {
77 &self.payload
78 }
79}
80
81pub struct Live96 {
85 key: Zeroizing<[u8; 32]>,
86 base: Zeroizing<u64>,
87 context: MediaContext,
88 epoch: MediaEpoch,
89 tx_scope: Option<TransmitScope>,
90 first_tx_audio: u16,
91 next_audio: u32,
92 next_control: u32,
93 clear_sequence: u32,
94 rx_highest: BTreeMap<u8, u16>,
95 rx_seen: BTreeMap<u8, Box<[u64; 1024]>>,
96 rx: BTreeMap<[u8; 8], [u8; 32]>,
97 audio_overlaps: u64,
98 closed: bool,
99}
100impl Live96 {
101 pub fn new(
102 key: [u8; 32],
103 nonce_base: u64,
104 context: MediaContext,
105 epoch: MediaEpoch,
106 ) -> Result<Self> {
107 epoch.validate()?;
108 ensure!(
109 context.room_id != 0 && nonce_base != 0 && context.own_member != context.peer_member,
110 "Invalid media room/member context"
111 );
112 Ok(Self {
113 key: Zeroizing::new(key),
114 base: Zeroizing::new(nonce_base),
115 context,
116 epoch,
117 tx_scope: None,
118 first_tx_audio: 0,
119 next_audio: 0,
120 next_control: 0,
121 clear_sequence: 0,
122 rx_highest: BTreeMap::new(),
123 rx_seen: BTreeMap::new(),
124 rx: BTreeMap::new(),
125 audio_overlaps: 0,
126 closed: false,
127 })
128 }
129 fn check(&mut self, current: MediaEpoch) -> Result<()> {
130 if self.closed || self.epoch != current {
131 self.close();
132 bail!("Media generation changed or closed");
133 }
134 Ok(())
135 }
136 pub fn authorize_transmit(&mut self, current: MediaEpoch, scope: TransmitScope) -> Result<()> {
137 self.check(current)?;
138 ensure!(
139 self.tx_scope.is_none(),
140 "Media transmit scope already installed"
141 );
142 match &scope {
143 TransmitScope::VerifiedGeneration { key_generation }
144 | TransmitScope::LegacyRelayAudio { key_generation } => ensure!(
145 *key_generation == current.key_generation,
146 "Media key generation mismatch"
147 ),
148 TransmitScope::IsolatedCompatibilityTest {
149 intent_id,
150 deadline_ms,
151 ..
152 } => ensure!(
153 *intent_id != [0; 32] && *deadline_ms != 0,
154 "Missing isolated media test scope"
155 ),
156 };
157 if matches!(scope, TransmitScope::LegacyRelayAudio { .. }) {
158 self.first_tx_audio = self.legacy_audio_start()?;
159 self.next_audio = u32::from(self.first_tx_audio);
160 }
161 self.tx_scope = Some(scope);
162 Ok(())
163 }
164 fn legacy_audio_start(&self) -> Result<u16> {
165 let key = PKey::hmac(&self.key[..])?;
166 let mut mac = Signer::new(MessageDigest::sha256(), &key)?;
167 mac.update(b"agent-wechat/live96/audio-start/v1")?;
168 mac.update(&self.context.room_id.to_le_bytes())?;
169 mac.update(&self.base.to_le_bytes())?;
170 mac.update(&self.context.own_member.to_le_bytes())?;
171 let digest = Zeroizing::new(mac.sign_to_vec()?);
172 let prefix = u32::from_le_bytes(digest[..4].try_into()?);
173 Ok((prefix % u32::from(MAX_LEGACY_AUDIO_START) + 1) as u16)
174 }
175 fn tx_check(&mut self, current: MediaEpoch, now_ms: u64) -> Result<()> {
176 self.check(current)?;
177 match &self.tx_scope {
178 None => bail!("Media transmission is not authorized"),
179 Some(
180 TransmitScope::VerifiedGeneration { .. } | TransmitScope::LegacyRelayAudio { .. },
181 ) => Ok(()),
182 Some(TransmitScope::IsolatedCompatibilityTest { deadline_ms, .. }) => {
183 if now_ms >= *deadline_ms {
184 self.close();
185 bail!("Isolated media test expired");
186 }
187 Ok(())
188 }
189 }
190 }
191 fn nonce(&self, kind: u8, sequence: u16) -> [u8; 8] {
192 self.base
193 .wrapping_add(u64::from(kind) << 32)
194 .wrapping_add(u64::from(sequence))
195 .to_le_bytes()
196 }
197 fn diagnostic_audio_overlap(&mut self, kind: u8) -> Result<()> {
198 if kind == 0x10
199 && matches!(
200 self.tx_scope,
201 Some(
202 TransmitScope::LegacyRelayAudio { .. }
203 | TransmitScope::IsolatedCompatibilityTest {
204 allow_known_audio_overlap: true,
205 ..
206 }
207 )
208 )
209 {
210 self.audio_overlaps = self
211 .audio_overlaps
212 .checked_add(1)
213 .ok_or_else(|| anyhow::anyhow!("Media diagnostic count exhausted"))?;
214 Ok(())
215 } else {
216 self.close();
217 bail!("Known media cross-direction nonce reuse")
218 }
219 }
220 pub fn known_audio_overlaps(&self) -> u64 {
221 self.audio_overlaps
222 }
223 pub fn seal_audio(
224 &mut self,
225 current: MediaEpoch,
226 now_ms: u64,
227 conf_payload: &[u8],
228 ) -> Result<Vec<u8>> {
229 parse_ordinary95(conf_payload)?;
230 self.seal(current, now_ms, 0x10, conf_payload)
231 }
232 fn seal(
233 &mut self,
234 current: MediaEpoch,
235 now_ms: u64,
236 kind: u8,
237 payload: &[u8],
238 ) -> Result<Vec<u8>> {
239 self.tx_check(current, now_ms)?;
240 ensure!(
241 !payload.is_empty() && payload.len() <= 1482,
242 "Invalid media payload extent"
243 );
244 let next = match kind {
245 0x10 => &mut self.next_audio,
246 0x70 => &mut self.next_control,
247 _ => bail!("Unsupported media sender family"),
248 };
249 ensure!(
250 *next <= u32::from(u16::MAX) && self.clear_sequence < u32::MAX,
251 "Media sequence exhausted; verified rekey required"
252 );
253 let sequence = *next as u16;
254 *next += 1;
255 let nonce = self.nonce(kind, sequence);
256 if self
257 .rx_seen
258 .get(&kind)
259 .is_some_and(|seen| seen[usize::from(sequence) / 64] & (1u64 << (sequence % 64)) != 0)
260 {
261 self.diagnostic_audio_overlap(kind)?;
262 }
263 let mut protected = Zeroizing::new(Vec::with_capacity(payload.len() + 2));
264 protected.extend(self.context.own_member.to_le_bytes());
265 protected.extend(payload);
266 let cipher = seal_original(&self.key, nonce, &protected)?;
267 let mut packet = Vec::with_capacity(HEADER + cipher.len());
268 packet.extend([0x96, 19]);
269 packet.extend(self.context.room_id.to_le_bytes());
270 packet.extend(self.context.own_member.to_le_bytes());
271 packet.extend((self.clear_sequence as u16).to_le_bytes());
272 packet.push(kind);
273 packet.extend(sequence.to_le_bytes());
274 packet.extend([0, self.context.connection_id as u8]);
275 packet.extend(cipher);
276 self.clear_sequence += 1;
277 Ok(packet)
278 }
279 pub fn open(&mut self, current: MediaEpoch, packet: &[u8]) -> Result<Option<OpenedPayload>> {
280 self.check(current)?;
281 ensure!(
282 (38..=MAX_PACKET).contains(&packet.len()) && packet[..2] == [0x96, 19],
283 "Unsupported Live96 packet shape"
284 );
285 ensure!(
286 u64::from_le_bytes(packet[2..10].try_into()?) == self.context.room_id
287 && u16::from_le_bytes(packet[10..12].try_into()?) == self.context.peer_member,
288 "Media room/member mismatch"
289 );
290 let kind = packet[14];
291 ensure!(
292 matches!(kind, 0x10 | 0x20 | 0x70) && packet[17] == 0,
293 "Unsupported Live96 packet family or extension"
294 );
295 let sequence = u16::from_le_bytes(packet[15..17].try_into()?);
296 let nonce = self.nonce(kind, sequence);
297 let clear = open_original(&self.key, nonce, &packet[HEADER..])?;
298 let digest: [u8; 32] = openssl::sha::sha256(&packet[HEADER..]);
299 if let Some(previous) = self.rx.get(&nonce) {
300 if *previous != digest {
301 self.close();
302 bail!("Authenticated media nonce conflict");
303 }
304 return Ok(None);
305 }
306 let own_next = if kind == 0x10 {
307 self.next_audio
308 } else if kind == 0x70 {
309 self.next_control
310 } else {
311 0
312 };
313 if u32::from(sequence) < own_next && (kind != 0x10 || sequence >= self.first_tx_audio) {
314 self.diagnostic_audio_overlap(kind)?;
315 }
316 let seen = self
320 .rx_seen
321 .entry(kind)
322 .or_insert_with(|| Box::new([0; 1024]));
323 seen[usize::from(sequence) / 64] |= 1u64 << (sequence % 64);
324 if self
325 .rx_highest
326 .get(&kind)
327 .is_some_and(|high| sequence < *high && *high - sequence >= WINDOW)
328 {
329 return Ok(None);
330 }
331 let high = self.rx_highest.entry(kind).or_insert(sequence);
332 *high = (*high).max(sequence);
333 let minimum = high.saturating_sub(WINDOW - 1);
334 let base = *self.base;
335 self.rx.retain(|nonce, _| {
336 let combined = u64::from_le_bytes(*nonce).wrapping_sub(base);
337 let k = (combined >> 32) as u8;
338 let s = combined as u16;
339 k != kind || s >= minimum
340 });
341 self.rx.insert(nonce, digest);
342 ensure!(
343 clear.len() > 2
344 && clear.len() <= 1484
345 && u16::from_le_bytes(clear[..2].try_into()?) == self.context.peer_member,
346 "Protected media member mismatch or extent"
347 );
348 Ok(Some(OpenedPayload {
350 packed_kind: kind,
351 sequence,
352 payload: Zeroizing::new(clear[2..].to_vec()),
353 epoch: current,
354 context: self.context,
355 }))
356 }
357 pub fn reply_to_probe(
361 &mut self,
362 current: MediaEpoch,
363 now_ms: u64,
364 opened: &OpenedPayload,
365 ) -> Result<Option<Vec<u8>>> {
366 self.check(current)?;
367 ensure!(
368 opened.epoch == current && opened.context == self.context,
369 "Stale authenticated control payload"
370 );
371 if opened.packed_kind != 0x70 {
372 return Ok(None);
373 }
374 match opened.payload() {
375 [8, 1, 18, 2, 8, 1] => {
376 if self.next_control <= u32::from(u16::MAX)
379 && self.rx_seen.get(&0x70).is_some_and(|seen| {
380 let sequence = self.next_control as usize;
381 seen[sequence / 64] & (1u64 << (sequence % 64)) != 0
382 })
383 {
384 return Ok(None);
385 }
386 Ok(Some(self.seal(
387 current,
388 now_ms,
389 0x70,
390 &[8, 1, 18, 2, 8, 2],
391 )?))
392 }
393 [8, 1, 18, 2, 8, 0 | 2] => Ok(None),
394 _ => Ok(None),
397 }
398 }
399 pub fn close(&mut self) {
400 self.key = Zeroizing::new([0; 32]);
401 self.base = Zeroizing::new(0);
402 self.tx_scope = None;
403 self.closed = true;
404 self.rx.clear();
405 self.rx_highest.clear();
406 self.rx_seen.clear();
407 }
408}
409
410pub fn ordinary95_single(
412 metadata: u8,
413 sequence: u16,
414 timestamp: u32,
415 frame: &[u8],
416) -> Result<Vec<u8>> {
417 ensure!(
418 !frame.is_empty() && frame.len() <= 1023 && 15 + frame.len() <= MAX_CONF,
419 "Invalid ordinary95 primary extent"
420 );
421 let mut out = Vec::with_capacity(15 + frame.len());
422 out.extend([1, metadata, 0, 0, 0x95, 0x74]);
423 out.extend(sequence.to_be_bytes());
424 out.extend(timestamp.to_be_bytes());
425 out.push(1);
426 out.extend((frame.len() as i16).to_le_bytes());
427 out.extend(frame);
428 Ok(out)
429}
430
431pub fn ordinary95_pair(
434 metadata: u8,
435 last_sequence: u16,
436 last_timestamp: u32,
437 first: &[u8],
438 second: &[u8],
439) -> Result<Vec<u8>> {
440 ensure!(
441 !first.is_empty()
442 && !second.is_empty()
443 && first.len() <= 1023
444 && second.len() <= 1023
445 && 17 + first.len() + second.len() <= MAX_CONF,
446 "Invalid ordinary95 primary extent"
447 );
448 let mut out = Vec::with_capacity(17 + first.len() + second.len());
449 out.extend([1, metadata, 0, 0, 0x95, 0x74]);
450 out.extend(last_sequence.to_be_bytes());
451 out.extend(last_timestamp.to_be_bytes());
452 out.push(2);
453 out.extend((first.len() as i16).to_le_bytes());
454 out.extend((second.len() as i16).to_le_bytes());
455 out.extend(first);
456 out.extend(second);
457 Ok(out)
458}
459
460pub struct OrdinaryAudio<'a> {
463 pub metadata: [u8; 4],
464 pub last_sequence: u16,
465 pub last_timestamp: u32,
466 pub primaries: Vec<&'a [u8]>,
467}
468pub fn parse_ordinary95(payload: &[u8]) -> Result<OrdinaryAudio<'_>> {
469 ensure!(
470 (16..=MAX_CONF).contains(&payload.len())
471 && payload[0] == 1
472 && payload[4..6] == [0x95, 0x74],
473 "Unsupported ordinary95 audio shape"
474 );
475 let count = usize::from(payload[12]);
476 ensure!(
477 (1..=3).contains(&count) && 13 + 2 * count <= payload.len(),
478 "Invalid ordinary95 primary count"
479 );
480 let mut cursor = 13 + 2 * count;
481 let mut primaries = Vec::with_capacity(count);
482 for n in 0..count {
483 let length = i16::from_le_bytes(payload[13 + 2 * n..15 + 2 * n].try_into()?);
484 ensure!(
485 (1..=1023).contains(&length),
486 "Invalid ordinary95 primary length"
487 );
488 let length = length as usize;
489 ensure!(
490 length <= payload.len() - cursor,
491 "Truncated ordinary95 primary"
492 );
493 primaries.push(&payload[cursor..cursor + length]);
494 cursor += length;
495 }
496 ensure!(cursor == payload.len(), "Trailing ordinary95 bytes");
497 Ok(OrdinaryAudio {
498 metadata: payload[..4].try_into()?,
499 last_sequence: u16::from_be_bytes(payload[6..8].try_into()?),
500 last_timestamp: u32::from_be_bytes(payload[8..12].try_into()?),
501 primaries,
502 })
503}
504fn xor(key: &[u8; 32], nonce: [u8; 8], counter: u64, input: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
505 let mut iv = Zeroizing::new([0; 16]);
506 iv[..8].copy_from_slice(&counter.to_le_bytes());
507 iv[8..].copy_from_slice(&nonce);
508 let cipher = Cipher::chacha20();
509 let mut crypter = Crypter::new(cipher, Mode::Encrypt, key, Some(&*iv))?;
510 crypter.pad(false);
511 let mut out = Zeroizing::new(vec![0; input.len() + cipher.block_size()]);
512 let n = crypter.update(input, &mut out)?;
513 let end = crypter.finalize(&mut out[n..])?;
514 out.truncate(n + end);
515 ensure!(out.len() == input.len(), "Unexpected call cipher size");
516 Ok(out)
517}
518fn tag(key: &[u8; 32], nonce: [u8; 8], cipher: &[u8]) -> Result<[u8; 16]> {
519 let one = xor(key, nonce, 0, &[0; 32])?;
520 let key = PKey::private_key_from_raw_bytes(&one, Id::POLY1305)?;
521 let mut signer = Signer::new_without_digest(&key)?;
522 signer.update(&0u64.to_le_bytes())?;
523 signer.update(cipher)?;
524 signer.update(&(cipher.len() as u64).to_le_bytes())?;
525 let mut tag = [0; 16];
526 ensure!(signer.sign(&mut tag)? == 16, "Unexpected call tag size");
527 Ok(tag)
528}
529fn seal_original(key: &[u8; 32], nonce: [u8; 8], clear: &[u8]) -> Result<Vec<u8>> {
530 ensure!(clear.len() <= 1484, "Call cipher input too large");
531 let cipher = xor(key, nonce, 1, clear)?;
532 let tag = tag(key, nonce, &cipher)?;
533 let mut out = Vec::with_capacity(cipher.len() + 16);
534 out.extend(&*cipher);
535 out.extend(tag);
536 Ok(out)
537}
538fn open_original(key: &[u8; 32], nonce: [u8; 8], sealed: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
539 ensure!(
540 (16..=1500).contains(&sealed.len()),
541 "Invalid call ciphertext extent"
542 );
543 let (cipher, expected) = sealed.split_at(sealed.len() - 16);
544 let actual = tag(key, nonce, cipher)?;
545 ensure!(
546 memcmp::eq(&actual, expected),
547 "Call media authentication failed"
548 );
549 xor(key, nonce, 1, cipher)
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555 fn epoch() -> MediaEpoch {
556 MediaEpoch {
557 account_generation: 1,
558 logout_epoch: 0,
559 call_generation: 1,
560 key_generation: 1,
561 }
562 }
563 fn context() -> MediaContext {
564 MediaContext {
565 room_id: 9,
566 own_member: 0,
567 peer_member: 1,
568 connection_id: 2,
569 }
570 }
571 fn receiver() -> Live96 {
572 Live96::new([7; 32], 11, context(), epoch()).unwrap()
573 }
574 fn peer() -> Live96 {
575 let mut c = context();
576 std::mem::swap(&mut c.own_member, &mut c.peer_member);
577 let mut sender = Live96::new([7; 32], 11, c, epoch()).unwrap();
578 sender
579 .authorize_transmit(
580 epoch(),
581 TransmitScope::VerifiedGeneration { key_generation: 1 },
582 )
583 .unwrap();
584 sender
585 }
586 #[test]
587 fn legacy_audio_start_depends_on_the_authenticated_member() {
588 let mut local = receiver();
589 let mut peer_context = context();
590 std::mem::swap(&mut peer_context.own_member, &mut peer_context.peer_member);
591 let mut remote = Live96::new([7; 32], 11, peer_context, epoch()).unwrap();
592 for channel in [&mut local, &mut remote] {
593 channel
594 .authorize_transmit(
595 epoch(),
596 TransmitScope::LegacyRelayAudio { key_generation: 1 },
597 )
598 .unwrap();
599 }
600 assert_eq!(local.first_tx_audio, 440);
602 assert_eq!(remote.first_tx_audio, 429);
603 assert_eq!(local.next_audio, 440);
604 assert_eq!(remote.next_audio, 429);
605 }
606
607 #[test]
608 fn legacy_relay_allows_opposite_direction_audio_overlap_in_either_order() {
609 let raw = ordinary95_pair(0, 1, 320, &[1], &[2]).unwrap();
610 for transmit_first in [false, true] {
611 let mut local = receiver();
612 local
613 .authorize_transmit(
614 epoch(),
615 TransmitScope::LegacyRelayAudio { key_generation: 1 },
616 )
617 .unwrap();
618 let mut remote = peer();
619 remote.next_audio = u32::from(local.first_tx_audio);
620 let inbound = remote.seal_audio(epoch(), 0, &raw).unwrap();
621 if transmit_first {
622 local.seal_audio(epoch(), 0, &raw).unwrap();
623 }
624 assert_eq!(
625 local.open(epoch(), &inbound).unwrap().unwrap().payload(),
626 raw
627 );
628 if !transmit_first {
629 local.seal_audio(epoch(), 0, &raw).unwrap();
630 }
631 assert!(local.open(epoch(), &inbound).unwrap().is_none());
632 assert_eq!(local.known_audio_overlaps(), 1);
633
634 let mut conflicting_peer = peer();
635 conflicting_peer.next_audio = u32::from(local.first_tx_audio);
636 let different = ordinary95_pair(0, 1, 320, &[3], &[4]).unwrap();
637 let conflict = conflicting_peer.seal_audio(epoch(), 0, &different).unwrap();
638 assert!(local.open(epoch(), &conflict).is_err());
639 assert!(local.seal_audio(epoch(), 0, &raw).is_err());
640 }
641 }
642
643 #[test]
644 fn legacy_relay_keeps_control_and_send_exhaustion_fences() {
645 let mut local = receiver();
646 local
647 .authorize_transmit(
648 epoch(),
649 TransmitScope::LegacyRelayAudio { key_generation: 1 },
650 )
651 .unwrap();
652 let mut remote = peer();
653 local.seal(epoch(), 0, 0x70, &[8, 1, 18, 2, 8, 2]).unwrap();
654 let control = remote.seal(epoch(), 0, 0x70, &[8, 1, 18, 2, 8, 1]).unwrap();
655 assert!(local.open(epoch(), &control).is_err());
656
657 let mut local = receiver();
658 local
659 .authorize_transmit(
660 epoch(),
661 TransmitScope::LegacyRelayAudio { key_generation: 1 },
662 )
663 .unwrap();
664 local.next_audio = u32::from(u16::MAX);
665 let raw = ordinary95_pair(0, 1, 320, &[1], &[2]).unwrap();
666 local.seal_audio(epoch(), 0, &raw).unwrap();
667 assert!(local.seal_audio(epoch(), 0, &raw).is_err());
668 }
669
670 #[test]
671 fn original_aead_matches_independent_literal_vectors() {
672 let vectors = [
675 (
676 "43b718d5a398bf36a5f3904a469febac12f7b5b22efa1ae5cc5deb54e75f5574",
677 "f06be3d7749e299c",
678 "",
679 "a02210edb5c4fd736d7366ae81089103",
680 ),
681 (
682 "9456a10f0c4b0644e9524fedcc329f052c443f55df5d911f4c172e07121e3740",
683 "beb908121cb81960",
684 "67736a080137aef7b92f53145b6ec56622",
685 "4576fc20a665cd99f668062c319677cd82a2bbd02898dbd27db411e002570cf3d5",
686 ),
687 (
688 "4b0fdc9081934247c7e945a10ca2ef52989dad5a2477fe041a6780e06d9254ed",
689 "948fb8afa967f654",
690 "1169e988a0972d513ca73734aad2e21f053d292b78d3eb9d3a6486da320d3e9bc67871bb80fdceb0f18b197aa8862b69b2b83993bab8bff256ff7c87ff9a486beb",
691 "ff673095b4835ccdbbd73719c1351799309fca1063534ccd3fb8a5a5b4492b282ef72fa53cb0af85ed86900773e081dcd78ae54215970a122fef411d62561ad3dd67a359f949c8d666c180287b67f297d3",
692 ),
693 ];
694 for (key_hex, nonce_hex, plaintext_hex, expected_hex) in vectors {
695 let key = hex::decode(key_hex).unwrap().try_into().unwrap();
696 let nonce = hex::decode(nonce_hex).unwrap().try_into().unwrap();
697 let plaintext = hex::decode(plaintext_hex).unwrap();
698 let expected = hex::decode(expected_hex).unwrap();
699 assert_eq!(seal_original(&key, nonce, &plaintext).unwrap(), expected);
700 assert_eq!(&*open_original(&key, nonce, &expected).unwrap(), &plaintext);
701 }
702 }
703 #[test]
704 fn literal_primary_layout_and_extent() {
705 let single = ordinary95_single(3, 1, 320, &[1, 2]).unwrap();
706 assert_eq!(
707 single,
708 [1, 3, 0, 0, 0x95, 0x74, 0, 1, 0, 0, 1, 64, 1, 2, 0, 1, 2]
709 );
710 assert_eq!(
711 parse_ordinary95(&single).unwrap().primaries,
712 vec![&[1, 2][..]]
713 );
714 let raw = ordinary95_pair(3, 1, 320, &[1, 2], &[3]).unwrap();
715 assert_eq!(
716 &raw[..17],
717 &[1, 3, 0, 0, 0x95, 0x74, 0, 1, 0, 0, 1, 64, 2, 2, 0, 1, 0]
718 );
719 let decoded = parse_ordinary95(&raw).unwrap();
720 assert_eq!(decoded.primaries, vec![&[1, 2][..], &[3][..]]);
721 let mut trailing = raw;
722 trailing.push(0);
723 assert!(parse_ordinary95(&trailing).is_err());
724 }
725 #[test]
726 fn unauthorized_sender_fails_and_authenticated_receiver_rejects_replay() {
727 let raw = ordinary95_pair(0, 1, 320, &[1], &[2]).unwrap();
728 let mut receiver = receiver();
729 assert!(receiver.seal_audio(epoch(), 0, &raw).is_err());
730 let mut peer = peer();
731 let packet = peer.seal_audio(epoch(), 0, &raw).unwrap();
732 assert_eq!(&packet[..2], &[0x96, 19]);
733 let clear = receiver.open(epoch(), &packet).unwrap().unwrap();
734 assert_eq!(clear.payload(), raw);
735 assert!(receiver.open(epoch(), &packet).unwrap().is_none());
736 let mut corrupt = packet;
737 *corrupt.last_mut().unwrap() ^= 1;
738 assert!(receiver.open(epoch(), &corrupt).is_err());
739 }
740 #[test]
741 fn clear_header_member_cannot_replace_protected_member() {
742 let raw = ordinary95_pair(0, 1, 320, &[1], &[2]).unwrap();
743 let mut peer = peer();
744 let mut packet = peer.seal_audio(epoch(), 0, &raw).unwrap();
745 packet[10..12].copy_from_slice(&2u16.to_le_bytes());
746 assert!(receiver().open(epoch(), &packet).is_err());
747 }
748 #[test]
749 fn strict_scope_rejects_overlap_and_isolated_scope_expires() {
750 let raw = ordinary95_pair(0, 1, 320, &[1], &[2]).unwrap();
751 let mut peer = peer();
752 let packet = peer.seal_audio(epoch(), 0, &raw).unwrap();
753 let mut receiver = receiver();
754 receiver.open(epoch(), &packet).unwrap();
755 receiver
756 .authorize_transmit(
757 epoch(),
758 TransmitScope::VerifiedGeneration { key_generation: 1 },
759 )
760 .unwrap();
761 assert!(receiver.seal_audio(epoch(), 0, &raw).is_err());
762 let mut sender_first = self::receiver();
764 sender_first
765 .authorize_transmit(
766 epoch(),
767 TransmitScope::VerifiedGeneration { key_generation: 1 },
768 )
769 .unwrap();
770 sender_first.seal_audio(epoch(), 0, &raw).unwrap();
771 assert!(sender_first.open(epoch(), &packet).is_err());
772 assert!(sender_first.seal_audio(epoch(), 1, &raw).is_err());
773 let mut diagnostic = self::receiver();
774 diagnostic
775 .authorize_transmit(
776 epoch(),
777 TransmitScope::IsolatedCompatibilityTest {
778 intent_id: [1; 32],
779 deadline_ms: 100,
780 allow_known_audio_overlap: true,
781 },
782 )
783 .unwrap();
784 diagnostic.open(epoch(), &packet).unwrap();
785 assert!(diagnostic.seal_audio(epoch(), 1, &raw).is_ok());
786 assert_eq!(diagnostic.known_audio_overlaps(), 1);
787 assert!(diagnostic.seal_audio(epoch(), 100, &raw).is_err());
788 }
789 #[test]
790 fn authenticated_controls_preserve_nonce_fence_and_ignore_unhandled_messages() {
791 let mut other_peer = peer();
792 let other = other_peer.seal(epoch(), 0, 0x70, &[8, 7]).unwrap();
793 let mut other_receiver = receiver();
794 let opened = other_receiver.open(epoch(), &other).unwrap().unwrap();
795 assert!(other_receiver
796 .reply_to_probe(epoch(), 0, &opened)
797 .unwrap()
798 .is_none());
799 assert!(other_receiver.check(epoch()).is_ok());
800
801 let mut peer = peer();
802 let request = peer.seal(epoch(), 0, 0x70, &[8, 1, 18, 2, 8, 1]).unwrap();
803 let mut receiver = receiver();
804 let opened = receiver.open(epoch(), &request).unwrap().unwrap();
805 receiver
806 .authorize_transmit(
807 epoch(),
808 TransmitScope::VerifiedGeneration { key_generation: 1 },
809 )
810 .unwrap();
811 assert!(receiver
812 .reply_to_probe(epoch(), 0, &opened)
813 .unwrap()
814 .is_none());
815 assert!(receiver.check(epoch()).is_ok());
816 assert!(receiver
818 .seal(epoch(), 0, 0x70, &[8, 1, 18, 2, 8, 2])
819 .is_err());
820 }
821}