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::rsn::Rsna;
6use crate::client::ClientConfig;
7use anyhow::{format_err, Error};
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::{wpa, SecurityAuthenticator};
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(ref 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 wlan_common::ie::fake_ies::fake_wpa_ie;
360    use wlan_common::ie::rsn::fake_rsnes::{fake_wpa2_s_rsne, fake_wpa3_s_rsne};
361    use wlan_common::security::wep::{WEP104_KEY_BYTES, WEP40_KEY_BYTES};
362    use wlan_common::security::wpa::credential::PSK_SIZE_BYTES;
363    use wlan_common::test_utils::fake_features::{
364        fake_security_support, fake_security_support_empty,
365    };
366    use wlan_common::{assert_variant, fake_bss_description};
367
368    #[test]
369    fn rsn_auth_method() {
370        // Open
371        let protection = Protection::Open;
372        assert!(protection.rsn_auth_method().is_none());
373
374        // Wep
375        let protection = Protection::Wep(WepKey::parse([1; 5]).expect("unable to parse WEP key"));
376        assert!(protection.rsn_auth_method().is_none());
377
378        // WPA1
379        let protection_info = ProtectionInfo::LegacyWpa(fake_wpa_ie());
380        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
381            .expect("could create mocked WPA1 NegotiatedProtection");
382        let protection = Protection::LegacyWpa(Rsna {
383            negotiated_protection,
384            supplicant: Box::new(client::test_utils::mock_psk_supplicant().0),
385        });
386        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Psk));
387
388        // WPA2
389        let protection_info = ProtectionInfo::Rsne(fake_wpa2_s_rsne());
390        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
391            .expect("could create mocked WPA2 NegotiatedProtection");
392        let protection = Protection::Rsna(Rsna {
393            negotiated_protection,
394            supplicant: Box::new(client::test_utils::mock_psk_supplicant().0),
395        });
396        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Psk));
397
398        // WPA3
399        let protection_info = ProtectionInfo::Rsne(fake_wpa3_s_rsne());
400        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
401            .expect("could create mocked WPA3 NegotiatedProtection");
402        let protection = Protection::Rsna(Rsna {
403            negotiated_protection,
404            supplicant: Box::new(client::test_utils::mock_sae_supplicant().0),
405        });
406        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Sae));
407    }
408
409    #[test]
410    fn protection_from_wep40() {
411        let device = crate::test_utils::fake_device_info([1u8; 6].into());
412        let security_support = fake_security_support();
413        let config = Default::default();
414        let bss = fake_bss_description!(Wep);
415        let authenticator = wep::WepAuthenticator { key: WepKey::from([1u8; WEP40_KEY_BYTES]) };
416        let protection = Protection::try_from(SecurityContext {
417            security: &authenticator,
418            device: &device,
419            security_support: &security_support,
420            config: &config,
421            bss: &bss,
422        })
423        .unwrap();
424        assert!(matches!(protection, Protection::Wep(_)));
425    }
426
427    #[test]
428    fn protection_from_wep104() {
429        let device = crate::test_utils::fake_device_info([1u8; 6].into());
430        let security_support = fake_security_support();
431        let config = Default::default();
432        let bss = fake_bss_description!(Wep);
433        let authenticator = wep::WepAuthenticator { key: WepKey::from([1u8; WEP104_KEY_BYTES]) };
434        let protection = Protection::try_from(SecurityContext {
435            security: &authenticator,
436            device: &device,
437            security_support: &security_support,
438            config: &config,
439            bss: &bss,
440        })
441        .unwrap();
442        assert!(matches!(protection, Protection::Wep(_)));
443    }
444
445    #[test]
446    fn protection_from_wpa1_psk() {
447        let device = crate::test_utils::fake_device_info([1u8; 6].into());
448        let security_support = fake_security_support();
449        let config = Default::default();
450        let bss = fake_bss_description!(Wpa1);
451        let credentials = wpa::Wpa1Credentials::Psk([1u8; PSK_SIZE_BYTES].into());
452        let protection = Protection::try_from(SecurityContext {
453            security: &credentials,
454            device: &device,
455            security_support: &security_support,
456            config: &config,
457            bss: &bss,
458        })
459        .unwrap();
460        assert!(matches!(protection, Protection::LegacyWpa(_)));
461    }
462
463    #[test]
464    fn protection_from_wpa2_personal_psk() {
465        let device = crate::test_utils::fake_device_info([1u8; 6].into());
466        let security_support = fake_security_support();
467        let config = Default::default();
468        let bss = fake_bss_description!(Wpa2);
469        let credentials = wpa::Wpa2PersonalCredentials::Psk([1u8; PSK_SIZE_BYTES].into());
470        let protection = Protection::try_from(SecurityContext {
471            security: &credentials,
472            device: &device,
473            security_support: &security_support,
474            config: &config,
475            bss: &bss,
476        })
477        .unwrap();
478        assert!(matches!(protection, Protection::Rsna(_)));
479    }
480
481    #[test]
482    fn protection_from_wpa2_personal_passphrase() {
483        let device = crate::test_utils::fake_device_info([1u8; 6].into());
484        let security_support = fake_security_support();
485        let config = Default::default();
486        let bss = fake_bss_description!(Wpa2);
487        let credentials =
488            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
489        let protection = Protection::try_from(SecurityContext {
490            security: &credentials,
491            device: &device,
492            security_support: &security_support,
493            config: &config,
494            bss: &bss,
495        })
496        .unwrap();
497        assert!(matches!(protection, Protection::Rsna(_)));
498    }
499
500    #[test]
501    fn protection_from_wpa2_personal_tkip_only_passphrase() {
502        let device = crate::test_utils::fake_device_info([1u8; 6].into());
503        let security_support = fake_security_support();
504        let config = Default::default();
505        let bss = fake_bss_description!(Wpa2TkipOnly);
506        let credentials =
507            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
508        let protection = Protection::try_from(SecurityContext {
509            security: &credentials,
510            device: &device,
511            security_support: &security_support,
512            config: &config,
513            bss: &bss,
514        })
515        .unwrap();
516        assert!(matches!(protection, Protection::Rsna(_)));
517    }
518
519    #[test]
520    fn protection_from_wpa3_personal_passphrase() {
521        let device = crate::test_utils::fake_device_info([1u8; 6].into());
522        let security_support = fake_security_support();
523        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
524        let bss = fake_bss_description!(Wpa3);
525        let credentials =
526            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
527        let protection = Protection::try_from(SecurityContext {
528            security: &credentials,
529            device: &device,
530            security_support: &security_support,
531            config: &config,
532            bss: &bss,
533        })
534        .unwrap();
535        assert!(matches!(protection, Protection::Rsna(_)));
536    }
537
538    #[test]
539    fn protection_from_wpa1_passphrase_with_open_bss() {
540        let device = crate::test_utils::fake_device_info([1u8; 6].into());
541        let security_support = fake_security_support();
542        let config = Default::default();
543        let bss = fake_bss_description!(Open);
544        let credentials =
545            wpa::Wpa1Credentials::Passphrase("password".as_bytes().try_into().unwrap());
546        let _ = Protection::try_from(SecurityContext {
547            security: &credentials,
548            device: &device,
549            security_support: &security_support,
550            config: &config,
551            bss: &bss,
552        })
553        .expect_err("incorrectly accepted WPA1 passphrase credentials with open BSS");
554    }
555
556    #[test]
557    fn protection_from_open_authenticator_with_wpa1_bss() {
558        let device = crate::test_utils::fake_device_info([1u8; 6].into());
559        let security_support = fake_security_support();
560        let config = Default::default();
561        let bss = fake_bss_description!(Wpa1);
562        // Note that there is no credentials type associated with an open authenticator, so an
563        // authenticator is used here instead.
564        let authenticator = SecurityAuthenticator::Open;
565        let _ = Protection::try_from(SecurityContext {
566            security: &authenticator,
567            device: &device,
568            security_support: &security_support,
569            config: &config,
570            bss: &bss,
571        })
572        .expect_err("incorrectly accepted open authenticator with WPA1 BSS");
573    }
574
575    #[test]
576    fn protection_from_wpa2_personal_passphrase_with_wpa3_bss() {
577        let device = crate::test_utils::fake_device_info([1u8; 6].into());
578        let security_support = fake_security_support();
579        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
580        let bss = fake_bss_description!(Wpa3);
581        let credentials =
582            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
583        let _ = Protection::try_from(SecurityContext {
584            security: &credentials,
585            device: &device,
586            security_support: &security_support,
587            config: &config,
588            bss: &bss,
589        })
590        .expect_err("incorrectly accepted WPA2 passphrase credentials with WPA3 BSS");
591    }
592
593    #[test]
594    fn wpa1_psk_rsna() {
595        let device = crate::test_utils::fake_device_info([1u8; 6].into());
596        let security_support = fake_security_support();
597        let config = Default::default();
598        let bss = fake_bss_description!(Wpa1);
599        let credentials = wpa::Wpa1Credentials::Psk([1u8; PSK_SIZE_BYTES].into());
600        let context = SecurityContext {
601            security: &credentials,
602            device: &device,
603            security_support: &security_support,
604            config: &config,
605            bss: &bss,
606        };
607        assert!(context.authenticator_supplicant_ie().is_ok());
608        assert!(matches!(context.authentication_config(), auth::Config::ComputedPsk(_)));
609
610        let protection = Protection::try_from(context).unwrap();
611        assert_variant!(protection, Protection::LegacyWpa(rsna) => {
612            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Psk);
613        });
614    }
615
616    #[test]
617    fn wpa2_personal_psk_rsna() {
618        let device = crate::test_utils::fake_device_info([1u8; 6].into());
619        let security_support = fake_security_support();
620        let config = Default::default();
621        let bss = fake_bss_description!(Wpa2);
622        let credentials = wpa::Wpa2PersonalCredentials::Psk([1u8; PSK_SIZE_BYTES].into());
623        let context = SecurityContext {
624            security: &credentials,
625            device: &device,
626            security_support: &security_support,
627            config: &config,
628            bss: &bss,
629        };
630        assert!(context.authenticator_supplicant_rsne().is_ok());
631        assert!(matches!(context.authentication_config(), auth::Config::ComputedPsk(_)));
632
633        let protection = Protection::try_from(context).unwrap();
634        assert_variant!(protection, Protection::Rsna(rsna) => {
635            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Psk);
636        });
637    }
638
639    #[test]
640    fn wpa3_personal_passphrase_rsna_sme_auth() {
641        let device = crate::test_utils::fake_device_info([1u8; 6].into());
642        let security_support = fake_security_support();
643        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
644        let bss = fake_bss_description!(Wpa3);
645        let credentials =
646            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
647        let context = SecurityContext {
648            security: &credentials,
649            device: &device,
650            security_support: &security_support,
651            config: &config,
652            bss: &bss,
653        };
654        assert!(context.authenticator_supplicant_rsne().is_ok());
655        assert!(matches!(context.authentication_config(), Ok(auth::Config::Sae { .. })));
656
657        let protection = Protection::try_from(context).unwrap();
658        assert_variant!(protection, Protection::Rsna(rsna) => {
659            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
660        });
661    }
662
663    #[test]
664    fn wpa3_personal_passphrase_rsna_driver_auth() {
665        let device = crate::test_utils::fake_device_info([1u8; 6].into());
666        let mut security_support = fake_security_support_empty();
667        security_support.mfp.supported = true;
668        security_support.sae.driver_handler_supported = true;
669        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
670        let bss = fake_bss_description!(Wpa3);
671        let credentials =
672            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
673        let context = SecurityContext {
674            security: &credentials,
675            device: &device,
676            security_support: &security_support,
677            config: &config,
678            bss: &bss,
679        };
680        assert!(context.authenticator_supplicant_rsne().is_ok());
681        assert!(matches!(context.authentication_config(), Ok(auth::Config::DriverSae { .. })));
682
683        let protection = Protection::try_from(context).unwrap();
684        assert_variant!(protection, Protection::Rsna(rsna) => {
685            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
686        });
687    }
688
689    #[test]
690    fn wpa3_personal_passphrase_prefer_sme_auth() {
691        let device = crate::test_utils::fake_device_info([1u8; 6].into());
692        let mut security_support = fake_security_support_empty();
693        security_support.mfp.supported = true;
694        security_support.sae.driver_handler_supported = true;
695        security_support.sae.sme_handler_supported = true;
696        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
697        let bss = fake_bss_description!(Wpa3);
698        let credentials =
699            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
700        let context = SecurityContext {
701            security: &credentials,
702            device: &device,
703            security_support: &security_support,
704            config: &config,
705            bss: &bss,
706        };
707        assert!(matches!(context.authentication_config(), Ok(auth::Config::Sae { .. })));
708    }
709
710    #[test]
711    fn wpa3_personal_passphrase_no_security_support_features() {
712        let device = crate::test_utils::fake_device_info([1u8; 6].into());
713        let security_support = fake_security_support_empty();
714        let config = Default::default();
715        let bss = fake_bss_description!(Wpa3);
716        let credentials =
717            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
718        let context = SecurityContext {
719            security: &credentials,
720            device: &device,
721            security_support: &security_support,
722            config: &config,
723            bss: &bss,
724        };
725        let _ = context
726            .authentication_config()
727            .expect_err("created WPA3 auth config for incompatible device");
728    }
729}