wlan_fcg_crypto/sae/
mod.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5mod frame;
6mod state;
7
8use crate::boringssl::EcGroupId;
9use crate::ecc;
10use crate::fcg::FiniteCyclicGroup;
11use crate::hmac_utils::{HmacUtils, HmacUtilsImpl};
12
13use anyhow::{Error, bail};
14use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
15pub use frame::{AntiCloggingTokenMsg, CommitMsg, ConfirmMsg};
16use ieee80211::{MacAddr, Ssid};
17use log::warn;
18use mundane::hash::Sha256;
19use num::FromPrimitive;
20use wlan_common::ie::rsn::akm::{self, Akm};
21
22/// Maximum number of incorrect frames sent before SAE fails.
23const MAX_RETRIES_PER_EXCHANGE: u16 = 3;
24
25/// A shared key computed by an SAE handshake.
26#[derive(Clone, PartialEq, Debug)]
27pub struct Key {
28    pub pmk: Vec<u8>,
29    pub pmkid: Vec<u8>,
30}
31
32/// IEEE Std 802.11-2020 9.4.2.241
33/// Method used to generate the PWE from a password.
34#[derive(Debug, Copy, Clone, PartialEq)]
35pub enum PweMethod {
36    /// IEEE Std 802.11-2020, 12.4.4.2.2/12.4.4.3.2
37    /// Generate the PWE using the looping hunt-and-peck method.
38    Loop = 0,
39
40    /// IEEE Std 802.11-2020, 12.4.4.2.3/12.4.4.3.3
41    /// Generate the PWE using the direct hashing method, hash-to-curve or hash-to-element.
42    Direct = 1,
43}
44
45/// Types of timeout that are used by SAE handshakes. Duration and scheduling of these timeouts
46/// is left to the user of this library.
47#[derive(Debug, Clone, PartialEq)]
48pub enum Timeout {
49    /// Timeout before the most recent message(s) should be resent.
50    Retransmission,
51    /// Timeout before the PMK produced by a successful handshake is considered invalid.
52    KeyExpiration,
53}
54
55#[derive(Debug)]
56pub enum RejectReason {
57    /// We experienced a failure that was unrelated to data received from the peer. This likely
58    /// means we are not in a good state.
59    InternalError(Error),
60    /// Data received from the peer failed validation, and we cannot generate a PMK.
61    AuthFailed,
62    /// The peer has failed to respond or sent incorrect responses too many times.
63    TooManyRetries,
64    /// The SAE PMKSA has expired, reauthenticate.
65    KeyExpiration,
66}
67
68impl From<Error> for RejectReason {
69    fn from(e: Error) -> Self {
70        Self::InternalError(e)
71    }
72}
73
74#[derive(Debug)]
75pub struct AuthFrameRx<'a> {
76    pub seq: u16,
77    pub status_code: fidl_ieee80211::StatusCode,
78    pub body: &'a [u8],
79}
80
81#[derive(Debug, Clone, Eq, PartialEq)]
82pub struct AuthFrameTx {
83    pub seq: u16,
84    pub status_code: fidl_ieee80211::StatusCode,
85    pub body: Vec<u8>,
86}
87
88/// An update generated to progress an SAE handshake. These updates should generally be converted
89/// into a frame and sent to the SAE peer.
90#[derive(Debug)]
91pub enum SaeUpdate {
92    /// Send an auth frame to the peer.
93    SendFrame(AuthFrameTx),
94    /// Indicates the handshake is complete. The handshake should *not* be deleted at this point.
95    Success(Key),
96    /// Indicates that the handshake has failed and must be aborted or restarted.
97    Reject(RejectReason),
98    /// Request the user of the library to set or reset a timeout. If this timeout expires, it
99    /// should be passed to SaeHandshake::handle_timeout.
100    ResetTimeout(Timeout),
101    /// Request the user of the library to cancel a timeout that was previously set.
102    CancelTimeout(Timeout),
103}
104
105pub type SaeUpdateSink = Vec<SaeUpdate>;
106
107/// IEEE 802.11-2016 12.4: Simultaneous Authentication of Equals (SAE)
108///
109/// An SAE handshake with a peer is a symmetric handshake that may be used in place of open
110/// authentication as the AKM. A full handshake consists of both peers sending a Commit and Confirm
111/// frame, at which point they have both derived a shared key that is unique to those peers and that
112/// session.
113///
114/// Structs implementing this trait are responsible for handling both a successful SAE handshake,
115/// various failure modes, and edge cases such as retries and timeouts.
116///
117/// None of the functions in this trait return errors. Instead, non-fatal errors are logged, and
118/// fatal errors push an SaeUpdate::Reject to the update sink. Once an SaeUpdate::Reject is pushed,
119/// all further operations are no-ops.
120pub trait SaeHandshake: Send {
121    /// Initiate SAE by sending the first commit message. If the peer STA sends the first commit
122    /// message, handle_commit should be called first and initiate_sae should never be called.
123    fn initiate_sae(&mut self, sink: &mut SaeUpdateSink);
124
125    fn handle_commit(&mut self, sink: &mut SaeUpdateSink, commit_msg: &CommitMsg<'_>);
126    fn handle_confirm(&mut self, sink: &mut SaeUpdateSink, confirm_msg: &ConfirmMsg<'_>);
127    fn handle_anti_clogging_token(
128        &mut self,
129        sink: &mut SaeUpdateSink,
130        act_msg: &AntiCloggingTokenMsg<'_>,
131    );
132    fn handle_timeout(&mut self, sink: &mut SaeUpdateSink, timeout: Timeout);
133
134    fn handle_frame(&mut self, sink: &mut SaeUpdateSink, frame: &AuthFrameRx<'_>) {
135        match frame::parse(frame) {
136            Ok(parse) => match parse {
137                frame::ParseSuccess::Commit(commit) => self.handle_commit(sink, &commit),
138                frame::ParseSuccess::Confirm(confirm) => self.handle_confirm(sink, &confirm),
139                frame::ParseSuccess::AntiCloggingToken(act_msg) => {
140                    self.handle_anti_clogging_token(sink, &act_msg)
141                }
142            },
143            Err(e) => warn!("Failed to parse SAE auth frame: {}", e),
144        }
145    }
146}
147
148/// Creates a new SAE handshake for the given group ID and authentication parameters.
149pub fn new_sae_handshake(
150    group_id: u16,
151    akm: Akm,
152    pwe_method: PweMethod,
153    ssid: Ssid,
154    password: Vec<u8>,
155    password_id: Option<Vec<u8>>,
156    mac: MacAddr,
157    peer_mac: MacAddr,
158) -> Result<Box<dyn SaeHandshake>, Error> {
159    match akm.suite_type {
160        akm::SAE | akm::FT_SAE => (),
161        _ => bail!("Cannot construct SAE handshake with AKM {:?}", akm),
162    };
163    let (hmac, group_constructor) = match EcGroupId::from_u16(group_id) {
164        Some(EcGroupId::P256) => {
165            // IEEE 802.11-2020 12.4.2
166            // Group 19 has a 256-bit prime length, thus we use SHA256.
167            let hmac = Box::new(HmacUtilsImpl::<Sha256>::new());
168            let group_constructor = Box::new(|| {
169                ecc::Group::new(EcGroupId::P256).map(|group| {
170                    Box::new(group)
171                        as Box<
172                            dyn FiniteCyclicGroup<
173                                Element = <ecc::Group as FiniteCyclicGroup>::Element,
174                            >,
175                        >
176                })
177            });
178            (hmac, group_constructor)
179        }
180        _ => bail!("Unsupported SAE group id: {}", group_id),
181    };
182    Ok(Box::new(state::SaeHandshakeImpl::new(
183        group_constructor,
184        SaeParameters {
185            hmac,
186            pwe_method,
187            ssid,
188            password,
189            password_id,
190            sta_a_mac: mac,
191            sta_b_mac: peer_mac,
192        },
193    )?))
194}
195
196/// Creates a new SAE handshake in response to a first message from a peer, using the FCG indicated
197/// by the peer if possible. In a successful handshake, this will immediately push a Commit and
198/// Confirm to the given update sink.
199pub fn join_sae_handshake(
200    sink: &mut SaeUpdateSink,
201    first_frame: &AuthFrameRx<'_>,
202    akm: Akm,
203    ssid: Ssid,
204    password: Vec<u8>,
205    mac: MacAddr,
206    peer_mac: MacAddr,
207) -> Result<Box<dyn SaeHandshake>, Error> {
208    let parsed_frame = frame::parse(first_frame)?;
209    // IEEE 802.11-2020 12.4.4.2.3
210    // The first frame of the exchange indicates the use of hash to element via
211    // a special status code.
212    let pwe_method = match first_frame.status_code {
213        fidl_ieee80211::StatusCode::Success => PweMethod::Loop,
214        fidl_ieee80211::StatusCode::SaeHashToElement => PweMethod::Direct,
215        other => bail!("Unexpected status code {:?} in first frame", other),
216    };
217    match parsed_frame {
218        frame::ParseSuccess::Commit(commit) => {
219            let mut handshake = new_sae_handshake(
220                commit.group_id,
221                akm,
222                pwe_method,
223                ssid,
224                password,
225                None,
226                mac,
227                peer_mac,
228            )?;
229            handshake.handle_commit(sink, &commit);
230            Ok(handshake)
231        }
232        _ => bail!("Recieved incorrect first frame of SAE handshake"),
233    }
234}
235
236pub(crate) struct SaeParameters {
237    pub hmac: Box<dyn HmacUtils + Send>,
238    pub pwe_method: PweMethod,
239    // IEEE Std 802.11-2020 12.4.4.2.3/12.4.4.3.3: The SSID is needed to generate a password
240    // seed.
241    pub ssid: Ssid,
242    // IEEE Std 802.11-2020 12.4.3
243    pub password: Vec<u8>,
244    pub password_id: Option<Vec<u8>>,
245    // IEEE Std 802.11-2016 12.4.4.2.2: The MacAddrs are needed to generate a password seed.
246    pub sta_a_mac: MacAddr,
247    pub sta_b_mac: MacAddr,
248}
249
250#[cfg(test)]
251mod tests {
252    #![allow(unused_variables)] // Allow unused variables in tests.
253    use super::*;
254    use assert_matches::assert_matches;
255    use std::convert::TryFrom;
256    use std::sync::LazyLock;
257    use test_case::test_case;
258    use wlan_common::ie::rsn::akm::{AKM_PSK, AKM_SAE};
259
260    // IEEE 802.11-2016 Annex J.10 SAE test vector
261    const TEST_SSID: &'static str = "SSID not in 802.11-2016";
262    const TEST_PWD: &'static str = "thisisreallysecret";
263
264    pub(crate) static TEST_STA_A: LazyLock<MacAddr> =
265        LazyLock::new(|| MacAddr::from([0x7b, 0x88, 0x56, 0x20, 0x2d, 0x8d]));
266    pub(crate) static TEST_STA_B: LazyLock<MacAddr> =
267        LazyLock::new(|| MacAddr::from([0xe2, 0x47, 0x1c, 0x0a, 0x5a, 0xcb]));
268
269    #[test]
270    fn bad_akm() {
271        let akm = AKM_PSK;
272        let res = new_sae_handshake(
273            19,
274            akm,
275            PweMethod::Loop,
276            Ssid::try_from(TEST_SSID).unwrap(),
277            Vec::from(TEST_PWD),
278            None, // Not required for PweMethod::Loop
279            *TEST_STA_A,
280            *TEST_STA_B,
281        );
282        assert!(res.is_err());
283        assert!(
284            format!("{}", res.err().unwrap())
285                .contains("Cannot construct SAE handshake with AKM 00-0F-AC:2")
286        );
287    }
288
289    #[test]
290    fn bad_fcg() {
291        let akm = AKM_SAE;
292        let res = new_sae_handshake(
293            200,
294            akm,
295            PweMethod::Loop,
296            Ssid::try_from(TEST_SSID).unwrap(),
297            Vec::from(TEST_PWD),
298            None, // Not required for PweMethod::Loop
299            *TEST_STA_A,
300            *TEST_STA_B,
301        );
302        assert!(res.is_err());
303        assert!(format!("{}", res.err().unwrap()).contains("Unsupported SAE group id: 200"));
304    }
305
306    struct TestHandshake {
307        sta1: Box<dyn SaeHandshake>,
308        sta2: Box<dyn SaeHandshake>,
309    }
310
311    // Helper structs for differentiating Commit/Confirm messages once they've been converted into
312    // generic auth frames.
313    #[derive(Clone, Eq, PartialEq, Debug)]
314    struct CommitTx(AuthFrameTx);
315    #[derive(Clone, Eq, PartialEq, Debug)]
316    struct ConfirmTx(AuthFrameTx);
317    struct CommitRx<'a>(AuthFrameRx<'a>);
318    struct ConfirmRx<'a>(AuthFrameRx<'a>);
319
320    fn to_rx(frame: &AuthFrameTx) -> AuthFrameRx<'_> {
321        AuthFrameRx { seq: frame.seq, status_code: frame.status_code, body: &frame.body[..] }
322    }
323
324    impl CommitTx {
325        fn to_rx(&self) -> CommitRx<'_> {
326            CommitRx(to_rx(&self.0))
327        }
328    }
329
330    impl ConfirmTx {
331        fn to_rx(&self) -> ConfirmRx<'_> {
332            ConfirmRx(to_rx(&self.0))
333        }
334    }
335
336    impl<'a> CommitRx<'a> {
337        fn msg(&'a self) -> CommitMsg<'a> {
338            assert_matches!(frame::parse(&self.0),
339                Ok(frame::ParseSuccess::Commit(commit)) => commit)
340        }
341    }
342
343    impl<'a> ConfirmRx<'a> {
344        fn msg(&'a self) -> ConfirmMsg<'a> {
345            assert_matches!(frame::parse(&self.0),
346                Ok(frame::ParseSuccess::Confirm(confirm)) => confirm)
347        }
348    }
349
350    fn expect_commit(sink: &mut Vec<SaeUpdate>) -> CommitTx {
351        let commit = assert_matches!(sink.remove(0), SaeUpdate::SendFrame(frame) => frame);
352        assert_matches!(frame::parse(&to_rx(&commit)), Ok(frame::ParseSuccess::Commit(msg)));
353        CommitTx(commit)
354    }
355
356    fn expect_confirm(sink: &mut Vec<SaeUpdate>) -> ConfirmTx {
357        let confirm = assert_matches!(sink.remove(0), SaeUpdate::SendFrame(frame) => frame);
358        assert_matches!(frame::parse(&to_rx(&confirm)), Ok(frame::ParseSuccess::Confirm(msg)));
359        ConfirmTx(confirm)
360    }
361
362    fn expect_reset_timeout(sink: &mut Vec<SaeUpdate>, timeout: Timeout) {
363        assert_matches!(sink.remove(0), SaeUpdate::ResetTimeout(timeout));
364    }
365
366    fn expect_cancel_timeout(sink: &mut Vec<SaeUpdate>, timeout: Timeout) {
367        assert_matches!(sink.remove(0), SaeUpdate::CancelTimeout(timeout));
368    }
369
370    struct TestHandshakeConfig {
371        pwe_method: PweMethod,
372    }
373
374    impl TestHandshakeConfig {
375        fn direct() -> Self {
376            Self { pwe_method: PweMethod::Direct }
377        }
378
379        fn looping() -> Self {
380            Self { pwe_method: PweMethod::Loop }
381        }
382    }
383
384    // Test helper to advance through successful steps of an SAE handshake.
385    impl TestHandshake {
386        fn new(cfg: TestHandshakeConfig) -> Self {
387            let akm = AKM_SAE;
388            let sta1 = new_sae_handshake(
389                19,
390                akm.clone(),
391                cfg.pwe_method,
392                Ssid::try_from(TEST_SSID).unwrap(),
393                Vec::from(TEST_PWD),
394                None, // Not required for PweMethod::Loop
395                *TEST_STA_A,
396                *TEST_STA_B,
397            )
398            .unwrap();
399            let sta2 = new_sae_handshake(
400                19,
401                akm,
402                cfg.pwe_method,
403                Ssid::try_from(TEST_SSID).unwrap(),
404                Vec::from(TEST_PWD),
405                None, // Not required for PweMethod::Loop
406                *TEST_STA_B,
407                *TEST_STA_A,
408            )
409            .unwrap();
410            Self { sta1, sta2 }
411        }
412
413        fn sta1_init(&mut self) -> CommitTx {
414            let mut sink = vec![];
415            self.sta1.initiate_sae(&mut sink);
416            assert_eq!(sink.len(), 2);
417            let commit = expect_commit(&mut sink);
418            expect_reset_timeout(&mut sink, Timeout::Retransmission);
419            commit
420        }
421
422        fn sta2_handle_commit(&mut self, commit1: CommitRx<'_>) -> (CommitTx, ConfirmTx) {
423            let mut sink = vec![];
424            self.sta2.handle_commit(&mut sink, &commit1.msg());
425            assert_eq!(sink.len(), 3);
426            let commit2 = expect_commit(&mut sink);
427            let confirm2 = expect_confirm(&mut sink);
428            expect_reset_timeout(&mut sink, Timeout::Retransmission);
429            (commit2, confirm2)
430        }
431
432        fn sta1_handle_commit(&mut self, commit2: CommitRx<'_>) -> ConfirmTx {
433            let mut sink = vec![];
434            self.sta1.handle_commit(&mut sink, &commit2.msg());
435            assert_eq!(sink.len(), 2);
436            let confirm1 = expect_confirm(&mut sink);
437            expect_reset_timeout(&mut sink, Timeout::Retransmission);
438            confirm1
439        }
440
441        fn sta1_handle_confirm(&mut self, confirm2: ConfirmRx<'_>) -> Key {
442            Self::__internal_handle_confirm(&mut self.sta1, confirm2.msg())
443        }
444
445        fn sta2_handle_confirm(&mut self, confirm1: ConfirmRx<'_>) -> Key {
446            Self::__internal_handle_confirm(&mut self.sta2, confirm1.msg())
447        }
448
449        fn __internal_handle_confirm(
450            sta: &mut Box<dyn SaeHandshake>,
451            confirm: ConfirmMsg<'_>,
452        ) -> Key {
453            let mut sink = vec![];
454            sta.handle_confirm(&mut sink, &confirm);
455            assert_eq!(sink.len(), 3);
456            expect_cancel_timeout(&mut sink, Timeout::Retransmission);
457            expect_reset_timeout(&mut sink, Timeout::KeyExpiration);
458            assert_matches!(sink.remove(0), SaeUpdate::Success(key) => key)
459        }
460    }
461
462    #[test_case(TestHandshakeConfig::looping(); "looping")]
463    #[test_case(TestHandshakeConfig::direct(); "direct")]
464    fn sae_handshake_success(cfg: TestHandshakeConfig) {
465        let mut handshake = TestHandshake::new(cfg);
466        let commit1 = handshake.sta1_init();
467        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
468        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
469        let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
470        let key2 = handshake.sta2_handle_confirm(confirm1.to_rx());
471        assert_eq!(key1, key2);
472    }
473
474    #[test]
475    fn password_mismatch() {
476        let akm = AKM_SAE;
477        let sta1 = new_sae_handshake(
478            19,
479            akm.clone(),
480            PweMethod::Loop,
481            Ssid::try_from(TEST_SSID).unwrap(),
482            Vec::from(TEST_PWD),
483            None, // Not required for PweMethod::Loop
484            *TEST_STA_A,
485            *TEST_STA_B,
486        )
487        .unwrap();
488        let sta2 = new_sae_handshake(
489            19,
490            akm,
491            PweMethod::Loop,
492            Ssid::try_from(TEST_SSID).unwrap(),
493            Vec::from("other_pwd"),
494            None, // Not required for PweMethod::Loop
495            *TEST_STA_B,
496            *TEST_STA_A,
497        )
498        .unwrap();
499        let mut handshake = TestHandshake { sta1, sta2 };
500
501        let commit1 = handshake.sta1_init();
502        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
503        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
504
505        let mut sink1 = vec![];
506        handshake.sta1.handle_confirm(&mut sink1, &confirm2.to_rx().msg());
507        let mut sink2 = vec![];
508        handshake.sta2.handle_confirm(&mut sink2, &confirm1.to_rx().msg());
509        // The confirm is dropped both ways.
510        assert_eq!(sink1.len(), 0);
511        assert_eq!(sink2.len(), 0);
512    }
513
514    #[test]
515    fn retry_commit_on_unexpected_confirm() {
516        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
517
518        let commit1 = handshake.sta1_init();
519        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
520        let mut sink = vec![];
521        handshake.sta1.handle_confirm(&mut sink, &confirm2.to_rx().msg());
522        assert_eq!(sink.len(), 2);
523        let commit1_retry = expect_commit(&mut sink);
524        assert_matches!(sink.remove(0), SaeUpdate::ResetTimeout(Timeout::Retransmission));
525
526        // We retransmit the same commit in response to a faulty confirm.
527        assert_eq!(commit1, commit1_retry);
528    }
529
530    #[test]
531    fn retry_commit_on_anti_clogging_token() {
532        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
533
534        let commit1 = handshake.sta1_init();
535
536        // Simulate an anti-clogging token sent to sta1.
537        let mut sink = vec![];
538        let anti_clogging_token = "anticloggingtokentext";
539        let act_msg = AntiCloggingTokenMsg {
540            group_id: 19,
541            anti_clogging_token: anti_clogging_token.as_bytes(),
542        };
543        handshake.sta1.handle_anti_clogging_token(&mut sink, &act_msg);
544        let commit1_retry = expect_commit(&mut sink);
545        assert_eq!(
546            commit1_retry.clone().to_rx().msg().anti_clogging_token,
547            Some(anti_clogging_token.as_bytes())
548        );
549
550        // Finish the handshake.
551        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1_retry.to_rx());
552        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
553        let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
554        let key2 = handshake.sta2_handle_confirm(confirm1.to_rx());
555        assert_eq!(key1, key2);
556    }
557
558    #[test]
559    fn ignore_wrong_confirm() {
560        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
561
562        let commit1 = handshake.sta1_init();
563        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
564        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
565
566        let mut sink = vec![];
567        let confirm2_wrong = ConfirmTx(frame::write_confirm(1, &[1; 32][..]));
568        handshake.sta1.handle_confirm(&mut sink, &confirm2_wrong.to_rx().msg());
569        assert_eq!(sink.len(), 0); // Ignored.
570
571        // STA1 should still be able to handle a subsequent correct confirm.
572        handshake.sta1_handle_confirm(confirm2.to_rx());
573    }
574
575    #[test]
576    fn handle_resent_commit() {
577        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
578        let commit1 = handshake.sta1_init();
579        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
580        let (commit2_retry, confirm2_retry) = handshake.sta2_handle_commit(commit1.to_rx());
581
582        // The resent commit message should be unchanged, but the resent confirm should increment
583        // sc and produce a different value.
584        assert_eq!(commit2, commit2_retry);
585        assert_eq!(confirm2.to_rx().msg().send_confirm, 1);
586        assert_eq!(confirm2_retry.to_rx().msg().send_confirm, 2);
587        assert!(confirm2.to_rx().msg().confirm != confirm2_retry.to_rx().msg().confirm);
588
589        // Now complete the handshake.
590        let confirm1 = handshake.sta1_handle_commit(commit2_retry.to_rx());
591        let key1 = handshake.sta1_handle_confirm(confirm2_retry.to_rx());
592        let key2 = handshake.sta2_handle_confirm(confirm1.to_rx());
593        assert_eq!(key1, key2);
594    }
595
596    #[test]
597    fn completed_handshake_handles_resent_confirm() {
598        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
599        let commit1 = handshake.sta1_init();
600        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
601        let (commit2_retry, confirm2_retry) = handshake.sta2_handle_commit(commit1.to_rx());
602        // Send STA1 the second confirm message first.
603        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
604        let key1 = handshake.sta1_handle_confirm(confirm2.clone().to_rx());
605
606        // STA1 should respond to the second confirm with its own confirm.
607        let mut sink = vec![];
608        handshake.sta1.handle_confirm(&mut sink, &confirm2_retry.to_rx().msg());
609        assert_eq!(sink.len(), 1);
610        let confirm1_retry = expect_confirm(&mut sink);
611        assert!(confirm1.to_rx().msg().confirm != confirm1_retry.to_rx().msg().confirm);
612        assert_eq!(confirm1_retry.to_rx().msg().send_confirm, u16::max_value());
613
614        // STA2 should complete the handshake with the resent confirm.
615        let key2 = handshake.sta2_handle_confirm(confirm1_retry.to_rx());
616        assert_eq!(key1, key2);
617
618        // STA1 should silently drop either of our confirm frames now.
619        handshake.sta1.handle_confirm(&mut sink, &confirm2_retry.to_rx().msg());
620        assert!(sink.is_empty());
621        handshake.sta1.handle_confirm(&mut sink, &confirm2.to_rx().msg());
622        assert!(sink.is_empty());
623
624        // STA1 should also silently drop an incorrect confirm, even if send_confirm is incremented.
625        let confirm2_wrong = ConfirmMsg { send_confirm: 10, confirm: &[0xab; 32][..] };
626        handshake.sta1.handle_confirm(&mut sink, &confirm2_wrong);
627        assert!(sink.is_empty());
628    }
629
630    #[test]
631    fn completed_handshake_ignores_commit() {
632        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
633        let commit1 = handshake.sta1_init();
634        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
635        handshake.sta1_handle_commit(commit2.to_rx());
636        handshake.sta1_handle_confirm(confirm2.clone().to_rx());
637
638        // STA1 has completed it's side of the handshake.
639        let mut sink = vec![];
640        handshake.sta1.handle_confirm(&mut sink, &confirm2.to_rx().msg());
641        assert!(sink.is_empty());
642    }
643
644    #[test]
645    fn bad_first_commit_rejects_auth() {
646        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
647        let commit1_wrong = CommitMsg {
648            group_id: 19,
649            scalar: &[0xab; 32][..],
650            element: &[0xcd; 64][..],
651            anti_clogging_token: None,
652        };
653
654        let mut sink = vec![];
655        handshake.sta1.handle_commit(&mut sink, &commit1_wrong);
656        assert_eq!(sink.len(), 1);
657        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::AuthFailed));
658    }
659
660    #[test]
661    fn bad_second_commit_ignored() {
662        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
663        let commit1 = handshake.sta1_init();
664        let (_commit1, _confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
665        let commit2_wrong = CommitMsg {
666            group_id: 19,
667            scalar: &[0xab; 32][..],
668            element: &[0xcd; 64][..],
669            anti_clogging_token: None,
670        };
671        let mut sink = vec![];
672        handshake.sta1.handle_commit(&mut sink, &commit2_wrong);
673        assert_eq!(sink.len(), 0);
674    }
675
676    #[test]
677    fn reflected_commit_discarded() {
678        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
679        let commit1 = handshake.sta1_init();
680
681        let mut sink = vec![];
682        handshake.sta1.handle_commit(&mut sink, &commit1.to_rx().msg());
683        assert_eq!(sink.len(), 1);
684        assert_matches!(sink.remove(0), SaeUpdate::ResetTimeout(Timeout::Retransmission));
685    }
686
687    #[test]
688    fn maximum_commit_retries() {
689        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
690        let commit1 = handshake.sta1_init();
691        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
692
693        // STA2 should allow MAX_RETRIES_PER_EXCHANGE retry operations before giving up.
694        for i in 0..MAX_RETRIES_PER_EXCHANGE {
695            let (commit2_retry, confirm2_retry) =
696                handshake.sta2_handle_commit(commit1.clone().to_rx());
697            assert_eq!(commit2, commit2_retry);
698            assert_eq!(confirm2_retry.to_rx().msg().send_confirm, i + 2);
699        }
700
701        // The last straw!
702        let mut sink = vec![];
703        handshake.sta2.handle_commit(&mut sink, &commit1.to_rx().msg());
704        assert_eq!(sink.len(), 1);
705        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
706    }
707
708    #[test]
709    fn completed_exchange_fails_after_retries() {
710        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
711        let commit1 = handshake.sta1_init();
712        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
713
714        // STA2 should allow MAX_RETRIES_PER_EXCHANGE retry operations before giving up. We subtract 1
715        // here for the reason explained in the note below.
716        for i in 0..(MAX_RETRIES_PER_EXCHANGE - 1) {
717            let (commit2_retry, confirm2_retry) =
718                handshake.sta2_handle_commit(commit1.clone().to_rx());
719            assert_eq!(commit2, commit2_retry);
720            assert_eq!(confirm2_retry.to_rx().msg().send_confirm, i + 2);
721        }
722
723        let mut sink = vec![];
724
725        // Generate 3 different confirm messages for our testing...
726        let confirm1_sc1 = handshake.sta1_handle_commit(commit2.clone().to_rx());
727        handshake.sta1.handle_commit(&mut sink, &commit2.to_rx().msg());
728        assert_eq!(sink.len(), 3);
729        sink.remove(0);
730        let confirm1_sc2 = expect_confirm(&mut sink);
731        sink.clear();
732        let confirm1_invalid = ConfirmMsg { send_confirm: 3, confirm: &[0xab; 32][..] };
733
734        // STA2 completes the handshake. However, one more indication that STA1 is misbehaving will
735        // immediately kill the authentication.
736        handshake.sta2_handle_confirm(confirm1_sc1.clone().to_rx());
737
738        // NOTE: We run all of the operations here two times. This is because of a quirk in the SAE
739        // state machine: while only certain operations *increment* sync, all invalid operations
740        // will *check* sync. We can test whether sync is being incremented by running twice to see
741        // if this pushes us over the MAX_RETRIES_PER_EXCHANGE threshold.
742
743        // STA2 ignores commits.
744        handshake.sta2.handle_commit(&mut sink, &commit1.to_rx().msg());
745        handshake.sta2.handle_commit(&mut sink, &commit1.to_rx().msg());
746        assert_eq!(sink.len(), 0);
747
748        // STA2 ignores invalid confirm.
749        handshake.sta2.handle_confirm(&mut sink, &confirm1_invalid);
750        handshake.sta2.handle_confirm(&mut sink, &confirm1_invalid);
751        assert_eq!(sink.len(), 0);
752
753        // STA2 ignores old confirm.
754        handshake.sta2.handle_confirm(&mut sink, &confirm1_sc1.to_rx().msg());
755        handshake.sta2.handle_confirm(&mut sink, &confirm1_sc1.to_rx().msg());
756        assert_eq!(sink.len(), 0);
757
758        // But another valid confirm increments sync!
759        handshake.sta2.handle_confirm(&mut sink, &confirm1_sc2.to_rx().msg());
760        assert_eq!(sink.len(), 1);
761        expect_confirm(&mut sink);
762        handshake.sta2.handle_confirm(&mut sink, &confirm1_sc2.to_rx().msg());
763        assert_eq!(sink.len(), 1);
764        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
765    }
766
767    #[test]
768    fn resend_commit_after_retransmission_timeout() {
769        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
770        let commit1 = handshake.sta1_init();
771
772        let mut sink = vec![];
773        handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
774        let commit1_retry = expect_commit(&mut sink);
775        expect_reset_timeout(&mut sink, Timeout::Retransmission);
776        assert_eq!(commit1, commit1_retry);
777    }
778
779    #[test]
780    fn resend_confirm_after_retransmission_timeout() {
781        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
782        let commit1 = handshake.sta1_init();
783        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
784
785        let mut sink = vec![];
786        handshake.sta2.handle_timeout(&mut sink, Timeout::Retransmission);
787        // On timeout we should only send commit and confirm.
788        let confirm2_retry = expect_confirm(&mut sink);
789        expect_reset_timeout(&mut sink, Timeout::Retransmission);
790        assert_eq!(
791            confirm2.to_rx().msg().send_confirm + 1,
792            confirm2_retry.to_rx().msg().send_confirm
793        );
794    }
795
796    #[test]
797    fn abort_commit_after_too_many_timeouts() {
798        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
799        let commit1 = handshake.sta1_init();
800
801        let mut sink = vec![];
802        for i in 0..MAX_RETRIES_PER_EXCHANGE {
803            handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
804            let commit1_retry = expect_commit(&mut sink);
805            expect_reset_timeout(&mut sink, Timeout::Retransmission);
806            assert_eq!(commit1, commit1_retry);
807        }
808
809        // This camel can't hold another straw!
810        handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
811        assert_eq!(sink.len(), 1);
812        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
813    }
814
815    #[test]
816    fn abort_confirm_after_too_many_timeouts() {
817        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
818        let commit1 = handshake.sta1_init();
819        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
820
821        let mut sink = vec![];
822        for i in 0..MAX_RETRIES_PER_EXCHANGE {
823            handshake.sta2.handle_timeout(&mut sink, Timeout::Retransmission);
824            // On timeout we should only send commit and confirm.
825            let confirm2_retry = expect_confirm(&mut sink);
826            expect_reset_timeout(&mut sink, Timeout::Retransmission);
827            assert_eq!(
828                confirm2.to_rx().msg().send_confirm + i + 1,
829                confirm2_retry.to_rx().msg().send_confirm
830            );
831        }
832
833        handshake.sta2.handle_timeout(&mut sink, Timeout::Retransmission);
834        assert_eq!(sink.len(), 1);
835        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
836    }
837
838    #[test]
839    fn ignore_unexpected_retransmit_timeout() {
840        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
841        let mut sink = vec![];
842        // Timeout::Retransmission is ignored while in New state.
843        handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
844        assert!(sink.is_empty());
845
846        let commit1 = handshake.sta1_init();
847        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
848        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
849        let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
850
851        // Timeout::Retransmission is ignored while in Accepted state.
852        handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
853        assert!(sink.is_empty());
854    }
855
856    #[test]
857    fn fail_on_early_key_expiration() {
858        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
859        handshake.sta1_init();
860
861        // Early key expiration indicates that something has gone very wrong, so we abort.
862        let mut sink = vec![];
863        handshake.sta1.handle_timeout(&mut sink, Timeout::KeyExpiration);
864        assert_eq!(sink.len(), 1);
865        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::InternalError(_)));
866    }
867
868    #[test]
869    fn key_expiration_timeout() {
870        let mut handshake = TestHandshake::new(TestHandshakeConfig::looping());
871        // Timeout::KeyExpiration is only expected once our handshake has completed.
872        let commit1 = handshake.sta1_init();
873        let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
874        let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
875        let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
876
877        let mut sink = vec![];
878        handshake.sta1.handle_timeout(&mut sink, Timeout::KeyExpiration);
879        assert_eq!(sink.len(), 1);
880        assert_matches!(sink.remove(0), SaeUpdate::Reject(RejectReason::KeyExpiration));
881    }
882}