wlan_sme/client/
protection.rs

1// Copyright 2019 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
5use crate::client::ClientConfig;
6use crate::client::rsn::Rsna;
7use anyhow::{Error, format_err};
8use fidl_fuchsia_wlan_common as fidl_common;
9use fidl_fuchsia_wlan_mlme::DeviceInfo;
10use wlan_common::bss::BssDescription;
11use wlan_common::ie::rsn::rsne::{self, Rsne};
12use wlan_common::ie::wpa::WpaIe;
13use wlan_common::ie::{self};
14use wlan_common::security::wep::{self, WepKey};
15use wlan_common::security::{SecurityAuthenticator, wpa};
16use wlan_rsn::auth::psk::ToPsk;
17use wlan_rsn::auth::{self};
18use wlan_rsn::nonce::NonceReader;
19use wlan_rsn::{NegotiatedProtection, ProtectionInfo};
20
21#[derive(Debug)]
22pub enum Protection {
23    Open,
24    Wep(WepKey),
25    // WPA1 is based off of a modified pre-release version of IEEE 802.11i. It is similar enough
26    // that we can reuse the existing RSNA implementation rather than duplicating large pieces of
27    // logic.
28    LegacyWpa(Rsna),
29    Rsna(Rsna),
30}
31
32impl Protection {
33    pub fn rsn_auth_method(&self) -> Option<auth::MethodName> {
34        let rsna = match self {
35            Self::LegacyWpa(rsna) => rsna,
36            Self::Rsna(rsna) => rsna,
37            // Neither WEP or Open use an RSN, so None is returned.
38            Self::Wep(_) | Self::Open => {
39                return None;
40            }
41        };
42
43        Some(rsna.supplicant.get_auth_method())
44    }
45}
46
47#[derive(Debug)]
48pub enum ProtectionIe {
49    Rsne(Vec<u8>),
50    VendorIes(Vec<u8>),
51}
52
53/// Context for authentication.
54///
55/// This ephemeral type is used to query and derive various IEs and RSN entities based on
56/// parameterized security data, the configured client, and the target BSS. This is exposed to
57/// client code via `TryFrom` implementations, which allow a context to be converted into a
58/// negotiated `Protection`. These conversions fail if the combination of an authenticator and a
59/// network is incompatible.
60///
61/// The type parameter `C` represents the parameterized security data and is either a type
62/// representing credential data or a `SecurityAuthenticator`.
63///
64/// # Examples
65///
66/// To derive a `Protection`, construct a `SecurityContext` using a `SecurityAuthenticator` and
67/// perform a conversion.
68///
69/// ```rust,ignore
70/// // See the documentation for `SecurityAuthenticator` for more details.
71/// let authenticator = SecurityAuthenticator::try_from(authentication)?;
72/// let protection = Protection::try_from(SecurityContext {
73///     security: &authenticator,
74///     device: &device, // Device information.
75///     security_support: &security_support, // Security features.
76///     config: &config, // Client configuration.
77///     bss: &bss, // BSS description.
78/// })?;
79/// ```
80#[derive(Clone, Copy, Debug)]
81pub struct SecurityContext<'a, C> {
82    /// Contextual security data. This field has security-related
83    pub security: &'a C,
84    pub device: &'a DeviceInfo,
85    pub security_support: &'a fidl_common::SecuritySupport,
86    pub config: &'a ClientConfig,
87    pub bss: &'a BssDescription,
88}
89
90impl<'a, C> SecurityContext<'a, C> {
91    /// Gets a context with a subject replaced by the given subject. Other fields are unmodified.
92    fn map<U>(&self, subject: &'a U) -> SecurityContext<'a, U> {
93        SecurityContext {
94            security: subject,
95            device: self.device,
96            security_support: self.security_support,
97            config: self.config,
98            bss: self.bss,
99        }
100    }
101}
102
103impl SecurityContext<'_, wpa::Wpa1Credentials> {
104    /// Gets the authenticator and supplicant IEs for WPA1 from the associated BSS.
105    fn authenticator_supplicant_ie(&self) -> Result<(WpaIe, WpaIe), Error> {
106        let a_wpa_ie = self.bss.wpa_ie()?;
107        if !crate::client::wpa::is_legacy_wpa_compatible(&a_wpa_ie) {
108            return Err(format_err!("Legacy WPA requested but IE is incompatible: {:?}", a_wpa_ie));
109        }
110        let s_wpa_ie = crate::client::wpa::construct_s_wpa(&a_wpa_ie);
111        Ok((a_wpa_ie, s_wpa_ie))
112    }
113
114    /// Gets the PSK used to authenticate via WPA1.
115    fn authentication_config(&self) -> auth::Config {
116        auth::Config::ComputedPsk(self.security.to_psk(&self.bss.ssid).into())
117    }
118}
119
120impl SecurityContext<'_, wpa::Wpa2PersonalCredentials> {
121    /// Gets the authenticator and supplicant RSNEs for WPA2 Personal from the associated BSS.
122    fn authenticator_supplicant_rsne(&self) -> Result<(Rsne, Rsne), Error> {
123        let a_rsne_ie = self
124            .bss
125            .rsne()
126            .ok_or_else(|| format_err!("WPA2 requested but RSNE is not present in BSS."))?;
127        let (_, a_rsne) = rsne::from_bytes(a_rsne_ie)
128            .map_err(|error| format_err!("Invalid RSNE IE {:02x?}: {:?}", a_rsne_ie, error))?;
129        let s_rsne = a_rsne.derive_wpa2_s_rsne(self.security_support)?;
130        Ok((a_rsne, s_rsne))
131    }
132
133    /// Gets the PSK used to authenticate via WPA2 Personal.
134    fn authentication_config(&self) -> auth::Config {
135        auth::Config::ComputedPsk(self.security.to_psk(&self.bss.ssid).into())
136    }
137}
138
139impl SecurityContext<'_, wpa::Wpa3PersonalCredentials> {
140    /// Gets the authenticator and supplicant RSNEs for WPA3 Personal from the associated BSS.
141    fn authenticator_supplicant_rsne(&self) -> Result<(Rsne, Rsne), Error> {
142        let a_rsne_ie = self
143            .bss
144            .rsne()
145            .ok_or_else(|| format_err!("WPA3 requested but RSNE is not present in BSS."))?;
146        let (_, a_rsne) = rsne::from_bytes(a_rsne_ie)
147            .map_err(|error| format_err!("Invalid RSNE IE {:02x?}: {:?}", a_rsne_ie, error))?;
148        let s_rsne = a_rsne.derive_wpa3_s_rsne(self.security_support)?;
149        Ok((a_rsne, s_rsne))
150    }
151
152    /// Gets the SAE used to authenticate via WPA3 Personal.
153    fn authentication_config(&self) -> Result<auth::Config, Error> {
154        match self.security {
155            wpa::Wpa3PersonalCredentials::Passphrase(passphrase) => {
156                // Prefer SAE in SME.
157                if self.security_support.sae.sme_handler_supported {
158                    Ok(auth::Config::Sae {
159                        ssid: self.bss.ssid.clone(),
160                        password: passphrase.clone().into(),
161                        mac: self.device.sta_addr.into(),
162                        peer_mac: self.bss.bssid.into(),
163                    })
164                } else if self.security_support.sae.driver_handler_supported {
165                    Ok(auth::Config::DriverSae { password: passphrase.clone().into() })
166                } else {
167                    Err(format_err!(
168                        "Failed to generate WPA3 authentication config: no SAE SME nor driver \
169                         handler"
170                    ))
171                }
172            }
173        }
174    }
175}
176
177impl<'a> TryFrom<SecurityContext<'a, SecurityAuthenticator>> for Protection {
178    type Error = Error;
179
180    fn try_from(context: SecurityContext<'a, SecurityAuthenticator>) -> Result<Self, Self::Error> {
181        match context.security {
182            SecurityAuthenticator::Open => context
183                .bss
184                .is_open()
185                .then(|| Protection::Open)
186                .ok_or_else(|| format_err!("BSS is not configured for open authentication")),
187            SecurityAuthenticator::Wep(authenticator) => context.map(authenticator).try_into(),
188            SecurityAuthenticator::Wpa(wpa) => match wpa {
189                wpa::WpaAuthenticator::Wpa1 { credentials, .. } => {
190                    context.map(credentials).try_into()
191                }
192                wpa::WpaAuthenticator::Wpa2 { authentication, .. } => match authentication {
193                    wpa::Authentication::Personal(personal) => context.map(personal).try_into(),
194                    // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
195                    _ => Err(format_err!("WPA Enterprise is unsupported")),
196                },
197                wpa::WpaAuthenticator::Wpa3 { authentication, .. } => match authentication {
198                    wpa::Authentication::Personal(personal) => context.map(personal).try_into(),
199                    // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
200                    _ => Err(format_err!("WPA Enterprise is unsupported")),
201                },
202            },
203        }
204    }
205}
206
207impl<'a> TryFrom<SecurityContext<'a, wep::WepAuthenticator>> for Protection {
208    type Error = Error;
209
210    fn try_from(context: SecurityContext<'a, wep::WepAuthenticator>) -> Result<Self, Self::Error> {
211        context
212            .bss
213            .has_wep_configured()
214            .then(|| Protection::Wep(context.security.key.clone()))
215            .ok_or_else(|| format_err!("BSS is not configured for WEP"))
216    }
217}
218
219impl<'a> TryFrom<SecurityContext<'a, wpa::Wpa1Credentials>> for Protection {
220    type Error = Error;
221
222    fn try_from(context: SecurityContext<'a, wpa::Wpa1Credentials>) -> Result<Self, Self::Error> {
223        context
224            .bss
225            .has_wpa1_configured()
226            .then(|| -> Result<_, Self::Error> {
227                let sta_addr = context.device.sta_addr.into();
228                let (a_wpa_ie, s_wpa_ie) = context.authenticator_supplicant_ie()?;
229                let negotiated_protection = NegotiatedProtection::from_legacy_wpa(&s_wpa_ie)?;
230                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
231                    NonceReader::new(&sta_addr)?,
232                    context.authentication_config(),
233                    sta_addr,
234                    ProtectionInfo::LegacyWpa(s_wpa_ie),
235                    context.bss.bssid.into(),
236                    ProtectionInfo::LegacyWpa(a_wpa_ie),
237                )
238                .map_err(|error| format_err!("Failed to create ESS-SA: {:?}", error))?;
239                Ok(Protection::LegacyWpa(Rsna {
240                    negotiated_protection,
241                    supplicant: Box::new(supplicant),
242                }))
243            })
244            .transpose()?
245            .ok_or_else(|| format_err!("BSS is not configured for WPA1"))
246    }
247}
248
249impl<'a> TryFrom<SecurityContext<'a, wpa::Wpa2PersonalCredentials>> for Protection {
250    type Error = Error;
251
252    fn try_from(
253        context: SecurityContext<'a, wpa::Wpa2PersonalCredentials>,
254    ) -> Result<Self, Self::Error> {
255        context
256            .bss
257            .has_wpa2_personal_configured()
258            .then(|| -> Result<_, Self::Error> {
259                let sta_addr = context.device.sta_addr.into();
260                let (a_rsne, s_rsne) = context.authenticator_supplicant_rsne()?;
261                let negotiated_protection = NegotiatedProtection::from_rsne(&s_rsne)?;
262                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
263                    NonceReader::new(&sta_addr)?,
264                    context.authentication_config(),
265                    sta_addr,
266                    ProtectionInfo::Rsne(s_rsne),
267                    context.bss.bssid.into(),
268                    ProtectionInfo::Rsne(a_rsne),
269                )
270                .map_err(|error| format_err!("Failed to creat ESS-SA: {:?}", error))?;
271                Ok(Protection::Rsna(Rsna {
272                    negotiated_protection,
273                    supplicant: Box::new(supplicant),
274                }))
275            })
276            .transpose()?
277            .ok_or_else(|| format_err!("BSS is not configured for WPA2 Personal"))
278    }
279}
280
281impl<'a> TryFrom<SecurityContext<'a, wpa::Wpa3PersonalCredentials>> for Protection {
282    type Error = Error;
283
284    fn try_from(
285        context: SecurityContext<'a, wpa::Wpa3PersonalCredentials>,
286    ) -> Result<Self, Self::Error> {
287        context
288            .bss
289            .has_wpa3_personal_configured()
290            .then(|| -> Result<_, Self::Error> {
291                let sta_addr = context.device.sta_addr.into();
292                if !context.config.wpa3_supported {
293                    return Err(format_err!("WPA3 requested but client does not support WPA3"));
294                }
295                let (a_rsne, s_rsne) = context.authenticator_supplicant_rsne()?;
296                let negotiated_protection = NegotiatedProtection::from_rsne(&s_rsne)?;
297                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
298                    NonceReader::new(&sta_addr)?,
299                    context.authentication_config()?,
300                    sta_addr,
301                    ProtectionInfo::Rsne(s_rsne),
302                    context.bss.bssid.into(),
303                    ProtectionInfo::Rsne(a_rsne),
304                )
305                .map_err(|error| format_err!("Failed to create ESS-SA: {:?}", error))?;
306                Ok(Protection::Rsna(Rsna {
307                    negotiated_protection,
308                    supplicant: Box::new(supplicant),
309                }))
310            })
311            .transpose()?
312            .ok_or_else(|| format_err!("BSS is not configured for WPA3 Personal"))
313    }
314}
315
316/// Based on the type of protection, derive either RSNE or Vendor IEs:
317/// No Protection or WEP: Neither
318/// WPA2: RSNE
319/// WPA1: Vendor IEs
320pub(crate) fn build_protection_ie(protection: &Protection) -> Result<Option<ProtectionIe>, Error> {
321    match protection {
322        Protection::Open | Protection::Wep(_) => Ok(None),
323        Protection::LegacyWpa(rsna) => {
324            let s_protection = rsna.negotiated_protection.to_full_protection();
325            let s_wpa = match s_protection {
326                ProtectionInfo::Rsne(_) => {
327                    return Err(format_err!("found RSNE protection inside a WPA1 association..."));
328                }
329                ProtectionInfo::LegacyWpa(wpa) => wpa,
330            };
331            let mut buf = vec![];
332            #[expect(clippy::unwrap_used)]
333            ie::write_wpa1_ie(&mut buf, &s_wpa).unwrap(); // Writing to a Vec never fails
334            Ok(Some(ProtectionIe::VendorIes(buf)))
335        }
336        Protection::Rsna(rsna) => {
337            let s_protection = rsna.negotiated_protection.to_full_protection();
338            let s_rsne = match s_protection {
339                ProtectionInfo::Rsne(rsne) => rsne,
340                ProtectionInfo::LegacyWpa(_) => {
341                    return Err(format_err!("found WPA protection inside an RSNA..."));
342                }
343            };
344            let mut buf = Vec::with_capacity(s_rsne.len());
345            // Writing an RSNE into a Vector can never fail as a Vector can be grown when more
346            // space is required. If this panic ever triggers, something is clearly broken
347            // somewhere else.
348            #[expect(clippy::unwrap_used)]
349            let () = s_rsne.write_into(&mut buf).unwrap();
350            Ok(Some(ProtectionIe::Rsne(buf)))
351        }
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358    use crate::client::{self};
359    use assert_matches::assert_matches;
360    use wlan_common::fake_bss_description;
361    use wlan_common::ie::fake_ies::fake_wpa_ie;
362    use wlan_common::ie::rsn::fake_rsnes::{fake_wpa2_s_rsne, fake_wpa3_s_rsne};
363    use wlan_common::security::wep::{WEP40_KEY_BYTES, WEP104_KEY_BYTES};
364    use wlan_common::security::wpa::credential::PSK_SIZE_BYTES;
365    use wlan_common::test_utils::fake_features::{
366        fake_security_support, fake_security_support_empty,
367    };
368
369    #[test]
370    fn rsn_auth_method() {
371        // Open
372        let protection = Protection::Open;
373        assert!(protection.rsn_auth_method().is_none());
374
375        // Wep
376        let protection = Protection::Wep(WepKey::parse([1; 5]).expect("unable to parse WEP key"));
377        assert!(protection.rsn_auth_method().is_none());
378
379        // WPA1
380        let protection_info = ProtectionInfo::LegacyWpa(fake_wpa_ie());
381        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
382            .expect("could create mocked WPA1 NegotiatedProtection");
383        let protection = Protection::LegacyWpa(Rsna {
384            negotiated_protection,
385            supplicant: Box::new(client::test_utils::mock_psk_supplicant().0),
386        });
387        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Psk));
388
389        // WPA2
390        let protection_info = ProtectionInfo::Rsne(fake_wpa2_s_rsne());
391        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
392            .expect("could create mocked WPA2 NegotiatedProtection");
393        let protection = Protection::Rsna(Rsna {
394            negotiated_protection,
395            supplicant: Box::new(client::test_utils::mock_psk_supplicant().0),
396        });
397        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Psk));
398
399        // WPA3
400        let protection_info = ProtectionInfo::Rsne(fake_wpa3_s_rsne());
401        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
402            .expect("could create mocked WPA3 NegotiatedProtection");
403        let protection = Protection::Rsna(Rsna {
404            negotiated_protection,
405            supplicant: Box::new(client::test_utils::mock_sae_supplicant().0),
406        });
407        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Sae));
408    }
409
410    #[test]
411    fn protection_from_wep40() {
412        let device = crate::test_utils::fake_device_info([1u8; 6].into());
413        let security_support = fake_security_support();
414        let config = Default::default();
415        let bss = fake_bss_description!(Wep);
416        let authenticator = wep::WepAuthenticator { key: WepKey::from([1u8; WEP40_KEY_BYTES]) };
417        let protection = Protection::try_from(SecurityContext {
418            security: &authenticator,
419            device: &device,
420            security_support: &security_support,
421            config: &config,
422            bss: &bss,
423        })
424        .unwrap();
425        assert!(matches!(protection, Protection::Wep(_)));
426    }
427
428    #[test]
429    fn protection_from_wep104() {
430        let device = crate::test_utils::fake_device_info([1u8; 6].into());
431        let security_support = fake_security_support();
432        let config = Default::default();
433        let bss = fake_bss_description!(Wep);
434        let authenticator = wep::WepAuthenticator { key: WepKey::from([1u8; WEP104_KEY_BYTES]) };
435        let protection = Protection::try_from(SecurityContext {
436            security: &authenticator,
437            device: &device,
438            security_support: &security_support,
439            config: &config,
440            bss: &bss,
441        })
442        .unwrap();
443        assert!(matches!(protection, Protection::Wep(_)));
444    }
445
446    #[test]
447    fn protection_from_wpa1_psk() {
448        let device = crate::test_utils::fake_device_info([1u8; 6].into());
449        let security_support = fake_security_support();
450        let config = Default::default();
451        let bss = fake_bss_description!(Wpa1);
452        let credentials = wpa::Wpa1Credentials::Psk([1u8; PSK_SIZE_BYTES].into());
453        let protection = Protection::try_from(SecurityContext {
454            security: &credentials,
455            device: &device,
456            security_support: &security_support,
457            config: &config,
458            bss: &bss,
459        })
460        .unwrap();
461        assert!(matches!(protection, Protection::LegacyWpa(_)));
462    }
463
464    #[test]
465    fn protection_from_wpa2_personal_psk() {
466        let device = crate::test_utils::fake_device_info([1u8; 6].into());
467        let security_support = fake_security_support();
468        let config = Default::default();
469        let bss = fake_bss_description!(Wpa2);
470        let credentials = wpa::Wpa2PersonalCredentials::Psk([1u8; PSK_SIZE_BYTES].into());
471        let protection = Protection::try_from(SecurityContext {
472            security: &credentials,
473            device: &device,
474            security_support: &security_support,
475            config: &config,
476            bss: &bss,
477        })
478        .unwrap();
479        assert!(matches!(protection, Protection::Rsna(_)));
480    }
481
482    #[test]
483    fn protection_from_wpa2_personal_passphrase() {
484        let device = crate::test_utils::fake_device_info([1u8; 6].into());
485        let security_support = fake_security_support();
486        let config = Default::default();
487        let bss = fake_bss_description!(Wpa2);
488        let credentials =
489            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
490        let protection = Protection::try_from(SecurityContext {
491            security: &credentials,
492            device: &device,
493            security_support: &security_support,
494            config: &config,
495            bss: &bss,
496        })
497        .unwrap();
498        assert!(matches!(protection, Protection::Rsna(_)));
499    }
500
501    #[test]
502    fn protection_from_wpa2_personal_tkip_only_passphrase() {
503        let device = crate::test_utils::fake_device_info([1u8; 6].into());
504        let security_support = fake_security_support();
505        let config = Default::default();
506        let bss = fake_bss_description!(Wpa2TkipOnly);
507        let credentials =
508            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
509        let protection = Protection::try_from(SecurityContext {
510            security: &credentials,
511            device: &device,
512            security_support: &security_support,
513            config: &config,
514            bss: &bss,
515        })
516        .unwrap();
517        assert!(matches!(protection, Protection::Rsna(_)));
518    }
519
520    #[test]
521    fn protection_from_wpa3_personal_passphrase() {
522        let device = crate::test_utils::fake_device_info([1u8; 6].into());
523        let security_support = fake_security_support();
524        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
525        let bss = fake_bss_description!(Wpa3);
526        let credentials =
527            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
528        let protection = Protection::try_from(SecurityContext {
529            security: &credentials,
530            device: &device,
531            security_support: &security_support,
532            config: &config,
533            bss: &bss,
534        })
535        .unwrap();
536        assert!(matches!(protection, Protection::Rsna(_)));
537    }
538
539    #[test]
540    fn protection_from_wpa1_passphrase_with_open_bss() {
541        let device = crate::test_utils::fake_device_info([1u8; 6].into());
542        let security_support = fake_security_support();
543        let config = Default::default();
544        let bss = fake_bss_description!(Open);
545        let credentials =
546            wpa::Wpa1Credentials::Passphrase("password".as_bytes().try_into().unwrap());
547        let _ = Protection::try_from(SecurityContext {
548            security: &credentials,
549            device: &device,
550            security_support: &security_support,
551            config: &config,
552            bss: &bss,
553        })
554        .expect_err("incorrectly accepted WPA1 passphrase credentials with open BSS");
555    }
556
557    #[test]
558    fn protection_from_open_authenticator_with_wpa1_bss() {
559        let device = crate::test_utils::fake_device_info([1u8; 6].into());
560        let security_support = fake_security_support();
561        let config = Default::default();
562        let bss = fake_bss_description!(Wpa1);
563        // Note that there is no credentials type associated with an open authenticator, so an
564        // authenticator is used here instead.
565        let authenticator = SecurityAuthenticator::Open;
566        let _ = Protection::try_from(SecurityContext {
567            security: &authenticator,
568            device: &device,
569            security_support: &security_support,
570            config: &config,
571            bss: &bss,
572        })
573        .expect_err("incorrectly accepted open authenticator with WPA1 BSS");
574    }
575
576    #[test]
577    fn protection_from_wpa2_personal_passphrase_with_wpa3_bss() {
578        let device = crate::test_utils::fake_device_info([1u8; 6].into());
579        let security_support = fake_security_support();
580        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
581        let bss = fake_bss_description!(Wpa3);
582        let credentials =
583            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
584        let _ = Protection::try_from(SecurityContext {
585            security: &credentials,
586            device: &device,
587            security_support: &security_support,
588            config: &config,
589            bss: &bss,
590        })
591        .expect_err("incorrectly accepted WPA2 passphrase credentials with WPA3 BSS");
592    }
593
594    #[test]
595    fn wpa1_psk_rsna() {
596        let device = crate::test_utils::fake_device_info([1u8; 6].into());
597        let security_support = fake_security_support();
598        let config = Default::default();
599        let bss = fake_bss_description!(Wpa1);
600        let credentials = wpa::Wpa1Credentials::Psk([1u8; PSK_SIZE_BYTES].into());
601        let context = SecurityContext {
602            security: &credentials,
603            device: &device,
604            security_support: &security_support,
605            config: &config,
606            bss: &bss,
607        };
608        assert!(context.authenticator_supplicant_ie().is_ok());
609        assert!(matches!(context.authentication_config(), auth::Config::ComputedPsk(_)));
610
611        let protection = Protection::try_from(context).unwrap();
612        assert_matches!(protection, Protection::LegacyWpa(rsna) => {
613            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Psk);
614        });
615    }
616
617    #[test]
618    fn wpa2_personal_psk_rsna() {
619        let device = crate::test_utils::fake_device_info([1u8; 6].into());
620        let security_support = fake_security_support();
621        let config = Default::default();
622        let bss = fake_bss_description!(Wpa2);
623        let credentials = wpa::Wpa2PersonalCredentials::Psk([1u8; PSK_SIZE_BYTES].into());
624        let context = SecurityContext {
625            security: &credentials,
626            device: &device,
627            security_support: &security_support,
628            config: &config,
629            bss: &bss,
630        };
631        assert!(context.authenticator_supplicant_rsne().is_ok());
632        assert!(matches!(context.authentication_config(), auth::Config::ComputedPsk(_)));
633
634        let protection = Protection::try_from(context).unwrap();
635        assert_matches!(protection, Protection::Rsna(rsna) => {
636            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Psk);
637        });
638    }
639
640    #[test]
641    fn wpa3_personal_passphrase_rsna_sme_auth() {
642        let device = crate::test_utils::fake_device_info([1u8; 6].into());
643        let security_support = fake_security_support();
644        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
645        let bss = fake_bss_description!(Wpa3);
646        let credentials =
647            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
648        let context = SecurityContext {
649            security: &credentials,
650            device: &device,
651            security_support: &security_support,
652            config: &config,
653            bss: &bss,
654        };
655        assert!(context.authenticator_supplicant_rsne().is_ok());
656        assert!(matches!(context.authentication_config(), Ok(auth::Config::Sae { .. })));
657
658        let protection = Protection::try_from(context).unwrap();
659        assert_matches!(protection, Protection::Rsna(rsna) => {
660            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
661        });
662    }
663
664    #[test]
665    fn wpa3_personal_passphrase_rsna_driver_auth() {
666        let device = crate::test_utils::fake_device_info([1u8; 6].into());
667        let mut security_support = fake_security_support_empty();
668        security_support.mfp.supported = true;
669        security_support.sae.driver_handler_supported = true;
670        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
671        let bss = fake_bss_description!(Wpa3);
672        let credentials =
673            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
674        let context = SecurityContext {
675            security: &credentials,
676            device: &device,
677            security_support: &security_support,
678            config: &config,
679            bss: &bss,
680        };
681        assert!(context.authenticator_supplicant_rsne().is_ok());
682        assert!(matches!(context.authentication_config(), Ok(auth::Config::DriverSae { .. })));
683
684        let protection = Protection::try_from(context).unwrap();
685        assert_matches!(protection, Protection::Rsna(rsna) => {
686            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
687        });
688    }
689
690    #[test]
691    fn wpa3_personal_passphrase_prefer_sme_auth() {
692        let device = crate::test_utils::fake_device_info([1u8; 6].into());
693        let mut security_support = fake_security_support_empty();
694        security_support.mfp.supported = true;
695        security_support.sae.driver_handler_supported = true;
696        security_support.sae.sme_handler_supported = true;
697        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
698        let bss = fake_bss_description!(Wpa3);
699        let credentials =
700            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
701        let context = SecurityContext {
702            security: &credentials,
703            device: &device,
704            security_support: &security_support,
705            config: &config,
706            bss: &bss,
707        };
708        assert!(matches!(context.authentication_config(), Ok(auth::Config::Sae { .. })));
709    }
710
711    #[test]
712    fn wpa3_personal_passphrase_no_security_support_features() {
713        let device = crate::test_utils::fake_device_info([1u8; 6].into());
714        let security_support = fake_security_support_empty();
715        let config = Default::default();
716        let bss = fake_bss_description!(Wpa3);
717        let credentials =
718            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
719        let context = SecurityContext {
720            security: &credentials,
721            device: &device,
722            security_support: &security_support,
723            config: &config,
724            bss: &bss,
725        };
726        let _ = context
727            .authentication_config()
728            .expect_err("created WPA3 auth config for incompatible device");
729    }
730}