Skip to main content

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