Skip to main content

sapflow_srt/
crypto.rs

1//! SRT AES-CTR/KM profile, from draft-sharabayko-srt-01 ยงยง3.2.2,6.
2//!
3//! AES-128/256 only; AES-192, GCM, nonzero KEKI and alternative salts are
4//! explicitly unsupported. AES-CTR does not authenticate media or headers.
5//! Sequence-window duplicate rejection is not cryptographic authentication.
6//! Control packets (including stream IDs) remain clear. Use strong,
7//! session-scoped secrets supplied over an authenticated channel.
8//!
9//! Primitives: RustCrypto aes 0.9.3, ctr 0.10.1, aes-kw 0.3.1,
10//! pbkdf2 0.13.0, sha1 0.11.0 (MIT OR Apache-2.0); zeroize 1.9.1
11//! (Apache-2.0 OR MIT), getrandom 0.4.3 (MIT OR Apache-2.0).
12//! Primary algorithm references: RFC3394, RFC8018, NIST SP800-38A.
13//! The KDF uses SHA-1/2048 iterations because this is SRT's wire profile.
14//! Retained keys/passphrase and temporary key arrays are zeroised on drop;
15//! Keys occupy at most two 32-byte buffers plus a 32-byte KEK. KM messages
16//! are at most 104 bytes; parsing never derives a key until shape validation.
17//! Data encrypts/decrypts in place without extra per-payload allocations;
18//! AES schedules are expanded per packet (profiling/optimisation pending).
19//! This does not promise locked memory, crash-dump exclusion or erasure of
20//! copies held by the application or KDF's internal stack temporaries.
21
22use crate::seq::Seq;
23use aes_kw::{KeyInit, KwAes128, KwAes256};
24use ctr::cipher::{KeyIvInit, StreamCipher};
25use std::{fmt, sync::Arc};
26use zeroize::Zeroizing;
27
28const MAX_PERIOD: u32 = 1 << 25;
29const KM_RETRY_US: i64 = 100_000;
30
31/// AES key length: the two sizes this profile supports.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum KeySize {
34    /// AES-128 (16-byte keys).
35    Aes128,
36    /// AES-256 (32-byte keys).
37    Aes256,
38}
39impl KeySize {
40    pub(crate) fn len(self) -> usize {
41        match self {
42            Self::Aes128 => 16,
43            Self::Aes256 => 32,
44        }
45    }
46    pub(crate) fn field(self) -> u16 {
47        (self.len() / 8) as u16
48    }
49}
50
51/// Clones share one zeroising passphrase allocation; Debug is redacted.
52#[derive(Clone)]
53pub struct CryptoConfig {
54    secret: Arc<Zeroizing<Vec<u8>>>,
55    /// AES key length for the session.
56    pub key_size: KeySize,
57    /// Original packets per key, at most 2^25. Retransmissions do not count.
58    pub refresh_packets: u32,
59    /// Announce the next key this many packets before switching.
60    /// Must be below half the refresh period. Sender fails closed if a
61    /// replaced slot still has retained unacknowledged ciphertext.
62    pub preannounce_packets: u32,
63}
64impl fmt::Debug for CryptoConfig {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_struct("CryptoConfig")
67            .field("secret", &"[REDACTED]")
68            .field("key_size", &self.key_size)
69            .field("refresh_packets", &self.refresh_packets)
70            .field("preannounce_packets", &self.preannounce_packets)
71            .finish()
72    }
73}
74impl CryptoConfig {
75    /// Takes ownership of 10..=79 non-NUL passphrase bytes. No plaintext
76    /// fallback is permitted for a session configured with this value.
77    pub fn new(passphrase: Vec<u8>, key_size: KeySize) -> Result<Self, CryptoError> {
78        let secret = Zeroizing::new(passphrase);
79        if !(10..=79).contains(&secret.len()) || secret.contains(&0) {
80            return Err(CryptoError::Config);
81        }
82        Ok(Self {
83            secret: Arc::new(secret),
84            key_size,
85            refresh_packets: MAX_PERIOD,
86            preannounce_packets: 4000,
87        })
88    }
89    pub(crate) fn valid(&self) -> bool {
90        self.refresh_packets <= MAX_PERIOD
91            && self.preannounce_packets > 0
92            && self.preannounce_packets < self.refresh_packets / 2
93    }
94}
95
96/// Why a key, key material or payload operation failed.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum CryptoError {
99    /// The passphrase or key refresh settings are not valid.
100    Config,
101    /// The system's secure random source failed.
102    Random,
103    /// The key material or payload is the wrong shape or size.
104    Malformed,
105    /// Well formed but outside the supported profile (for example AES-192,
106    /// GCM, a nonzero KEKI, or a key size or salt that differs from the
107    /// session's).
108    Unsupported,
109    /// The key material would not unwrap with this passphrase.
110    BadSecret,
111    /// Encryption was required but not supplied. This crate does not
112    /// currently return it.
113    Required,
114    /// No usable key: a key slot is empty, a key has run its period or cannot
115    /// yet be replaced, or the next key was not confirmed in time.
116    KeyExpired,
117    /// A key refresh that repeats or contradicts the current keys, or a
118    /// packet under a key that has already been replaced.
119    Replay,
120}
121impl fmt::Display for CryptoError {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(f, "SRT crypto: {self:?}")
124    }
125}
126impl std::error::Error for CryptoError {}
127
128type Secret = Zeroizing<Vec<u8>>;
129struct Material {
130    size: KeySize,
131    salt: [u8; 16],
132    kek: Secret,
133    keys: [Option<Secret>; 2],
134}
135fn index(kk: u8) -> Result<usize, CryptoError> {
136    match kk {
137        1 => Ok(0),
138        2 => Ok(1),
139        _ => Err(CryptoError::Malformed),
140    }
141}
142fn derive(config: &CryptoConfig, salt: &[u8; 16]) -> Secret {
143    let mut key = Zeroizing::new(vec![0; config.key_size.len()]);
144    pbkdf2::pbkdf2_hmac::<sha1::Sha1>(
145        config.secret.as_slice(),
146        salt.get(8..).unwrap_or_default(),
147        2048,
148        &mut key,
149    );
150    key
151}
152fn random_key(size: KeySize) -> Result<Secret, CryptoError> {
153    let mut key = Zeroizing::new(vec![0; size.len()]);
154    getrandom::fill(&mut key).map_err(|_| CryptoError::Random)?;
155    Ok(key)
156}
157fn wrap(size: KeySize, kek: &[u8], plain: &[u8]) -> Result<Vec<u8>, CryptoError> {
158    let mut out = vec![0; plain.len() + 8];
159    match size {
160        KeySize::Aes128 => KwAes128::new_from_slice(kek)
161            .map_err(|_| CryptoError::Config)?
162            .wrap_key(plain, &mut out)
163            .map_err(|_| CryptoError::Malformed)?,
164        KeySize::Aes256 => KwAes256::new_from_slice(kek)
165            .map_err(|_| CryptoError::Config)?
166            .wrap_key(plain, &mut out)
167            .map_err(|_| CryptoError::Malformed)?,
168    };
169    Ok(out)
170}
171fn unwrap(size: KeySize, kek: &[u8], wrapped: &[u8]) -> Result<Secret, CryptoError> {
172    let mut out = Zeroizing::new(vec![
173        0;
174        wrapped
175            .len()
176            .checked_sub(8)
177            .ok_or(CryptoError::Malformed)?
178    ]);
179    match size {
180        KeySize::Aes128 => KwAes128::new_from_slice(kek)
181            .map_err(|_| CryptoError::Config)?
182            .unwrap_key(wrapped, &mut out)
183            .map_err(|_| CryptoError::BadSecret)?,
184        KeySize::Aes256 => KwAes256::new_from_slice(kek)
185            .map_err(|_| CryptoError::Config)?
186            .unwrap_key(wrapped, &mut out)
187            .map_err(|_| CryptoError::BadSecret)?,
188    };
189    Ok(out)
190}
191struct Km<'a> {
192    kk: u8,
193    size: KeySize,
194    salt: [u8; 16],
195    wrapped: &'a [u8],
196}
197impl<'a> Km<'a> {
198    fn parse(bytes: &'a [u8]) -> Result<Self, CryptoError> {
199        let header: [u8; 16] = bytes
200            .get(..16)
201            .ok_or(CryptoError::Malformed)?
202            .try_into()
203            .map_err(|_| CryptoError::Malformed)?;
204        let [
205            v,
206            sign1,
207            sign2,
208            kk,
209            k0,
210            k1,
211            k2,
212            k3,
213            cipher,
214            auth,
215            se,
216            r0,
217            r1,
218            r2,
219            slen,
220            klen,
221        ] = header;
222        if (v, sign1, sign2) != (0x12, 0x20, 0x29)
223            || !(1..=3).contains(&kk)
224            || [r0, r1, r2] != [0; 3]
225        {
226            return Err(CryptoError::Malformed);
227        }
228        if [k0, k1, k2, k3] != [0; 4] || (cipher, auth, se, slen) != (2, 0, 2, 4) {
229            return Err(CryptoError::Unsupported);
230        }
231        let size = match klen {
232            4 => KeySize::Aes128,
233            8 => KeySize::Aes256,
234            _ => return Err(CryptoError::Unsupported),
235        };
236        let count = if kk == 3 { 2 } else { 1 };
237        if bytes.len() != 32 + 8 + count * size.len() {
238            return Err(CryptoError::Malformed);
239        }
240        Ok(Self {
241            kk,
242            size,
243            salt: bytes
244                .get(16..32)
245                .ok_or(CryptoError::Malformed)?
246                .try_into()
247                .map_err(|_| CryptoError::Malformed)?,
248            wrapped: bytes.get(32..).ok_or(CryptoError::Malformed)?,
249        })
250    }
251}
252pub(crate) fn valid_km(bytes: &[u8], field: u16) -> bool {
253    Km::parse(bytes).is_ok_and(|km| field == 0 || km.size.field() == field)
254}
255impl Material {
256    fn message(&self) -> Result<Vec<u8>, CryptoError> {
257        let mut kk = 0;
258        let mut plain = Zeroizing::new(Vec::with_capacity(64));
259        for (i, key) in self.keys.iter().enumerate() {
260            if let Some(key) = key {
261                kk |= 1 << i;
262                plain.extend_from_slice(key);
263            }
264        }
265        let mut out = vec![
266            0x12,
267            0x20,
268            0x29,
269            kk,
270            0,
271            0,
272            0,
273            0,
274            2,
275            0,
276            2,
277            0,
278            0,
279            0,
280            4,
281            (self.size.len() / 4) as u8,
282        ];
283        out.extend_from_slice(&self.salt);
284        out.extend_from_slice(&wrap(self.size, &self.kek, &plain)?);
285        Ok(out)
286    }
287    fn crypt(&self, kk: u8, seq: Seq, bytes: &mut [u8]) -> Result<(), CryptoError> {
288        if bytes.len() > 1456 {
289            return Err(CryptoError::Malformed);
290        }
291        let key = self
292            .keys
293            .get(index(kk)?)
294            .and_then(Option::as_ref)
295            .ok_or(CryptoError::KeyExpired)?;
296        let iv = iv(&self.salt, seq);
297        match self.size {
298            KeySize::Aes128 => ctr::Ctr128BE::<aes::Aes128>::new_from_slices(key, &iv)
299                .map_err(|_| CryptoError::Config)?
300                .apply_keystream(bytes),
301            KeySize::Aes256 => ctr::Ctr128BE::<aes::Aes256>::new_from_slices(key, &iv)
302                .map_err(|_| CryptoError::Config)?
303                .apply_keystream(bytes),
304        }
305        Ok(())
306    }
307}
308fn iv(salt: &[u8; 16], seq: Seq) -> [u8; 16] {
309    let mut out = [0; 16];
310    for (dst, src) in out.iter_mut().take(14).zip(salt) {
311        *dst = *src;
312    }
313    for (dst, src) in out.iter_mut().skip(10).take(4).zip(seq.get().to_be_bytes()) {
314        *dst ^= src;
315    }
316    out
317}
318
319pub(crate) struct Encryptor {
320    material: Material,
321    period: u32,
322    preannounce: u32,
323    count: u32,
324    active: u8,
325    pending: Option<Vec<u8>>,
326    confirmed: bool,
327    next_km: i64,
328}
329impl Encryptor {
330    pub(crate) fn new(config: &CryptoConfig) -> Result<Self, CryptoError> {
331        if !config.valid() {
332            return Err(CryptoError::Config);
333        }
334        let mut salt = [0; 16];
335        getrandom::fill(&mut salt).map_err(|_| CryptoError::Random)?;
336        let material = Material {
337            size: config.key_size,
338            salt,
339            kek: derive(config, &salt),
340            keys: [Some(random_key(config.key_size)?), None],
341        };
342        Ok(Self {
343            material,
344            period: config.refresh_packets,
345            preannounce: config.preannounce_packets,
346            count: 0,
347            active: 1,
348            pending: None,
349            confirmed: false,
350            next_km: 0,
351        })
352    }
353    pub(crate) fn initial(&self) -> Result<Vec<u8>, CryptoError> {
354        self.material.message()
355    }
356    pub(crate) fn confirm_initial(&self, bytes: &[u8]) -> bool {
357        self.initial().is_ok_and(|b| b == bytes)
358    }
359    /// Prepare an alternate key only if its previous ciphertext has drained.
360    /// Packet count is independent of wrapping 31-bit sequence numbers.
361    pub(crate) fn encrypt(
362        &mut self,
363        seq: Seq,
364        bytes: &mut [u8],
365        retained_keys: u8,
366    ) -> Result<u8, CryptoError> {
367        if self.count == self.period {
368            if !self.confirmed {
369                return Err(CryptoError::KeyExpired);
370            }
371            self.active = 3 - self.active;
372            self.count = 0;
373            self.pending = None;
374            self.confirmed = false;
375        }
376        if self.count == self.period - self.preannounce {
377            let other = 3 - self.active;
378            if retained_keys & other != 0 {
379                return Err(CryptoError::KeyExpired);
380            }
381            let slot = self
382                .material
383                .keys
384                .get_mut(index(other)?)
385                .ok_or(CryptoError::Malformed)?;
386            *slot = Some(random_key(self.material.size)?);
387            self.pending = Some(self.material.message()?);
388            self.next_km = 0;
389        }
390        self.material.crypt(self.active, seq, bytes)?;
391        self.count += 1;
392        Ok(self.active)
393    }
394    pub(crate) fn poll_km(&mut self, now: i64) -> Option<Vec<u8>> {
395        if self.confirmed || now < self.next_km {
396            return None;
397        }
398        let bytes = self.pending.as_ref()?.clone();
399        self.next_km = now.saturating_add(KM_RETRY_US);
400        Some(bytes)
401    }
402    pub(crate) fn deadline(&self) -> Option<i64> {
403        (!self.confirmed && self.pending.is_some()).then_some(self.next_km)
404    }
405    pub(crate) fn confirm(&mut self, bytes: &[u8]) -> bool {
406        if self.pending.as_ref().is_some_and(|b| b == bytes) {
407            self.confirmed = true;
408            true
409        } else {
410            false
411        }
412    }
413}
414
415pub(crate) struct Decryptor {
416    material: Material,
417    active: u8,
418    highest: Option<Seq>,
419    transition: Option<Seq>,
420    active_start: Option<Seq>,
421    staged: bool,
422    last_km: Vec<u8>,
423}
424impl Decryptor {
425    pub(crate) fn new(config: &CryptoConfig, bytes: &[u8]) -> Result<Self, CryptoError> {
426        if !config.valid() {
427            return Err(CryptoError::Config);
428        }
429        let km = Km::parse(bytes)?;
430        if km.size != config.key_size {
431            return Err(CryptoError::Unsupported);
432        }
433        let kek = derive(config, &km.salt);
434        let plain = unwrap(km.size, &kek, km.wrapped)?;
435        let mut keys: [Option<Secret>; 2] = [None, None];
436        let mut chunks = plain.chunks_exact(km.size.len());
437        for (i, slot) in keys.iter_mut().enumerate() {
438            if km.kk & (1 << i) != 0 {
439                *slot = Some(Zeroizing::new(
440                    chunks.next().ok_or(CryptoError::Malformed)?.to_vec(),
441                ));
442            }
443        }
444        Ok(Self {
445            material: Material {
446                size: km.size,
447                salt: km.salt,
448                kek,
449                keys,
450            },
451            active: if km.kk == 2 { 2 } else { 1 },
452            highest: None,
453            transition: None,
454            active_start: None,
455            staged: km.kk == 3,
456            last_km: bytes.to_vec(),
457        })
458    }
459    pub(crate) fn refresh(&mut self, bytes: &[u8]) -> Result<(), CryptoError> {
460        if bytes == self.last_km {
461            return Ok(());
462        }
463        let km = Km::parse(bytes)?;
464        // Keep KDF work out of established packet callbacks. This profile
465        // holds the session salt fixed and refuses alternative refresh salts.
466        if km.size != self.material.size || km.salt != self.material.salt {
467            return Err(CryptoError::Unsupported);
468        }
469        if self.staged {
470            return Err(CryptoError::Replay);
471        }
472        let plain = unwrap(km.size, &self.material.kek, km.wrapped)?;
473        let mut chunks = plain.chunks_exact(km.size.len());
474        let mut replacement = None;
475        for (i, slot) in self.material.keys.iter().enumerate() {
476            if km.kk & (1 << i) == 0 {
477                continue;
478            }
479            let key = chunks.next().ok_or(CryptoError::Malformed)?;
480            if 1 << i == self.active {
481                if slot.as_ref().is_none_or(|old| old.as_slice() != key) {
482                    return Err(CryptoError::Replay);
483                }
484            } else {
485                if slot.as_ref().is_some_and(|old| old.as_slice() == key) {
486                    return Err(CryptoError::Replay);
487                }
488                replacement = Some(Zeroizing::new(key.to_vec()));
489            }
490        }
491        let key = replacement.ok_or(CryptoError::Replay)?;
492        *self
493            .material
494            .keys
495            .get_mut(index(3 - self.active)?)
496            .ok_or(CryptoError::Malformed)? = Some(key);
497        self.staged = true;
498        self.last_km = bytes.to_vec();
499        Ok(())
500    }
501    pub(crate) fn decrypt(
502        &mut self,
503        kk: u8,
504        seq: Seq,
505        bytes: &mut [u8],
506    ) -> Result<(), CryptoError> {
507        index(kk)?;
508        let newer = self.highest.is_none_or(|old| old.distance_to(seq) > 0);
509        if kk != self.active && newer {
510            if !self.staged {
511                return Err(CryptoError::KeyExpired);
512            }
513            self.active = kk;
514            self.staged = false;
515            self.transition = Some(seq);
516            self.active_start = Some(seq);
517        } else if kk != self.active && self.transition.is_some_and(|s| s.distance_to(seq) >= 0) {
518            return Err(CryptoError::Replay);
519        }
520        if kk == self.active {
521            let start = *self.active_start.get_or_insert(seq);
522            if start.distance_to(seq) >= MAX_PERIOD as i32 {
523                return Err(CryptoError::KeyExpired);
524            }
525        }
526        self.material.crypt(kk, seq, bytes)?;
527        if newer {
528            self.highest = Some(seq);
529        }
530        Ok(())
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537    fn hex(s: &str) -> Vec<u8> {
538        s.as_bytes()
539            .chunks_exact(2)
540            .map(|b| {
541                u8::from_str_radix(std::str::from_utf8(b).expect("valid crypto fixture"), 16)
542                    .expect("valid crypto fixture")
543            })
544            .collect()
545    }
546    fn config() -> CryptoConfig {
547        CryptoConfig::new(b"test passphrase!".to_vec(), KeySize::Aes128)
548            .expect("valid crypto fixture")
549    }
550    fn fixture() -> Material {
551        let salt = std::array::from_fn(|i| 16 + i as u8);
552        Material {
553            size: KeySize::Aes128,
554            salt,
555            kek: derive(&config(), &salt),
556            keys: [Some(Zeroizing::new((0..16).collect())), None],
557        }
558    }
559    #[test]
560    fn rfc3394_wrap_and_integrity_vector() {
561        let kek = hex("000102030405060708090a0b0c0d0e0f");
562        let key = hex("00112233445566778899aabbccddeeff");
563        let wrapped = hex("1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5");
564        assert_eq!(
565            wrap(KeySize::Aes128, &kek, &key).expect("valid crypto fixture"),
566            wrapped
567        );
568        assert_eq!(
569            *unwrap(KeySize::Aes128, &kek, &wrapped).expect("valid crypto fixture"),
570            key
571        );
572        let mut bad = wrapped;
573        bad[0] ^= 1;
574        assert_eq!(
575            unwrap(KeySize::Aes128, &kek, &bad).err(),
576            Some(CryptoError::BadSecret)
577        );
578    }
579    #[test]
580    fn rfc6070_pbkdf2_vector() {
581        let mut out = [0; 20];
582        pbkdf2::pbkdf2_hmac::<sha1::Sha1>(b"password", b"salt", 2, &mut out);
583        assert_eq!(
584            out.as_slice(),
585            hex("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957")
586        );
587    }
588    #[test]
589    fn ctr_iv_and_independent_openssl_vector() {
590        let material = fixture();
591        assert_eq!(
592            iv(&material.salt, Seq::new(0x12345678)).as_slice(),
593            hex("10111213141516171819082f4a650000")
594        );
595        let mut plain: Vec<u8> = (0..64).collect();
596        material
597            .crypt(1, Seq::new(0x12345678), &mut plain)
598            .expect("valid crypto fixture");
599        // Recorded from openssl enc -aes-128-ctr with the explicit IV above.
600        assert_eq!(
601            plain,
602            hex(
603                "8c6ed40552954bfd911b25200a07c0bc28573e7ef3521f5e26310ecb093bdf46e2cb08069963ed815a0d4da78f853892a07b722dea5da6e0d46054ea324790d6"
604            )
605        );
606        material
607            .crypt(1, Seq::new(0x12345678), &mut plain)
608            .expect("valid crypto fixture");
609        assert_eq!(plain, (0..64).collect::<Vec<_>>());
610    }
611    #[test]
612    fn malformed_km_is_bounded_and_wrong_secret_fails() {
613        let km = fixture().message().expect("valid crypto fixture");
614        for n in 0..km.len() {
615            assert!(Decryptor::new(&config(), &km[..n]).is_err());
616        }
617        for i in [0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 13, 14, 15] {
618            let mut bad = km.clone();
619            bad[i] ^= 0x80;
620            assert!(Decryptor::new(&config(), &bad).is_err());
621        }
622        let bad = CryptoConfig::new(b"different secret".to_vec(), KeySize::Aes128)
623            .expect("valid crypto fixture");
624        assert_eq!(
625            Decryptor::new(&bad, &km).err(),
626            Some(CryptoError::BadSecret)
627        );
628        assert!(!format!("{:?}", config()).contains("test passphrase"));
629    }
630    #[test]
631    fn refresh_ack_loss_reorder_replay_and_sequence_wrap() {
632        for size in [KeySize::Aes128, KeySize::Aes256] {
633            let mut cfg = config();
634            cfg.key_size = size;
635            cfg.refresh_packets = 10;
636            cfg.preannounce_packets = 3;
637            let mut tx = Encryptor::new(&cfg).expect("valid crypto fixture");
638            let initial = tx.initial().expect("valid crypto fixture");
639            let mut rx = Decryptor::new(&cfg, &initial).expect("valid crypto fixture");
640            let mut old = None;
641            let mut prior_km = None;
642            for n in 0..34 {
643                let seq = Seq::new(0x7fff_fff8).add(n);
644                let mut data = vec![n as u8; 23];
645                let kk = tx.encrypt(seq, &mut data, 0).expect("valid crypto fixture");
646                if n == 9 {
647                    old = Some((kk, seq, data.clone()));
648                }
649                let mut decoded = data;
650                rx.decrypt(kk, seq, &mut decoded)
651                    .expect("valid crypto fixture");
652                assert_eq!(decoded, vec![n as u8; 23]);
653                if n == 11 {
654                    let (kk, seq, mut data) = old.take().expect("valid crypto fixture");
655                    rx.decrypt(kk, seq, &mut data)
656                        .expect("valid crypto fixture");
657                    assert_eq!(data, vec![9; 23]);
658                }
659                if let Some(km) = tx.poll_km(i64::from(n) * 100_000) {
660                    rx.refresh(&km).expect("valid crypto fixture");
661                    rx.refresh(&km).expect("valid crypto fixture");
662                    if let Some(old) = prior_km.replace(km.clone()) {
663                        assert!(rx.refresh(&old).is_err());
664                    }
665                    assert!(tx.confirm(&km));
666                }
667            }
668        }
669    }
670    #[test]
671    fn unconfirmed_or_retained_key_expiry_fails_closed() {
672        let mut cfg = config();
673        cfg.refresh_packets = 6;
674        cfg.preannounce_packets = 2;
675        let mut tx = Encryptor::new(&cfg).expect("valid crypto fixture");
676        for n in 0..6 {
677            tx.encrypt(Seq::new(n), &mut [1], 0)
678                .expect("valid crypto fixture");
679        }
680        assert_eq!(
681            tx.encrypt(Seq::new(6), &mut [1], 0),
682            Err(CryptoError::KeyExpired)
683        );
684        let mut tx = Encryptor::new(&cfg).expect("valid crypto fixture");
685        for n in 0..4 {
686            tx.encrypt(Seq::new(n), &mut [1], 0)
687                .expect("valid crypto fixture");
688        }
689        assert_eq!(
690            tx.encrypt(Seq::new(4), &mut [1], 2),
691            Err(CryptoError::KeyExpired)
692        );
693    }
694    #[test]
695    fn lost_km_response_is_retried_without_burst_or_key_regeneration() {
696        let mut cfg = config();
697        cfg.refresh_packets = 12;
698        cfg.preannounce_packets = 5;
699        let mut tx = Encryptor::new(&cfg).expect("encryptor");
700        let mut rx = Decryptor::new(&cfg, &tx.initial().expect("initial KM")).expect("decryptor");
701        for n in 0..8 {
702            tx.encrypt(Seq::new(n), &mut [1], 0).expect("encrypt");
703        }
704        let first = tx.poll_km(0).expect("preannounce");
705        rx.refresh(&first).expect("install"); // Response is lost.
706        assert!(tx.poll_km(0).is_none());
707        assert!(tx.poll_km(99_999).is_none());
708        let retry = tx.poll_km(100_000).expect("retry");
709        assert_eq!(first, retry);
710        rx.refresh(&retry).expect("duplicate is idempotent");
711        assert!(tx.confirm(&retry));
712        assert!(tx.poll_km(200_000).is_none());
713        for n in 8..13 {
714            tx.encrypt(Seq::new(n), &mut [1], 0)
715                .expect("confirmed rotation");
716        }
717    }
718    #[test]
719    fn independent_kdf_km_header_and_receiver_key_use_limit() {
720        let material = fixture();
721        // Python hashlib.pbkdf2_hmac independent profile vector.
722        assert_eq!(
723            material.kek.as_slice(),
724            hex("3090ae37914479967b10bfc92164ab47")
725        );
726        let km = material.message().expect("KM");
727        assert_eq!(&km[..16], hex("12202901000000000200020000000404"));
728        assert_eq!(km.len(), 56);
729        let mut rx = Decryptor::new(&config(), &km).expect("decryptor");
730        rx.decrypt(1, Seq::new(0), &mut [1]).expect("first");
731        assert_eq!(
732            rx.decrypt(1, Seq::new(MAX_PERIOD), &mut [1]),
733            Err(CryptoError::KeyExpired)
734        );
735    }
736}