wlan_rsn/
lib.rs

1// Copyright 2021 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
5#![cfg_attr(feature = "benchmarks", feature(test))]
6
7use thiserror::Error;
8
9// TODO(hahnr): Limit exports and rearrange modules.
10
11mod aes;
12pub mod auth;
13mod integrity;
14pub mod key;
15mod key_data;
16mod keywrap;
17pub mod nonce;
18mod prf;
19pub mod rsna;
20
21use crate::aes::AesError;
22use crate::key::exchange::handshake::{HandshakeMessageNumber, fourway, group_key};
23use crate::key::exchange::{self};
24use crate::rsna::esssa::EssSa;
25use crate::rsna::{Role, UpdateSink};
26use fidl_fuchsia_wlan_mlme::{EapolResultCode, SaeFrame};
27use fuchsia_sync::Mutex;
28use ieee80211::{MacAddr, Ssid};
29use log::warn;
30use std::sync::Arc;
31use wlan_common::ie::rsn::cipher::Cipher;
32use wlan_common::ie::rsn::rsne::{self, Rsne};
33use wlan_common::ie::wpa::WpaIe;
34use zerocopy::SplitByteSlice;
35
36pub use crate::auth::psk;
37pub use crate::key::gtk::{self, GtkProvider};
38pub use crate::key::igtk::{self, IgtkProvider};
39pub use crate::rsna::NegotiatedProtection;
40
41#[derive(Debug)]
42pub struct Supplicant {
43    auth_method: auth::Method,
44    esssa: EssSa,
45    pub auth_cfg: auth::Config,
46}
47
48/// Any information (i.e. info elements) used to negotiate protection on an RSN.
49#[derive(Debug, Clone, PartialEq)]
50pub enum ProtectionInfo {
51    Rsne(Rsne),
52    LegacyWpa(WpaIe),
53}
54
55fn extract_sae_key_helper(update_sink: &UpdateSink) -> Option<Vec<u8>> {
56    for update in &update_sink[..] {
57        if let rsna::SecAssocUpdate::Key(key::exchange::Key::Pmk(pmk)) = update {
58            return Some(pmk.clone());
59        }
60    }
61    None
62}
63
64impl Supplicant {
65    /// WPA personal supplicant which supports 4-Way- and Group-Key Handshakes.
66    pub fn new_wpa_personal(
67        nonce_rdr: Arc<nonce::NonceReader>,
68        auth_cfg: auth::Config,
69        s_addr: MacAddr,
70        s_protection: ProtectionInfo,
71        a_addr: MacAddr,
72        a_protection: ProtectionInfo,
73    ) -> Result<Supplicant, anyhow::Error> {
74        let negotiated_protection = NegotiatedProtection::from_protection(&s_protection)?;
75        let gtk_exch_cfg = Some(exchange::Config::GroupKeyHandshake(group_key::Config {
76            role: Role::Supplicant,
77            protection: negotiated_protection.clone(),
78        }));
79
80        let auth_method = auth::Method::from_config(auth_cfg.clone())?;
81        let pmk = match auth_cfg.clone() {
82            auth::Config::ComputedPsk(psk) => Some(psk.to_vec()),
83            _ => None,
84        };
85        let esssa = EssSa::new(
86            Role::Supplicant,
87            pmk,
88            negotiated_protection,
89            exchange::Config::FourWayHandshake(fourway::Config::new(
90                Role::Supplicant,
91                s_addr,
92                s_protection,
93                a_addr,
94                a_protection,
95                nonce_rdr,
96                None,
97                None,
98            )?),
99            gtk_exch_cfg,
100        )?;
101
102        Ok(Supplicant { auth_method, esssa, auth_cfg })
103    }
104
105    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
106    pub fn start(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
107        self.esssa.initiate(update_sink)
108    }
109
110    pub fn reset(&mut self) {
111        // The replay counter must be reset so subsequent associations are not ignored.
112        self.esssa.reset_replay_counter();
113        self.esssa.reset_security_associations();
114    }
115
116    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
117    pub fn on_eapol_frame<B: SplitByteSlice>(
118        &mut self,
119        update_sink: &mut UpdateSink,
120        frame: eapol::Frame<B>,
121    ) -> Result<(), Error> {
122        self.esssa.on_eapol_frame(update_sink, frame)
123    }
124
125    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
126    pub fn on_eapol_conf(
127        &mut self,
128        update_sink: &mut UpdateSink,
129        result: EapolResultCode,
130    ) -> Result<(), Error> {
131        self.esssa.on_eapol_conf(update_sink, result)
132    }
133
134    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
135    pub fn on_rsna_retransmission_timeout(
136        &mut self,
137        update_sink: &mut UpdateSink,
138    ) -> Result<(), Error> {
139        self.esssa.on_rsna_retransmission_timeout(update_sink)
140    }
141
142    /// Can be called at anytime to determine the reason why the RSNA
143    /// is not complete. This is normally called when
144    /// the higher layer, usually SME, determines establishing the
145    /// RSNA failed, likely because of an expired timeout.
146    pub fn incomplete_reason(&self) -> Error {
147        self.esssa.incomplete_reason()
148    }
149
150    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
151    fn extract_sae_key(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
152        if let Some(pmk) = extract_sae_key_helper(&update_sink) {
153            self.esssa.on_pmk_available(update_sink, pmk)?;
154        }
155        Ok(())
156    }
157
158    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
159    pub fn on_pmk_available(
160        &mut self,
161        update_sink: &mut UpdateSink,
162        pmk: &[u8],
163        pmkid: &[u8],
164    ) -> Result<(), Error> {
165        let mut updates = UpdateSink::new();
166        self.auth_method.on_pmk_available(pmk, pmkid, &mut updates)?;
167        self.extract_sae_key(update_sink)
168    }
169
170    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
171    pub fn on_sae_handshake_ind(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
172        self.auth_method.on_sae_handshake_ind(update_sink).map_err(Error::AuthError)
173    }
174
175    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
176    pub fn on_sae_frame_rx(
177        &mut self,
178        update_sink: &mut UpdateSink,
179        frame: SaeFrame,
180    ) -> Result<(), Error> {
181        self.auth_method.on_sae_frame_rx(update_sink, frame).map_err(Error::AuthError)?;
182        self.extract_sae_key(update_sink)
183    }
184
185    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
186    pub fn on_sae_timeout(
187        &mut self,
188        update_sink: &mut UpdateSink,
189        event_id: u64,
190    ) -> Result<(), Error> {
191        self.auth_method.on_sae_timeout(update_sink, event_id).map_err(Error::AuthError)
192    }
193}
194
195#[derive(Debug)]
196pub struct Authenticator {
197    auth_method: auth::Method,
198    esssa: EssSa,
199    pub auth_cfg: auth::Config,
200}
201
202impl Authenticator {
203    /// WPA2-PSK CCMP-128 Authenticator which supports 4-Way Handshake.
204    /// The Authenticator does not support GTK rotations.
205    pub fn new_wpa2psk_ccmp128(
206        nonce_rdr: Arc<nonce::NonceReader>,
207        gtk_provider: Arc<Mutex<gtk::GtkProvider>>,
208        psk: psk::Psk,
209        s_addr: MacAddr,
210        s_protection: ProtectionInfo,
211        a_addr: MacAddr,
212        a_protection: ProtectionInfo,
213    ) -> Result<Authenticator, anyhow::Error> {
214        let negotiated_protection = NegotiatedProtection::from_protection(&s_protection)?;
215        let auth_cfg = auth::Config::ComputedPsk(psk.clone());
216        let auth_method = auth::Method::from_config(auth_cfg.clone())?;
217        let esssa = EssSa::new(
218            Role::Authenticator,
219            Some(psk.to_vec()),
220            negotiated_protection,
221            exchange::Config::FourWayHandshake(fourway::Config::new(
222                Role::Authenticator,
223                s_addr,
224                s_protection,
225                a_addr,
226                a_protection,
227                nonce_rdr,
228                Some(gtk_provider),
229                None,
230            )?),
231            // Group-Key Handshake does not support Authenticator role yet.
232            None,
233        )?;
234
235        Ok(Authenticator { auth_method, esssa, auth_cfg })
236    }
237
238    /// WPA3 Authenticator which supports 4-Way Handshake.
239    /// The Authenticator does not support GTK rotations.
240    pub fn new_wpa3(
241        nonce_rdr: Arc<nonce::NonceReader>,
242        gtk_provider: Arc<Mutex<gtk::GtkProvider>>,
243        igtk_provider: Arc<Mutex<igtk::IgtkProvider>>,
244        ssid: Ssid,
245        password: Vec<u8>,
246        s_addr: MacAddr,
247        s_protection: ProtectionInfo,
248        a_addr: MacAddr,
249        a_protection: ProtectionInfo,
250    ) -> Result<Authenticator, anyhow::Error> {
251        let negotiated_protection = NegotiatedProtection::from_protection(&s_protection)?;
252        let auth_cfg =
253            auth::Config::Sae { ssid, password, mac: a_addr.clone(), peer_mac: s_addr.clone() };
254        let auth_method = auth::Method::from_config(auth_cfg.clone())?;
255
256        let esssa = EssSa::new(
257            Role::Authenticator,
258            None,
259            negotiated_protection,
260            exchange::Config::FourWayHandshake(fourway::Config::new(
261                Role::Authenticator,
262                s_addr,
263                s_protection,
264                a_addr,
265                a_protection,
266                nonce_rdr,
267                Some(gtk_provider),
268                Some(igtk_provider),
269            )?),
270            // Group-Key Handshake does not support Authenticator role yet.
271            None,
272        )?;
273
274        Ok(Authenticator { auth_cfg, esssa, auth_method })
275    }
276
277    pub fn get_negotiated_protection(&self) -> &NegotiatedProtection {
278        &self.esssa.negotiated_protection
279    }
280
281    /// Resets all established Security Associations and invalidates all derived keys in this ESSSA.
282    /// The Authenticator must be reset or destroyed when the underlying 802.11 association
283    /// terminates. The replay counter is also reset.
284    pub fn reset(&mut self) {
285        self.esssa.reset_replay_counter();
286        self.esssa.reset_security_associations();
287
288        // Recreate auth_method to reset its state
289        match auth::Method::from_config(self.auth_cfg.clone()) {
290            Ok(auth_method) => self.auth_method = auth_method,
291            Err(e) => warn!("Unable to recreate auth::Method: {}", e),
292        }
293    }
294
295    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
296    /// `initiate(...)` must be called when the Authenticator should start establishing a
297    /// security association with a client.
298    /// The Authenticator must always initiate the security association in the current system as
299    /// EAPOL request frames from clients are not yet supported.
300    /// This method can be called multiple times to re-initiate the security association, however,
301    /// calling this method will invalidate all established security associations and their derived
302    /// keys.
303    pub fn initiate(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
304        self.esssa.initiate(update_sink)
305    }
306
307    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
308    /// Entry point for all incoming EAPOL frames. Incoming frames can be corrupted, invalid or of
309    /// unsupported types; the Authenticator will filter and drop all unexpected frames.
310    /// Outbound EAPOL frames, status and key updates will be pushed into the `update_sink`.
311    /// The method will return an `Error` if the frame was invalid.
312    pub fn on_eapol_frame<B: SplitByteSlice>(
313        &mut self,
314        update_sink: &mut UpdateSink,
315        frame: eapol::Frame<B>,
316    ) -> Result<(), Error> {
317        self.esssa.on_eapol_frame(update_sink, frame)
318    }
319
320    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
321    pub fn on_eapol_conf(
322        &mut self,
323        update_sink: &mut UpdateSink,
324        result: EapolResultCode,
325    ) -> Result<(), Error> {
326        self.esssa.on_eapol_conf(update_sink, result)
327    }
328
329    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
330    fn extract_sae_key(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
331        if let Some(pmk) = extract_sae_key_helper(&update_sink) {
332            self.esssa.on_pmk_available(update_sink, pmk)?;
333        }
334        Ok(())
335    }
336
337    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
338    pub fn on_sae_handshake_ind(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> {
339        self.auth_method.on_sae_handshake_ind(update_sink).map_err(Error::AuthError)
340    }
341
342    #[allow(clippy::result_large_err, reason = "mass allow for https://fxbug.dev/381896734")]
343    pub fn on_sae_frame_rx(
344        &mut self,
345        update_sink: &mut UpdateSink,
346        frame: SaeFrame,
347    ) -> Result<(), Error> {
348        self.auth_method.on_sae_frame_rx(update_sink, frame).map_err(Error::AuthError)?;
349        self.extract_sae_key(update_sink)
350    }
351}
352
353#[derive(Debug, Error)]
354pub enum Error {
355    #[error("invalid OUI length; expected 3 bytes but received {}", _0)]
356    InvalidOuiLength(usize),
357    #[error("invalid PMKID length; expected 16 bytes but received {}", _0)]
358    InvalidPmkidLength(usize),
359    #[error("invalid passphrase length: {}", _0)]
360    InvalidPassphraseLen(usize),
361    #[error("passphrase is not valid UTF-8; failed to parse after byte at index: {:x}", _0)]
362    InvalidPassphraseEncoding(usize),
363    #[error("the config `{:?}` is incompatible with the auth method `{:?}`", _0, _1)]
364    IncompatibleConfig(auth::Config, String),
365    #[error("invalid bit size; must be a multiple of 8 but was {}", _0)]
366    InvalidBitSize(usize),
367    #[error("nonce could not be generated")]
368    NonceError,
369    #[error("error deriving PTK; invalid PMK")]
370    PtkHierarchyInvalidPmkError,
371    #[error("error deriving PTK; unsupported AKM suite")]
372    PtkHierarchyUnsupportedAkmError,
373    #[error("error deriving PTK; unsupported cipher suite")]
374    PtkHierarchyUnsupportedCipherError,
375    #[error("error deriving GTK; unsupported cipher suite")]
376    GtkHierarchyUnsupportedCipherError,
377    #[error("error deriving IGTK; unsupported cipher suite")]
378    IgtkHierarchyUnsupportedCipherError,
379    #[error("no GtkProvider for Authenticator")]
380    MissingGtkProvider,
381    #[error("no IgtkProvider for Authenticator with Management Frame Protection support")]
382    MissingIgtkProvider,
383    #[error("invalid supplicant protection: {}", _0)]
384    InvalidSupplicantProtection(String),
385    #[error("required group mgmt cipher does not match IgtkProvider cipher: {:?} != {:?}", _0, _1)]
386    WrongIgtkProviderCipher(Cipher, Cipher),
387    #[error("error determining group mgmt cipher: {:?} != {:?}", _0, _1)]
388    GroupMgmtCipherMismatch(Cipher, Cipher),
389    #[error("client requires management frame protection and ap is not capable")]
390    MgmtFrameProtectionRequiredByClient,
391    #[error("ap requires management frame protection and client is not capable")]
392    MgmtFrameProtectionRequiredByAp,
393    #[error("client set MFP required bit without setting MFP capability bit")]
394    InvalidClientMgmtFrameProtectionCapabilityBit,
395    #[error("ap set MFP required bit without setting MFP capability bit")]
396    InvalidApMgmtFrameProtectionCapabilityBit,
397    #[error("AES operation failed: {}", _0)]
398    Aes(AesError),
399    #[error("invalid key data length; must be at least 16 bytes and a multiple of 8: {}", _0)]
400    InvaidKeyDataLength(usize),
401    #[error("invalid key data; error code: {:?}", _0)]
402    InvalidKeyData(nom::error::ErrorKind),
403    #[error("unknown authentication method")]
404    UnknownAuthenticationMethod,
405    #[error("no AKM negotiated")]
406    InvalidNegotiatedAkm,
407    #[error("unknown key exchange method")]
408    UnknownKeyExchange,
409    #[error("cannot initiate Fourway Handshake as Supplicant")]
410    UnexpectedInitiationRequest,
411    #[error("cannot initiate Supplicant in current EssSa state")]
412    UnexpectedEsssaInitiation,
413    #[error("key frame transmission failed")]
414    KeyFrameTransmissionFailed,
415    #[error("no key frame transmission confirm received; dropped {} pending updates", _0)]
416    NoKeyFrameTransmissionConfirm(usize),
417    #[error("eapol handshake not started")]
418    EapolHandshakeNotStarted,
419    #[error("likely wrong credential")]
420    LikelyWrongCredential,
421    #[error("eapol handshake incomplete: {}", _0)]
422    EapolHandshakeIncomplete(String),
423    #[error("unsupported Key Descriptor Type: {:?}", _0)]
424    UnsupportedKeyDescriptor(eapol::KeyDescriptor),
425    #[error("unexpected Key Descriptor Type {:?}; expected {:?}", _0, _1)]
426    InvalidKeyDescriptor(eapol::KeyDescriptor, eapol::KeyDescriptor),
427    #[error("unsupported Key Descriptor Version: {:?}", _0)]
428    UnsupportedKeyDescriptorVersion(u16),
429    #[error("only PTK and GTK derivation is supported")]
430    UnsupportedKeyDerivation,
431    #[error("unexpected message: {:?}", _0)]
432    UnexpectedHandshakeMessage(HandshakeMessageNumber),
433    #[error("invalid install bit value; message: {:?}", _0)]
434    InvalidInstallBitValue(HandshakeMessageNumber),
435    #[error("error, install bit set for Group-/SMK-Handshake")]
436    InvalidInstallBitGroupSmkHandshake,
437    #[error("invalid key_ack bit value; message: {:?}", _0)]
438    InvalidKeyAckBitValue(HandshakeMessageNumber),
439    #[error("invalid key_mic bit value; message: {:?}", _0)]
440    InvalidKeyMicBitValue(HandshakeMessageNumber),
441    #[error("invalid secure bit value; message: {:?}", _0)]
442    InvalidSecureBitValue(HandshakeMessageNumber),
443    #[error("error, secure bit set by Authenticator before PTK is known")]
444    SecureBitWithUnknownPtk,
445    #[error("error, secure bit set must be set by Supplicant once PTK and GTK are known")]
446    SecureBitNotSetWithKnownPtkGtk,
447    #[error("invalid error bit value; message: {:?}", _0)]
448    InvalidErrorBitValue(HandshakeMessageNumber),
449    #[error("invalid request bit value; message: {:?}", _0)]
450    InvalidRequestBitValue(HandshakeMessageNumber),
451    #[error("error, Authenticator set request bit")]
452    InvalidRequestBitAuthenticator,
453    #[error("error, Authenticator set error bit")]
454    InvalidErrorBitAuthenticator,
455    #[error("error, Supplicant set key_ack bit")]
456    InvalidKeyAckBitSupplicant,
457    #[error("invalid encrypted_key_data bit value")]
458    InvalidEncryptedKeyDataBitValue(HandshakeMessageNumber),
459    #[error("encrypted_key_data bit requires MIC bit to be set")]
460    InvalidMicBitForEncryptedKeyData,
461    #[error("invalid key length {:?}; expected {:?}", _0, _1)]
462    InvalidKeyLength(usize, usize),
463    #[error("unsupported cipher suite")]
464    UnsupportedCipherSuite,
465    #[error("unsupported AKM suite")]
466    UnsupportedAkmSuite,
467    #[error("cannot compute MIC for key frames which haven't set their MIC bit")]
468    ComputingMicForUnprotectedFrame,
469    #[error("cannot compute MIC; error while encrypting")]
470    ComputingMicEncryptionError,
471    #[error("the key frame's MIC size ({}) differes from the expected size: {}", _0, _1)]
472    MicSizesDiffer(usize, usize),
473    #[error("invalid MIC size")]
474    InvalidMicSize,
475    #[error("invalid Nonce; expected to be non-zero")]
476    InvalidNonce(HandshakeMessageNumber),
477    #[error("invalid RSC; expected to be zero")]
478    InvalidRsc(HandshakeMessageNumber),
479    #[error("invalid key data; must not be zero")]
480    EmptyKeyData(HandshakeMessageNumber),
481    #[error("invalid key data")]
482    InvalidKeyDataContent,
483    #[error("invalid key data length; doesn't match with key data")]
484    InvalidKeyDataLength,
485    #[error("cannot validate MIC; PTK not yet derived")]
486    UnexpectedMic,
487    #[error("invalid MIC")]
488    InvalidMic,
489    #[error("cannot decrypt key data; PTK not yet derived")]
490    UnexpectedEncryptedKeyData,
491    #[error("invalid key replay counter {:?}; expected counter to be > {:?}", _0, _1)]
492    InvalidKeyReplayCounter(u64, u64),
493    #[error("invalid nonce; nonce must match nonce from 1st message")]
494    ErrorNonceDoesntMatch,
495    #[error("invalid IV; EAPOL protocol version: {:?}; message: {:?}", _0, _1)]
496    InvalidIv(eapol::ProtocolVersion, HandshakeMessageNumber),
497    #[error("PMKSA was not yet established")]
498    PmksaNotEstablished,
499    #[error("invalid nonce size; expected 32 bytes, found: {:?}", _0)]
500    InvalidNonceSize(usize),
501    #[error("invalid key data; expected negotiated protection")]
502    InvalidKeyDataProtection,
503    #[error("buffer too small; required: {}, available: {}", _0, _1)]
504    BufferTooSmall(usize, usize),
505    #[error("error, SMK-Handshake is not supported")]
506    SmkHandshakeNotSupported,
507    #[error("error, negotiated protection is invalid")]
508    InvalidNegotiatedProtection,
509    #[error("unknown integrity algorithm for negotiated protection")]
510    UnknownIntegrityAlgorithm,
511    #[error("unknown keywrap algorithm for negotiated protection")]
512    UnknownKeywrapAlgorithm,
513    #[error("eapol error, {}", _0)]
514    EapolError(eapol::Error),
515    #[error("auth error, {}", _0)]
516    AuthError(auth::AuthError),
517    #[error("rsne error, {}", _0)]
518    RsneError(rsne::Error),
519    #[error("rsne invalid subset, supplicant: {:?}, authenticator: {:?}", _0, _1)]
520    RsneInvalidSubset(rsne::Rsne, rsne::Rsne),
521    #[error("error, {}", _0)]
522    GenericError(String),
523}
524
525impl PartialEq for Error {
526    fn eq(&self, other: &Self) -> bool {
527        format!("{:?}", self) == format!("{:?}", other)
528    }
529}
530impl Eq for Error {}
531
532impl From<AesError> for Error {
533    fn from(error: AesError) -> Self {
534        Error::Aes(error)
535    }
536}
537
538#[macro_export]
539macro_rules! rsn_ensure {
540    ($cond:expr, $err:literal) => {
541        if !$cond {
542            return std::result::Result::Err(Error::GenericError($err.to_string()));
543        }
544    };
545    ($cond:expr, $err:expr $(,)?) => {
546        if !$cond {
547            return std::result::Result::Err($err);
548        }
549    };
550}
551
552#[macro_export]
553macro_rules! format_rsn_err {
554    ($msg:literal $(,)?) => {
555        // Handle $:literal as a special case to make cargo-expanded code more
556        // concise in the common case.
557        Error::GenericError($msg.to_string())
558    };
559    ($err:expr $(,)?) => ({
560        Error::GenericError($err)
561    });
562    ($fmt:expr, $($arg:tt)*) => {
563        Error::GenericError(format!($fmt, $($arg)*))
564    };
565}
566
567impl From<eapol::Error> for Error {
568    fn from(e: eapol::Error) -> Self {
569        Error::EapolError(e)
570    }
571}
572
573impl From<auth::AuthError> for Error {
574    fn from(e: auth::AuthError) -> Self {
575        Error::AuthError(e)
576    }
577}
578
579impl From<rsne::Error> for Error {
580    fn from(e: rsne::Error) -> Self {
581        Error::RsneError(e)
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use crate::key::exchange::Key;
588    use crate::rsna::{SecAssocStatus, SecAssocUpdate, test_util};
589    use assert_matches::assert_matches;
590
591    #[test]
592    fn supplicant_extract_sae_key() {
593        let mut supplicant = test_util::get_wpa3_supplicant();
594        let mut dummy_update_sink = vec![
595            SecAssocUpdate::ScheduleSaeTimeout(123),
596            SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
597        ];
598        supplicant.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
599        // ESSSA should register the new PMK and report this.
600        assert_eq!(
601            dummy_update_sink,
602            vec![
603                SecAssocUpdate::ScheduleSaeTimeout(123),
604                SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
605                SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished),
606            ]
607        );
608    }
609
610    #[test]
611    fn supplicant_extract_sae_key_no_key() {
612        let mut supplicant = test_util::get_wpa3_supplicant();
613        let mut dummy_update_sink = vec![SecAssocUpdate::ScheduleSaeTimeout(123)];
614        supplicant.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
615        // No PMK means no new update.
616        assert_eq!(dummy_update_sink, vec![SecAssocUpdate::ScheduleSaeTimeout(123)]);
617    }
618
619    #[test]
620    fn authenticator_extract_sae_key() {
621        let mut authenticator = test_util::get_wpa3_authenticator();
622        let mut dummy_update_sink = vec![
623            SecAssocUpdate::ScheduleSaeTimeout(123),
624            SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
625        ];
626        authenticator.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
627        // ESSSA should register the new PMK and report this.
628        assert_eq!(
629            &dummy_update_sink[0..3],
630            vec![
631                SecAssocUpdate::ScheduleSaeTimeout(123),
632                SecAssocUpdate::Key(Key::Pmk(vec![1, 2, 3, 4, 5, 6, 7, 8])),
633                SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished),
634            ]
635            .as_slice(),
636        );
637
638        // ESSSA should also transmit an EAPOL frame since this is the Authenticator.
639        assert_matches!(&dummy_update_sink[3], &SecAssocUpdate::TxEapolKeyFrame { .. });
640    }
641
642    #[test]
643    fn authenticator_extract_sae_key_no_key() {
644        let mut authenticator = test_util::get_wpa3_authenticator();
645        let mut dummy_update_sink = vec![SecAssocUpdate::ScheduleSaeTimeout(123)];
646        authenticator.extract_sae_key(&mut dummy_update_sink).expect("Failed to extract key");
647        // No PMK means no new update.
648        assert_eq!(dummy_update_sink, vec![SecAssocUpdate::ScheduleSaeTimeout(123)]);
649    }
650}