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