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