wlan_rsn/auth/
mod.rs

1// Copyright 2018 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
5pub mod psk;
6
7use crate::Error;
8use crate::key::exchange::Key;
9use crate::rsna::{
10    AuthRejectedReason, AuthStatus, Dot11VerifiedKeyFrame, SecAssocUpdate, UpdateSink,
11};
12use fidl_fuchsia_wlan_mlme::SaeFrame;
13use ieee80211::{MacAddr, MacAddrBytes, Ssid};
14use log::warn;
15use wlan_common::ie::rsn::akm::{AKM_OWE, AKM_SAE};
16use wlan_fcg_crypto::{self as sae, owe};
17use zerocopy::SplitByteSlice;
18
19/// IEEE Std 802.11-2016, 12.4.4.1
20/// Elliptic curve group 19 is the default supported group -- all SAE peers must support it, and in
21/// practice it is generally used.
22const DEFAULT_GROUP_ID: u16 = 19;
23
24#[derive(Error, Debug)]
25pub enum AuthError {
26    #[error("Failed to construct auth method from the given configuration: {:?}", _0)]
27    FailedConstruction(anyhow::Error),
28    #[error("Non-SAE auth method received an SAE event")]
29    UnexpectedSaeEvent,
30    #[error("Non-OWE auth method received an OWE event")]
31    UnexpectedOweEvent,
32    #[error("Failed to initiate OWE: {:?}", _0)]
33    FailedInitiateOwe(anyhow::Error),
34    #[error("Failed to handle OWE public key: {:?}", _0)]
35    FailedHandleOwePublicKey(anyhow::Error),
36}
37
38pub struct SaeData {
39    peer: MacAddr,
40    pub pmk: Option<sae::Key>,
41    handshake: Box<dyn sae::SaeHandshake>,
42    // Our timer interface does not support cancellation, so we instead use a counter to skip
43    // outdated timouts.
44    retransmit_timeout_id: u64,
45}
46
47pub struct OweData {
48    pub pmk: Option<Vec<u8>>,
49    handshake: Box<dyn owe::ClientOweHandshake>,
50}
51
52#[derive(Debug, PartialEq, Clone)]
53pub enum Config {
54    ComputedPsk(psk::Psk),
55    Sae {
56        ssid: Ssid,
57        password: Vec<u8>,
58        mac: MacAddr,
59        peer_mac: MacAddr,
60        pwe_method: sae::PweMethod,
61    },
62    DriverSae {
63        password: Vec<u8>,
64    },
65    Owe,
66}
67
68impl Config {
69    pub fn method_name(&self) -> MethodName {
70        match self {
71            Config::ComputedPsk(_) => MethodName::Psk,
72            Config::Sae { .. } | Config::DriverSae { .. } => MethodName::Sae,
73            Config::Owe => MethodName::Owe,
74        }
75    }
76}
77
78pub enum Method {
79    Psk(psk::Psk),
80    Sae(SaeData),
81    /// SAE handled in the driver/firmware, so the PMK will just eventually arrive.
82    DriverSae(Option<sae::Key>),
83    Owe(OweData),
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum MethodName {
88    Psk,
89    Sae,
90    Owe,
91}
92
93impl std::fmt::Debug for Method {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
95        match self {
96            Self::Psk(psk) => write!(f, "Method::Psk({:?})", psk),
97            Self::Sae(sae_data) => write!(
98                f,
99                "Method::Sae {{ peer: {:?}, pmk: {}, .. }}",
100                sae_data.peer,
101                match sae_data.pmk {
102                    Some(_) => "Some(_)",
103                    None => "None",
104                }
105            ),
106            Self::DriverSae(key) => write!(f, "Method::DriverSae({:?})", key),
107            Self::Owe(owe_data) => write!(
108                f,
109                "Method::Owe {{ pmk: {}, .. }}",
110                match owe_data.pmk {
111                    Some(_) => "Some(_)",
112                    None => "None",
113                }
114            ),
115        }
116    }
117}
118
119impl Method {
120    pub fn from_config(cfg: Config) -> Result<Method, AuthError> {
121        match cfg {
122            Config::ComputedPsk(psk) => Ok(Method::Psk(psk)),
123            Config::Sae { ssid, password, mac, peer_mac, pwe_method } => {
124                // TODO(https://fxbug.dev/42173568): Use PweMethod::Direct here for SAE Hash-to-Element.
125                let handshake = sae::new_sae_handshake(
126                    DEFAULT_GROUP_ID,
127                    AKM_SAE,
128                    pwe_method,
129                    ssid,
130                    password,
131                    None, // Not required for PweMethod::Loop
132                    mac,
133                    peer_mac.clone(),
134                )
135                .map_err(AuthError::FailedConstruction)?;
136                Ok(Method::Sae(SaeData {
137                    peer: peer_mac,
138                    pmk: None,
139                    handshake,
140                    retransmit_timeout_id: 0,
141                }))
142            }
143            Config::DriverSae { .. } => Ok(Method::DriverSae(None)),
144            Config::Owe => {
145                let handshake = owe::new_client_owe_handshake(DEFAULT_GROUP_ID, AKM_OWE)
146                    .map_err(AuthError::FailedConstruction)?;
147                Ok(Method::Owe(OweData { pmk: None, handshake }))
148            }
149        }
150    }
151
152    // Unused as only PSK is supported so far.
153    pub fn on_eapol_key_frame<B: SplitByteSlice>(
154        &self,
155        _update_sink: &mut UpdateSink,
156        _frame: Dot11VerifiedKeyFrame<B>,
157    ) -> Result<(), AuthError> {
158        Ok(())
159    }
160
161    /// Currently only used so that an SAE handshake managed in firmware can send
162    /// the PMK upward.
163    pub fn on_pmk_available(
164        &mut self,
165        pmk: &[u8],
166        pmkid: &[u8],
167        assoc_update_sink: &mut UpdateSink,
168    ) -> Result<(), AuthError> {
169        match self {
170            Method::DriverSae(key) => {
171                key.replace(sae::Key { pmk: pmk.to_vec(), pmkid: pmkid.to_vec() });
172                assoc_update_sink.push(SecAssocUpdate::Key(Key::Pmk(pmk.to_vec())));
173                Ok(())
174            }
175            _ => Err(AuthError::UnexpectedSaeEvent),
176        }
177    }
178
179    pub fn on_sae_handshake_ind(
180        &mut self,
181        assoc_update_sink: &mut UpdateSink,
182    ) -> Result<(), AuthError> {
183        match self {
184            Method::Sae(sae_data) => {
185                let mut sae_update_sink = sae::SaeUpdateSink::default();
186                sae_data.handshake.initiate_sae(&mut sae_update_sink);
187                process_sae_updates(sae_data, assoc_update_sink, sae_update_sink);
188                Ok(())
189            }
190            _ => Err(AuthError::UnexpectedSaeEvent),
191        }
192    }
193
194    pub fn on_sae_frame_rx(
195        &mut self,
196        assoc_update_sink: &mut UpdateSink,
197        frame: SaeFrame,
198    ) -> Result<(), AuthError> {
199        match self {
200            Method::Sae(sae_data) => {
201                let mut sae_update_sink = sae::SaeUpdateSink::default();
202                let frame_rx = sae::AuthFrameRx {
203                    seq: frame.seq_num,
204                    status_code: frame.status_code,
205                    body: &frame.sae_fields[..],
206                };
207                sae_data.handshake.handle_frame(&mut sae_update_sink, &frame_rx);
208                process_sae_updates(sae_data, assoc_update_sink, sae_update_sink);
209                Ok(())
210            }
211            _ => Err(AuthError::UnexpectedSaeEvent),
212        }
213    }
214
215    pub fn on_sae_timeout(
216        &mut self,
217        assoc_update_sink: &mut UpdateSink,
218        event_id: u64,
219    ) -> Result<(), AuthError> {
220        match self {
221            Method::Sae(sae_data) => {
222                if sae_data.retransmit_timeout_id == event_id {
223                    sae_data.retransmit_timeout_id += 1;
224                    let mut sae_update_sink = sae::SaeUpdateSink::default();
225                    sae_data
226                        .handshake
227                        .handle_timeout(&mut sae_update_sink, sae::Timeout::Retransmission);
228                    process_sae_updates(sae_data, assoc_update_sink, sae_update_sink);
229                }
230                Ok(())
231            }
232            _ => Err(AuthError::UnexpectedSaeEvent),
233        }
234    }
235
236    pub fn initiate_owe(&mut self, assoc_update_sink: &mut UpdateSink) -> Result<(), AuthError> {
237        match self {
238            Method::Owe(owe_data) => {
239                let mut owe_update_sink = owe::OweUpdateSink::default();
240                owe_data
241                    .handshake
242                    .initiate_owe(&mut owe_update_sink)
243                    .map_err(AuthError::FailedInitiateOwe)?;
244                process_owe_updates(owe_data, assoc_update_sink, owe_update_sink);
245                Ok(())
246            }
247            _ => Err(AuthError::UnexpectedOweEvent),
248        }
249    }
250
251    pub fn on_owe_public_key_rx(
252        &mut self,
253        assoc_update_sink: &mut UpdateSink,
254        group: u16,
255        public_key: Vec<u8>,
256    ) -> Result<(), AuthError> {
257        match self {
258            Method::Owe(owe_data) => {
259                let mut owe_update_sink = owe::OweUpdateSink::default();
260                owe_data
261                    .handshake
262                    .handle_public_key(&mut owe_update_sink, group, public_key)
263                    .map_err(AuthError::FailedHandleOwePublicKey)?;
264                process_owe_updates(owe_data, assoc_update_sink, owe_update_sink);
265                Ok(())
266            }
267            _ => Err(AuthError::UnexpectedOweEvent),
268        }
269    }
270}
271
272fn process_sae_updates(
273    sae_data: &mut SaeData,
274    assoc_update_sink: &mut UpdateSink,
275    sae_update_sink: sae::SaeUpdateSink,
276) {
277    for sae_update in sae_update_sink {
278        match sae_update {
279            sae::SaeUpdate::SendFrame(frame) => {
280                let sae_frame = SaeFrame {
281                    peer_sta_address: sae_data.peer.clone().to_array(),
282                    status_code: frame.status_code,
283                    seq_num: frame.seq,
284                    sae_fields: frame.body,
285                };
286                assoc_update_sink.push(SecAssocUpdate::TxSaeFrame(sae_frame));
287            }
288            sae::SaeUpdate::Success(key) => {
289                sae_data.pmk.replace(key.clone());
290                assoc_update_sink.push(SecAssocUpdate::Key(Key::Pmk(key.pmk)));
291                assoc_update_sink.push(SecAssocUpdate::SaeAuthStatus(AuthStatus::Success));
292            }
293            sae::SaeUpdate::Reject(reason) => {
294                warn!("SAE handshake rejected: {:?}", reason);
295                let status = match reason {
296                    sae::RejectReason::AuthFailed => {
297                        AuthStatus::Rejected(AuthRejectedReason::AuthFailed)
298                    }
299                    sae::RejectReason::KeyExpiration => {
300                        AuthStatus::Rejected(AuthRejectedReason::PmksaExpired)
301                    }
302                    sae::RejectReason::TooManyRetries => {
303                        AuthStatus::Rejected(AuthRejectedReason::TooManyRetries)
304                    }
305                    sae::RejectReason::InternalError(_) => AuthStatus::InternalError,
306                };
307                assoc_update_sink.push(SecAssocUpdate::SaeAuthStatus(status));
308            }
309            sae::SaeUpdate::ResetTimeout(timer) => {
310                match timer {
311                    sae::Timeout::KeyExpiration => (), // We don't use this event.
312                    sae::Timeout::Retransmission => {
313                        sae_data.retransmit_timeout_id += 1;
314                        assoc_update_sink.push(SecAssocUpdate::ScheduleSaeTimeout(
315                            sae_data.retransmit_timeout_id,
316                        ));
317                    }
318                };
319            }
320            sae::SaeUpdate::CancelTimeout(timer) => {
321                match timer {
322                    sae::Timeout::KeyExpiration => (),
323                    sae::Timeout::Retransmission => {
324                        sae_data.retransmit_timeout_id += 1;
325                    }
326                };
327            }
328        }
329    }
330}
331
332fn process_owe_updates(
333    owe_data: &mut OweData,
334    assoc_update_sink: &mut UpdateSink,
335    owe_update_sink: owe::OweUpdateSink,
336) {
337    for owe_update in owe_update_sink {
338        match owe_update {
339            owe::OweUpdate::TxPublicKey { group_id, key } => {
340                assoc_update_sink.push(SecAssocUpdate::TxOwePublicKey { group_id, key });
341            }
342            owe::OweUpdate::Success { key } => {
343                owe_data.pmk.replace(key.clone());
344                assoc_update_sink.push(SecAssocUpdate::Key(Key::Pmk(key)));
345            }
346        }
347    }
348}
349
350#[cfg(test)]
351mod test {
352    use super::*;
353    use assert_matches::assert_matches;
354    use fuchsia_sync::Mutex;
355    use std::sync::Arc;
356
357    #[test]
358    fn psk_rejects_sae() {
359        let mut auth = Method::from_config(Config::ComputedPsk(Box::new([0x8; 16])))
360            .expect("Failed to construct PSK auth method");
361        let mut sink = UpdateSink::default();
362        auth.on_sae_handshake_ind(&mut sink).expect_err("PSK auth method accepted SAE ind");
363        let frame = SaeFrame {
364            peer_sta_address: [0xaa; 6],
365            status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
366            seq_num: 1,
367            sae_fields: vec![0u8; 10],
368        };
369        auth.on_sae_frame_rx(&mut sink, frame).expect_err("PSK auth method accepted SAE frame");
370        // No updates should be queued for these invalid ops.
371        assert!(sink.is_empty());
372    }
373
374    #[derive(Default)]
375    struct SaeCounter {
376        initiated: bool,
377        handled_commits: u32,
378        handled_confirms: u32,
379        handled_timeouts: u32,
380    }
381
382    struct DummySae(Arc<Mutex<SaeCounter>>);
383
384    // This sends dummy frames as though it is the SAE initiator.
385    impl sae::SaeHandshake for DummySae {
386        fn initiate_sae(&mut self, sink: &mut sae::SaeUpdateSink) {
387            self.0.lock().initiated = true;
388            sink.push(sae::SaeUpdate::SendFrame(sae::AuthFrameTx {
389                seq: 1,
390                status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
391                body: vec![],
392            }));
393        }
394        fn handle_commit(
395            &mut self,
396            _sink: &mut sae::SaeUpdateSink,
397            _commit_msg: &sae::CommitMsg<'_>,
398        ) {
399            assert!(self.0.lock().initiated);
400            self.0.lock().handled_commits += 1;
401        }
402        fn handle_confirm(
403            &mut self,
404            sink: &mut sae::SaeUpdateSink,
405            _confirm_msg: &sae::ConfirmMsg<'_>,
406        ) {
407            assert!(self.0.lock().initiated);
408            self.0.lock().handled_confirms += 1;
409            sink.push(sae::SaeUpdate::SendFrame(sae::AuthFrameTx {
410                seq: 2,
411                status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
412                body: vec![],
413            }));
414            sink.push(sae::SaeUpdate::Success(sae::Key { pmk: vec![0xaa], pmkid: vec![0xbb] }))
415        }
416        fn handle_anti_clogging_token(
417            &mut self,
418            _sink: &mut sae::SaeUpdateSink,
419            _msg: &sae::AntiCloggingTokenMsg<'_>,
420        ) {
421            panic!("The SAE initiator should never receive an anti-clogging token.");
422        }
423        fn handle_timeout(&mut self, _sink: &mut sae::SaeUpdateSink, _timeout: sae::Timeout) {
424            self.0.lock().handled_timeouts += 1;
425        }
426    }
427
428    // These are not valid commit and confirm bodies, but are appropriately sized so they will parse.
429
430    const COMMIT: [u8; 98] = [
431        0x13, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
432        0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
433        0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
434        0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
435        0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
436        0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
437        0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
438    ];
439    const CONFIRM: [u8; 34] = [
440        0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
441        0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
442        0xbb, 0xbb, 0xbb, 0xbb,
443    ];
444
445    #[test]
446    fn sae_executes_handshake() {
447        let sae_counter = Arc::new(Mutex::new(SaeCounter::default()));
448        let mut auth = Method::Sae(SaeData {
449            peer: MacAddr::from([0xaa; 6]),
450            pmk: None,
451            handshake: Box::new(DummySae(sae_counter.clone())),
452            retransmit_timeout_id: 0,
453        });
454        let mut sink = UpdateSink::default();
455
456        auth.on_sae_handshake_ind(&mut sink).expect("SAE handshake should accept SAE ind");
457        assert!(sae_counter.lock().initiated);
458        assert_matches!(sink.pop(), Some(SecAssocUpdate::TxSaeFrame(_)));
459
460        let commit_frame = SaeFrame {
461            peer_sta_address: [0xaa; 6],
462            status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
463            seq_num: 1,
464            sae_fields: COMMIT.to_vec(),
465        };
466        auth.on_sae_frame_rx(&mut sink, commit_frame).expect("SAE handshake should accept commit");
467        assert_eq!(sae_counter.lock().handled_commits, 1);
468        assert!(sink.is_empty());
469
470        let confirm_frame = SaeFrame {
471            peer_sta_address: [0xaa; 6],
472            status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
473            seq_num: 2,
474            sae_fields: CONFIRM.to_vec(),
475        };
476        auth.on_sae_frame_rx(&mut sink, confirm_frame)
477            .expect("SAE handshake should accept confirm");
478        assert_eq!(sae_counter.lock().handled_confirms, 1);
479        assert_eq!(sink.len(), 3);
480        assert_matches!(sink.remove(0), SecAssocUpdate::TxSaeFrame(_));
481        assert_matches!(sink.remove(0), SecAssocUpdate::Key(_));
482        assert_matches!(sink.remove(0), SecAssocUpdate::SaeAuthStatus(AuthStatus::Success));
483        match auth {
484            Method::Sae(sae_data) => assert!(sae_data.pmk.is_some()),
485            _ => unreachable!(),
486        };
487    }
488
489    #[test]
490    fn sae_handles_current_timeouts() {
491        let sae_counter = Arc::new(Mutex::new(SaeCounter::default()));
492        let mut sae = Method::Sae(SaeData {
493            peer: MacAddr::from([0xaa; 6]),
494            pmk: None,
495            handshake: Box::new(DummySae(sae_counter.clone())),
496            retransmit_timeout_id: 0,
497        });
498        let mut sink = UpdateSink::default();
499
500        if let Method::Sae(data) = &mut sae {
501            process_sae_updates(
502                data,
503                &mut sink,
504                vec![sae::SaeUpdate::ResetTimeout(sae::Timeout::Retransmission)],
505            );
506        };
507        let event_id = assert_matches!(sink.pop(),
508            Some(SecAssocUpdate::ScheduleSaeTimeout(id)) => id
509        );
510        sae.on_sae_timeout(&mut sink, event_id).expect("SAE handshake should accept timeout");
511        assert_eq!(sae_counter.lock().handled_timeouts, 1);
512        // Don't handle the same timeout twice.
513        sae.on_sae_timeout(&mut sink, event_id).expect("SAE handshake should accept timeout");
514        assert_eq!(sae_counter.lock().handled_timeouts, 1); // No timeout handled.
515
516        // Don't handle a cancelled timeout.
517        if let Method::Sae(data) = &mut sae {
518            process_sae_updates(
519                data,
520                &mut sink,
521                vec![
522                    sae::SaeUpdate::ResetTimeout(sae::Timeout::Retransmission),
523                    sae::SaeUpdate::CancelTimeout(sae::Timeout::Retransmission),
524                ],
525            );
526        };
527        let event_id = assert_matches!(sink.pop(),
528                Some(SecAssocUpdate::ScheduleSaeTimeout(id)) => id
529        );
530        sae.on_sae_timeout(&mut sink, event_id).expect("SAE handshake should accept timeout");
531        assert_eq!(sae_counter.lock().handled_timeouts, 1); // No timeout handled.
532    }
533
534    #[test]
535    fn sae_key_expiration_no_op() {
536        let sae_counter = Arc::new(Mutex::new(SaeCounter::default()));
537        let mut data = SaeData {
538            peer: MacAddr::from([0xaa; 6]),
539            pmk: None,
540            handshake: Box::new(DummySae(sae_counter.clone())),
541            retransmit_timeout_id: 0,
542        };
543        let mut sink = UpdateSink::new();
544        process_sae_updates(
545            &mut data,
546            &mut sink,
547            vec![
548                sae::SaeUpdate::ResetTimeout(sae::Timeout::KeyExpiration),
549                sae::SaeUpdate::CancelTimeout(sae::Timeout::KeyExpiration),
550            ],
551        );
552        assert!(sink.is_empty(), "KeyExpiration should not produce updates.");
553    }
554
555    #[test]
556    fn driver_sae_handles_pmk() {
557        let mut auth = Method::from_config(Config::DriverSae { password: vec![0xbb; 8] })
558            .expect("Failed to construct PSK auth method");
559        let mut sink = UpdateSink::default();
560        auth.on_pmk_available(&[0xcc; 8][..], &[0xdd; 8][..], &mut sink)
561            .expect("Driver SAE should handle on_pmk_available");
562        assert_eq!(sink.len(), 1);
563        let pmk = assert_matches!(sink.get(0), Some(SecAssocUpdate::Key(Key::Pmk(pmk))) => pmk);
564        assert_eq!(*pmk, vec![0xcc; 8]);
565    }
566
567    #[test]
568    fn driver_sae_rejects_sme_sae_calls() {
569        let mut auth = Method::from_config(Config::DriverSae { password: vec![0xbb; 8] })
570            .expect("Failed to construct PSK auth method");
571        let mut sink = UpdateSink::default();
572        auth.on_sae_handshake_ind(&mut sink).expect_err("Driver SAE shouldn't handle SAE ind");
573        let frame = SaeFrame {
574            peer_sta_address: [0xaa; 6],
575            status_code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success,
576            seq_num: 1,
577            sae_fields: COMMIT.to_vec(),
578        };
579        auth.on_sae_frame_rx(&mut sink, frame).expect_err("Driver SAE shouldn't handle frames");
580        auth.on_sae_timeout(&mut sink, 0).expect_err("Driver SAE shouldn't handle SAE timeouts");
581        assert!(sink.is_empty());
582    }
583
584    #[test]
585    fn owe_initiates_and_handles_public_key() {
586        let mut auth =
587            Method::from_config(Config::Owe).expect("Failed to construct OWE auth method");
588        let mut sink = UpdateSink::default();
589
590        auth.initiate_owe(&mut sink).expect("OWE handshake should initiate");
591        assert_eq!(sink.len(), 1);
592        let (group_id, key) = assert_matches!(sink.remove(0),
593            SecAssocUpdate::TxOwePublicKey { group_id, key } => (group_id, key)
594        );
595        assert_eq!(group_id, 19);
596        assert!(!key.is_empty());
597
598        const AP_PUBLIC_KEY: [u8; 32] = [
599            0xa9, 0x8c, 0x47, 0xc5, 0xbd, 0xcf, 0x1d, 0x5e, 0x2c, 0x3c, 0x95, 0x8e, 0x10, 0xf3,
600            0x71, 0x61, 0xc4, 0x61, 0x02, 0x13, 0x22, 0xb2, 0x95, 0xf6, 0xc7, 0x81, 0x1e, 0xf8,
601            0x14, 0xc6, 0x03, 0x17,
602        ];
603        auth.on_owe_public_key_rx(&mut sink, group_id, AP_PUBLIC_KEY.to_vec())
604            .expect("OWE handshake should handle public key");
605        assert_eq!(sink.len(), 1);
606        let pmk = assert_matches!(sink.remove(0), SecAssocUpdate::Key(Key::Pmk(pmk)) => pmk);
607        assert!(!pmk.is_empty());
608    }
609}