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, PweMethod};
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
103struct OweAuthenticator;
104
105impl SecurityContext<'_, OweAuthenticator> {
106    /// Gets the authenticator and supplicant RSNEs for OWE from the associated BSS.
107    fn authenticator_supplicant_rsne(&self) -> Result<(Rsne, Rsne), Error> {
108        let a_rsne_ie = self
109            .bss
110            .rsne()
111            .ok_or_else(|| format_err!("OWE requested but RSNE is not present in BSS."))?;
112        let (_, a_rsne) = rsne::from_bytes(a_rsne_ie)
113            .map_err(|error| format_err!("Invalid RSNE IE {:02x?}: {:?}", a_rsne_ie, error))?;
114        let s_rsne = a_rsne.derive_owe_s_rsne(self.security_support)?;
115        Ok((a_rsne, s_rsne))
116    }
117
118    fn authentication_config(&self) -> Result<auth::Config, Error> {
119        Ok(auth::Config::Owe)
120    }
121}
122
123impl SecurityContext<'_, wpa::Wpa1Credentials> {
124    /// Gets the authenticator and supplicant IEs for WPA1 from the associated BSS.
125    fn authenticator_supplicant_ie(&self) -> Result<(WpaIe, WpaIe), Error> {
126        let a_wpa_ie = self.bss.wpa_ie()?;
127        if !crate::client::wpa::is_legacy_wpa_compatible(&a_wpa_ie) {
128            return Err(format_err!("Legacy WPA requested but IE is incompatible: {:?}", a_wpa_ie));
129        }
130        let s_wpa_ie = crate::client::wpa::construct_s_wpa(&a_wpa_ie);
131        Ok((a_wpa_ie, s_wpa_ie))
132    }
133
134    /// Gets the PSK used to authenticate via WPA1.
135    fn authentication_config(&self) -> auth::Config {
136        auth::Config::ComputedPsk(self.security.to_psk(&self.bss.ssid).into())
137    }
138}
139
140impl SecurityContext<'_, wpa::Wpa2PersonalCredentials> {
141    /// Gets the authenticator and supplicant RSNEs for WPA2 Personal from the associated BSS.
142    fn authenticator_supplicant_rsne(&self) -> Result<(Rsne, Rsne), Error> {
143        let a_rsne_ie = self
144            .bss
145            .rsne()
146            .ok_or_else(|| format_err!("WPA2 requested but RSNE is not present in BSS."))?;
147        let (_, a_rsne) = rsne::from_bytes(a_rsne_ie)
148            .map_err(|error| format_err!("Invalid RSNE IE {:02x?}: {:?}", a_rsne_ie, error))?;
149        let s_rsne = a_rsne.derive_wpa2_s_rsne(self.security_support)?;
150        Ok((a_rsne, s_rsne))
151    }
152
153    /// Gets the PSK used to authenticate via WPA2 Personal.
154    fn authentication_config(&self) -> auth::Config {
155        auth::Config::ComputedPsk(self.security.to_psk(&self.bss.ssid).into())
156    }
157}
158
159impl SecurityContext<'_, wpa::Wpa3PersonalCredentials> {
160    /// Gets the authenticator and supplicant RSNEs for WPA3 Personal from the associated BSS.
161    fn authenticator_supplicant_rsne(&self) -> Result<(Rsne, Rsne), Error> {
162        let a_rsne_ie = self
163            .bss
164            .rsne()
165            .ok_or_else(|| format_err!("WPA3 requested but RSNE is not present in BSS."))?;
166        let (_, a_rsne) = rsne::from_bytes(a_rsne_ie)
167            .map_err(|error| format_err!("Invalid RSNE IE {:02x?}: {:?}", a_rsne_ie, error))?;
168        let s_rsne = a_rsne.derive_wpa3_s_rsne(self.security_support)?;
169        Ok((a_rsne, s_rsne))
170    }
171
172    /// Gets the SAE used to authenticate via WPA3 Personal.
173    fn authentication_config(&self) -> Result<auth::Config, Error> {
174        match self.security {
175            wpa::Wpa3PersonalCredentials::Passphrase(passphrase) => {
176                // Prefer SAE in SME.
177                if self
178                    .security_support
179                    .sae
180                    .as_ref()
181                    .and_then(|sae| sae.sme_handler_supported)
182                    .unwrap_or(false)
183                {
184                    let direct_hash_supported_by_peer = self
185                        .bss
186                        .rsnxe()
187                        .and_then(|rsnxe| rsnxe.rsnxe_octet_1)
188                        .map(|octet| octet.sae_hash_to_element())
189                        .unwrap_or(false);
190                    let direct_hash_supported_by_driver = self
191                        .security_support
192                        .sae
193                        .as_ref()
194                        .and_then(|sae| sae.hash_to_element_supported)
195                        .unwrap_or(false);
196                    Ok(auth::Config::Sae {
197                        ssid: self.bss.ssid.clone(),
198                        password: passphrase.clone().into(),
199                        mac: self.device.sta_addr.into(),
200                        peer_mac: self.bss.bssid.into(),
201                        pwe_method: if direct_hash_supported_by_peer
202                            && direct_hash_supported_by_driver
203                        {
204                            PweMethod::Direct
205                        } else {
206                            PweMethod::Loop
207                        },
208                    })
209                } else if self
210                    .security_support
211                    .sae
212                    .as_ref()
213                    .and_then(|sae| sae.driver_handler_supported)
214                    .unwrap_or(false)
215                {
216                    Ok(auth::Config::DriverSae { password: passphrase.clone().into() })
217                } else {
218                    Err(format_err!(
219                        "Failed to generate WPA3 authentication config: no SAE SME nor driver \
220                         handler"
221                    ))
222                }
223            }
224        }
225    }
226}
227
228impl<'a> TryFrom<SecurityContext<'a, SecurityAuthenticator>> for Protection {
229    type Error = Error;
230
231    fn try_from(context: SecurityContext<'a, SecurityAuthenticator>) -> Result<Self, Self::Error> {
232        match context.security {
233            SecurityAuthenticator::Open => context
234                .bss
235                .is_open()
236                .then(|| Protection::Open)
237                .ok_or_else(|| format_err!("BSS is not configured for open authentication")),
238            SecurityAuthenticator::Owe => context.map(&OweAuthenticator).try_into(),
239            SecurityAuthenticator::Wep(authenticator) => context.map(authenticator).try_into(),
240            SecurityAuthenticator::Wpa(wpa) => match wpa {
241                wpa::WpaAuthenticator::Wpa1 { credentials, .. } => {
242                    context.map(credentials).try_into()
243                }
244                wpa::WpaAuthenticator::Wpa2 { authentication, .. } => match authentication {
245                    wpa::Authentication::Personal(personal) => context.map(personal).try_into(),
246                    // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
247                    _ => Err(format_err!("WPA Enterprise is unsupported")),
248                },
249                wpa::WpaAuthenticator::Wpa3 { authentication, .. } => match authentication {
250                    wpa::Authentication::Personal(personal) => context.map(personal).try_into(),
251                    // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
252                    _ => Err(format_err!("WPA Enterprise is unsupported")),
253                },
254            },
255        }
256    }
257}
258
259impl<'a> TryFrom<SecurityContext<'a, OweAuthenticator>> for Protection {
260    type Error = Error;
261
262    fn try_from(context: SecurityContext<'a, OweAuthenticator>) -> Result<Self, Self::Error> {
263        context
264            .bss
265            .has_owe_configured()
266            .then(|| -> Result<_, Self::Error> {
267                let sta_addr = context.device.sta_addr.into();
268                if !context.config.owe_supported {
269                    return Err(format_err!("OWE requested but client does not support OWE"));
270                }
271                let (a_rsne, s_rsne) = context.authenticator_supplicant_rsne()?;
272                let negotiated_protection = NegotiatedProtection::from_rsne(&s_rsne)?;
273                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
274                    NonceReader::new(&sta_addr)?,
275                    context.authentication_config()?,
276                    sta_addr,
277                    ProtectionInfo::Rsne(s_rsne),
278                    context.bss.bssid.into(),
279                    ProtectionInfo::Rsne(a_rsne),
280                )
281                .map_err(|error| format_err!("Failed to create ESS-SA: {:?}", error))?;
282                Ok(Protection::Rsna(Rsna {
283                    negotiated_protection,
284                    supplicant: Box::new(supplicant),
285                }))
286            })
287            .transpose()?
288            .ok_or_else(|| format_err!("BSS is not configured for OWE"))
289    }
290}
291
292impl<'a> TryFrom<SecurityContext<'a, wep::WepAuthenticator>> for Protection {
293    type Error = Error;
294
295    fn try_from(context: SecurityContext<'a, wep::WepAuthenticator>) -> Result<Self, Self::Error> {
296        context
297            .bss
298            .has_wep_configured()
299            .then(|| Protection::Wep(context.security.key.clone()))
300            .ok_or_else(|| format_err!("BSS is not configured for WEP"))
301    }
302}
303
304impl<'a> TryFrom<SecurityContext<'a, wpa::Wpa1Credentials>> for Protection {
305    type Error = Error;
306
307    fn try_from(context: SecurityContext<'a, wpa::Wpa1Credentials>) -> Result<Self, Self::Error> {
308        context
309            .bss
310            .has_wpa1_configured()
311            .then(|| -> Result<_, Self::Error> {
312                let sta_addr = context.device.sta_addr.into();
313                let (a_wpa_ie, s_wpa_ie) = context.authenticator_supplicant_ie()?;
314                let negotiated_protection = NegotiatedProtection::from_legacy_wpa(&s_wpa_ie)?;
315                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
316                    NonceReader::new(&sta_addr)?,
317                    context.authentication_config(),
318                    sta_addr,
319                    ProtectionInfo::LegacyWpa(s_wpa_ie),
320                    context.bss.bssid.into(),
321                    ProtectionInfo::LegacyWpa(a_wpa_ie),
322                )
323                .map_err(|error| format_err!("Failed to create ESS-SA: {:?}", error))?;
324                Ok(Protection::LegacyWpa(Rsna {
325                    negotiated_protection,
326                    supplicant: Box::new(supplicant),
327                }))
328            })
329            .transpose()?
330            .ok_or_else(|| format_err!("BSS is not configured for WPA1"))
331    }
332}
333
334impl<'a> TryFrom<SecurityContext<'a, wpa::Wpa2PersonalCredentials>> for Protection {
335    type Error = Error;
336
337    fn try_from(
338        context: SecurityContext<'a, wpa::Wpa2PersonalCredentials>,
339    ) -> Result<Self, Self::Error> {
340        context
341            .bss
342            .has_wpa2_personal_configured()
343            .then(|| -> Result<_, Self::Error> {
344                let sta_addr = context.device.sta_addr.into();
345                let (a_rsne, s_rsne) = context.authenticator_supplicant_rsne()?;
346                let negotiated_protection = NegotiatedProtection::from_rsne(&s_rsne)?;
347                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
348                    NonceReader::new(&sta_addr)?,
349                    context.authentication_config(),
350                    sta_addr,
351                    ProtectionInfo::Rsne(s_rsne),
352                    context.bss.bssid.into(),
353                    ProtectionInfo::Rsne(a_rsne),
354                )
355                .map_err(|error| format_err!("Failed to creat ESS-SA: {:?}", error))?;
356                Ok(Protection::Rsna(Rsna {
357                    negotiated_protection,
358                    supplicant: Box::new(supplicant),
359                }))
360            })
361            .transpose()?
362            .ok_or_else(|| format_err!("BSS is not configured for WPA2 Personal"))
363    }
364}
365
366impl<'a> TryFrom<SecurityContext<'a, wpa::Wpa3PersonalCredentials>> for Protection {
367    type Error = Error;
368
369    fn try_from(
370        context: SecurityContext<'a, wpa::Wpa3PersonalCredentials>,
371    ) -> Result<Self, Self::Error> {
372        context
373            .bss
374            .has_wpa3_personal_configured()
375            .then(|| -> Result<_, Self::Error> {
376                let sta_addr = context.device.sta_addr.into();
377                if !context.config.wpa3_supported {
378                    return Err(format_err!("WPA3 requested but client does not support WPA3"));
379                }
380                let (a_rsne, s_rsne) = context.authenticator_supplicant_rsne()?;
381                let negotiated_protection = NegotiatedProtection::from_rsne(&s_rsne)?;
382                let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
383                    NonceReader::new(&sta_addr)?,
384                    context.authentication_config()?,
385                    sta_addr,
386                    ProtectionInfo::Rsne(s_rsne),
387                    context.bss.bssid.into(),
388                    ProtectionInfo::Rsne(a_rsne),
389                )
390                .map_err(|error| format_err!("Failed to create ESS-SA: {:?}", error))?;
391                Ok(Protection::Rsna(Rsna {
392                    negotiated_protection,
393                    supplicant: Box::new(supplicant),
394                }))
395            })
396            .transpose()?
397            .ok_or_else(|| format_err!("BSS is not configured for WPA3 Personal"))
398    }
399}
400
401/// Based on the type of protection, derive either RSNE or Vendor IEs:
402/// No Protection or WEP: Neither
403/// WPA2: RSNE
404/// WPA1: Vendor IEs
405pub(crate) fn build_protection_ie(protection: &Protection) -> Result<Option<ProtectionIe>, Error> {
406    match protection {
407        Protection::Open | Protection::Wep(_) => Ok(None),
408        Protection::LegacyWpa(rsna) => {
409            let s_protection = rsna.negotiated_protection.to_full_protection();
410            let s_wpa = match s_protection {
411                ProtectionInfo::Rsne(_) => {
412                    return Err(format_err!("found RSNE protection inside a WPA1 association..."));
413                }
414                ProtectionInfo::LegacyWpa(wpa) => wpa,
415            };
416            let mut buf = vec![];
417            #[expect(clippy::unwrap_used)]
418            ie::write_wpa1_ie(&mut buf, &s_wpa).unwrap(); // Writing to a Vec never fails
419            Ok(Some(ProtectionIe::VendorIes(buf)))
420        }
421        Protection::Rsna(rsna) => {
422            let s_protection = rsna.negotiated_protection.to_full_protection();
423            let s_rsne = match s_protection {
424                ProtectionInfo::Rsne(rsne) => rsne,
425                ProtectionInfo::LegacyWpa(_) => {
426                    return Err(format_err!("found WPA protection inside an RSNA..."));
427                }
428            };
429            let mut buf = Vec::with_capacity(s_rsne.len());
430            // Writing an RSNE into a Vector can never fail as a Vector can be grown when more
431            // space is required. If this panic ever triggers, something is clearly broken
432            // somewhere else.
433            #[expect(clippy::unwrap_used)]
434            let () = s_rsne.write_into(&mut buf).unwrap();
435            Ok(Some(ProtectionIe::Rsne(buf)))
436        }
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443    use crate::client::{self};
444    use assert_matches::assert_matches;
445    use test_case::test_case;
446    use wlan_common::fake_bss_description;
447    use wlan_common::ie::fake_ies::fake_wpa_ie;
448    use wlan_common::ie::rsn::fake_rsnes::{fake_wpa2_s_rsne, fake_wpa3_s_rsne};
449    use wlan_common::security::wep::{WEP40_KEY_BYTES, WEP104_KEY_BYTES};
450    use wlan_common::security::wpa::credential::PSK_SIZE_BYTES;
451    use wlan_common::test_utils::fake_features::{
452        fake_security_support, fake_security_support_empty,
453    };
454
455    #[test]
456    fn rsn_auth_method() {
457        // Open
458        let protection = Protection::Open;
459        assert!(protection.rsn_auth_method().is_none());
460
461        // Wep
462        let protection = Protection::Wep(WepKey::parse([1; 5]).expect("unable to parse WEP key"));
463        assert!(protection.rsn_auth_method().is_none());
464
465        // WPA1
466        let protection_info = ProtectionInfo::LegacyWpa(fake_wpa_ie());
467        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
468            .expect("could create mocked WPA1 NegotiatedProtection");
469        let protection = Protection::LegacyWpa(Rsna {
470            negotiated_protection,
471            supplicant: Box::new(client::test_utils::mock_psk_supplicant().0),
472        });
473        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Psk));
474
475        // WPA2
476        let protection_info = ProtectionInfo::Rsne(fake_wpa2_s_rsne());
477        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
478            .expect("could create mocked WPA2 NegotiatedProtection");
479        let protection = Protection::Rsna(Rsna {
480            negotiated_protection,
481            supplicant: Box::new(client::test_utils::mock_psk_supplicant().0),
482        });
483        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Psk));
484
485        // WPA3
486        let protection_info = ProtectionInfo::Rsne(fake_wpa3_s_rsne());
487        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
488            .expect("could create mocked WPA3 NegotiatedProtection");
489        let protection = Protection::Rsna(Rsna {
490            negotiated_protection,
491            supplicant: Box::new(client::test_utils::mock_sae_supplicant().0),
492        });
493        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Sae));
494
495        // OWE
496        let protection_info = ProtectionInfo::Rsne(Rsne::common_owe_rsne());
497        let negotiated_protection = NegotiatedProtection::from_protection(&protection_info)
498            .expect("could create mocked OWE NegotiatedProtection");
499        let protection = Protection::Rsna(Rsna {
500            negotiated_protection,
501            supplicant: Box::new(client::test_utils::mock_owe_supplicant().0),
502        });
503        assert_eq!(protection.rsn_auth_method(), Some(auth::MethodName::Owe));
504    }
505
506    #[test]
507    fn protection_from_wep40() {
508        let device = crate::test_utils::fake_device_info([1u8; 6].into());
509        let security_support = fake_security_support();
510        let config = Default::default();
511        let bss = fake_bss_description!(Wep);
512        let authenticator = wep::WepAuthenticator { key: WepKey::from([1u8; WEP40_KEY_BYTES]) };
513        let protection = Protection::try_from(SecurityContext {
514            security: &authenticator,
515            device: &device,
516            security_support: &security_support,
517            config: &config,
518            bss: &bss,
519        })
520        .unwrap();
521        assert!(matches!(protection, Protection::Wep(_)));
522    }
523
524    #[test]
525    fn protection_from_wep104() {
526        let device = crate::test_utils::fake_device_info([1u8; 6].into());
527        let security_support = fake_security_support();
528        let config = Default::default();
529        let bss = fake_bss_description!(Wep);
530        let authenticator = wep::WepAuthenticator { key: WepKey::from([1u8; WEP104_KEY_BYTES]) };
531        let protection = Protection::try_from(SecurityContext {
532            security: &authenticator,
533            device: &device,
534            security_support: &security_support,
535            config: &config,
536            bss: &bss,
537        })
538        .unwrap();
539        assert!(matches!(protection, Protection::Wep(_)));
540    }
541
542    #[test]
543    fn protection_from_wpa1_psk() {
544        let device = crate::test_utils::fake_device_info([1u8; 6].into());
545        let security_support = fake_security_support();
546        let config = Default::default();
547        let bss = fake_bss_description!(Wpa1);
548        let credentials = wpa::Wpa1Credentials::Psk([1u8; PSK_SIZE_BYTES].into());
549        let protection = Protection::try_from(SecurityContext {
550            security: &credentials,
551            device: &device,
552            security_support: &security_support,
553            config: &config,
554            bss: &bss,
555        })
556        .unwrap();
557        assert!(matches!(protection, Protection::LegacyWpa(_)));
558    }
559
560    #[test]
561    fn protection_from_wpa2_personal_psk() {
562        let device = crate::test_utils::fake_device_info([1u8; 6].into());
563        let security_support = fake_security_support();
564        let config = Default::default();
565        let bss = fake_bss_description!(Wpa2);
566        let credentials = wpa::Wpa2PersonalCredentials::Psk([1u8; PSK_SIZE_BYTES].into());
567        let protection = Protection::try_from(SecurityContext {
568            security: &credentials,
569            device: &device,
570            security_support: &security_support,
571            config: &config,
572            bss: &bss,
573        })
574        .unwrap();
575        assert!(matches!(protection, Protection::Rsna(_)));
576    }
577
578    #[test]
579    fn protection_from_wpa2_personal_passphrase() {
580        let device = crate::test_utils::fake_device_info([1u8; 6].into());
581        let security_support = fake_security_support();
582        let config = Default::default();
583        let bss = fake_bss_description!(Wpa2);
584        let credentials =
585            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
586        let protection = Protection::try_from(SecurityContext {
587            security: &credentials,
588            device: &device,
589            security_support: &security_support,
590            config: &config,
591            bss: &bss,
592        })
593        .unwrap();
594        assert!(matches!(protection, Protection::Rsna(_)));
595    }
596
597    #[test]
598    fn protection_from_wpa2_personal_tkip_only_passphrase() {
599        let device = crate::test_utils::fake_device_info([1u8; 6].into());
600        let security_support = fake_security_support();
601        let config = Default::default();
602        let bss = fake_bss_description!(Wpa2TkipOnly);
603        let credentials =
604            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
605        let protection = Protection::try_from(SecurityContext {
606            security: &credentials,
607            device: &device,
608            security_support: &security_support,
609            config: &config,
610            bss: &bss,
611        })
612        .unwrap();
613        assert!(matches!(protection, Protection::Rsna(_)));
614    }
615
616    #[test]
617    fn protection_from_wpa3_personal_passphrase() {
618        let device = crate::test_utils::fake_device_info([1u8; 6].into());
619        let security_support = fake_security_support();
620        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
621        let bss = fake_bss_description!(Wpa3);
622        let credentials =
623            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
624        let protection = Protection::try_from(SecurityContext {
625            security: &credentials,
626            device: &device,
627            security_support: &security_support,
628            config: &config,
629            bss: &bss,
630        })
631        .unwrap();
632        assert!(matches!(protection, Protection::Rsna(_)));
633    }
634
635    #[test]
636    fn protection_from_owe() {
637        let device = crate::test_utils::fake_device_info([1u8; 6].into());
638        let security_support = fake_security_support();
639        let config = ClientConfig { owe_supported: true, ..Default::default() };
640        let bss = fake_bss_description!(Owe);
641        let security = SecurityAuthenticator::Owe;
642        let protection = Protection::try_from(SecurityContext {
643            security: &security,
644            device: &device,
645            security_support: &security_support,
646            config: &config,
647            bss: &bss,
648        })
649        .unwrap();
650        assert!(matches!(protection, Protection::Rsna(_)));
651    }
652
653    #[test]
654    fn protection_from_owe_no_security_support_features() {
655        let device = crate::test_utils::fake_device_info([1u8; 6].into());
656        let security_support = fake_security_support_empty();
657        let config = Default::default();
658        let bss = fake_bss_description!(Owe);
659        let security = SecurityAuthenticator::Owe;
660        let protection = Protection::try_from(SecurityContext {
661            security: &security,
662            device: &device,
663            security_support: &security_support,
664            config: &config,
665            bss: &bss,
666        });
667        let _ = protection.expect_err("created OWE auth config for incompatible device");
668    }
669
670    #[test]
671    fn protection_from_wpa1_passphrase_with_open_bss() {
672        let device = crate::test_utils::fake_device_info([1u8; 6].into());
673        let security_support = fake_security_support();
674        let config = Default::default();
675        let bss = fake_bss_description!(Open);
676        let credentials =
677            wpa::Wpa1Credentials::Passphrase("password".as_bytes().try_into().unwrap());
678        let _ = Protection::try_from(SecurityContext {
679            security: &credentials,
680            device: &device,
681            security_support: &security_support,
682            config: &config,
683            bss: &bss,
684        })
685        .expect_err("incorrectly accepted WPA1 passphrase credentials with open BSS");
686    }
687
688    #[test]
689    fn protection_from_open_authenticator_with_wpa1_bss() {
690        let device = crate::test_utils::fake_device_info([1u8; 6].into());
691        let security_support = fake_security_support();
692        let config = Default::default();
693        let bss = fake_bss_description!(Wpa1);
694        // Note that there is no credentials type associated with an open authenticator, so an
695        // authenticator is used here instead.
696        let authenticator = SecurityAuthenticator::Open;
697        let _ = Protection::try_from(SecurityContext {
698            security: &authenticator,
699            device: &device,
700            security_support: &security_support,
701            config: &config,
702            bss: &bss,
703        })
704        .expect_err("incorrectly accepted open authenticator with WPA1 BSS");
705    }
706
707    #[test]
708    fn protection_from_wpa2_personal_passphrase_with_wpa3_bss() {
709        let device = crate::test_utils::fake_device_info([1u8; 6].into());
710        let security_support = fake_security_support();
711        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
712        let bss = fake_bss_description!(Wpa3);
713        let credentials =
714            wpa::Wpa2PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
715        let _ = Protection::try_from(SecurityContext {
716            security: &credentials,
717            device: &device,
718            security_support: &security_support,
719            config: &config,
720            bss: &bss,
721        })
722        .expect_err("incorrectly accepted WPA2 passphrase credentials with WPA3 BSS");
723    }
724
725    #[test]
726    fn wpa1_psk_rsna() {
727        let device = crate::test_utils::fake_device_info([1u8; 6].into());
728        let security_support = fake_security_support();
729        let config = Default::default();
730        let bss = fake_bss_description!(Wpa1);
731        let credentials = wpa::Wpa1Credentials::Psk([1u8; PSK_SIZE_BYTES].into());
732        let context = SecurityContext {
733            security: &credentials,
734            device: &device,
735            security_support: &security_support,
736            config: &config,
737            bss: &bss,
738        };
739        assert!(context.authenticator_supplicant_ie().is_ok());
740        assert!(matches!(context.authentication_config(), auth::Config::ComputedPsk(_)));
741
742        let protection = Protection::try_from(context).unwrap();
743        assert_matches!(protection, Protection::LegacyWpa(rsna) => {
744            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Psk);
745        });
746    }
747
748    #[test]
749    fn wpa2_personal_psk_rsna() {
750        let device = crate::test_utils::fake_device_info([1u8; 6].into());
751        let security_support = fake_security_support();
752        let config = Default::default();
753        let bss = fake_bss_description!(Wpa2);
754        let credentials = wpa::Wpa2PersonalCredentials::Psk([1u8; PSK_SIZE_BYTES].into());
755        let context = SecurityContext {
756            security: &credentials,
757            device: &device,
758            security_support: &security_support,
759            config: &config,
760            bss: &bss,
761        };
762        assert!(context.authenticator_supplicant_rsne().is_ok());
763        assert!(matches!(context.authentication_config(), auth::Config::ComputedPsk(_)));
764
765        let protection = Protection::try_from(context).unwrap();
766        assert_matches!(protection, Protection::Rsna(rsna) => {
767            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Psk);
768        });
769    }
770
771    #[test]
772    fn wpa3_personal_passphrase_rsna_sme_auth() {
773        let device = crate::test_utils::fake_device_info([1u8; 6].into());
774        let security_support = fake_security_support();
775        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
776        let bss = fake_bss_description!(Wpa3);
777        let credentials =
778            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
779        let context = SecurityContext {
780            security: &credentials,
781            device: &device,
782            security_support: &security_support,
783            config: &config,
784            bss: &bss,
785        };
786        assert!(context.authenticator_supplicant_rsne().is_ok());
787        assert!(matches!(context.authentication_config(), Ok(auth::Config::Sae { .. })));
788
789        let protection = Protection::try_from(context).unwrap();
790        assert_matches!(protection, Protection::Rsna(rsna) => {
791            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
792        });
793    }
794
795    #[test_case(false, fake_bss_description!(Wpa3), PweMethod::Loop; "looping")]
796    #[test_case(true, fake_bss_description!(Wpa3), PweMethod::Loop; "only driver supports direct")]
797    #[test_case(false, fake_bss_description!(Wpa3, sae_hash_to_element: true), PweMethod::Loop; "only peer supports direct")]
798    #[test_case(true, fake_bss_description!(Wpa3, sae_hash_to_element: true), PweMethod::Direct; "direct")]
799    fn wpa3_personal_passphrase_rsna_sme_auth_hash_to_element(
800        driver_supports_h2e: bool,
801        bss: BssDescription,
802        expected_pwe_method: PweMethod,
803    ) {
804        let device = crate::test_utils::fake_device_info([1u8; 6].into());
805        let mut security_support = fake_security_support();
806        if driver_supports_h2e {
807            security_support.sae.as_mut().unwrap().hash_to_element_supported = Some(true);
808        }
809        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
810        let credentials =
811            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
812        let context = SecurityContext {
813            security: &credentials,
814            device: &device,
815            security_support: &security_support,
816            config: &config,
817            bss: &bss,
818        };
819        assert!(context.authenticator_supplicant_rsne().is_ok());
820        let pwe_method = assert_matches!(
821            context.authentication_config(),
822            Ok(auth::Config::Sae { pwe_method, .. }) => pwe_method
823        );
824        assert_eq!(pwe_method, expected_pwe_method);
825
826        let protection = Protection::try_from(context).unwrap();
827        assert_matches!(protection, Protection::Rsna(rsna) => {
828            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
829        });
830    }
831
832    #[test]
833    fn wpa3_personal_passphrase_rsna_driver_auth() {
834        let device = crate::test_utils::fake_device_info([1u8; 6].into());
835        let mut security_support = fake_security_support_empty();
836        security_support.mfp.get_or_insert_with(Default::default).supported = Some(true);
837        security_support.sae.get_or_insert_with(Default::default).driver_handler_supported =
838            Some(true);
839        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
840        let bss = fake_bss_description!(Wpa3);
841        let credentials =
842            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
843        let context = SecurityContext {
844            security: &credentials,
845            device: &device,
846            security_support: &security_support,
847            config: &config,
848            bss: &bss,
849        };
850        assert!(context.authenticator_supplicant_rsne().is_ok());
851        assert!(matches!(context.authentication_config(), Ok(auth::Config::DriverSae { .. })));
852
853        let protection = Protection::try_from(context).unwrap();
854        assert_matches!(protection, Protection::Rsna(rsna) => {
855            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Sae);
856        });
857    }
858
859    #[test]
860    fn wpa3_personal_passphrase_prefer_sme_auth() {
861        let device = crate::test_utils::fake_device_info([1u8; 6].into());
862        let mut security_support = fake_security_support_empty();
863        security_support.mfp.get_or_insert_with(Default::default).supported = Some(true);
864        security_support.sae.get_or_insert_with(Default::default).driver_handler_supported =
865            Some(true);
866        security_support.sae.get_or_insert_with(Default::default).sme_handler_supported =
867            Some(true);
868        let config = ClientConfig { wpa3_supported: true, ..Default::default() };
869        let bss = fake_bss_description!(Wpa3);
870        let credentials =
871            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
872        let context = SecurityContext {
873            security: &credentials,
874            device: &device,
875            security_support: &security_support,
876            config: &config,
877            bss: &bss,
878        };
879        assert!(matches!(context.authentication_config(), Ok(auth::Config::Sae { .. })));
880    }
881
882    #[test]
883    fn wpa3_personal_passphrase_no_security_support_features() {
884        let device = crate::test_utils::fake_device_info([1u8; 6].into());
885        let security_support = fake_security_support_empty();
886        let config = Default::default();
887        let bss = fake_bss_description!(Wpa3);
888        let credentials =
889            wpa::Wpa3PersonalCredentials::Passphrase("password".as_bytes().try_into().unwrap());
890        let context = SecurityContext {
891            security: &credentials,
892            device: &device,
893            security_support: &security_support,
894            config: &config,
895            bss: &bss,
896        };
897        let _ = context
898            .authentication_config()
899            .expect_err("created WPA3 auth config for incompatible device");
900    }
901
902    #[test]
903    fn owe_rsna_sme_auth() {
904        let device = crate::test_utils::fake_device_info([1u8; 6].into());
905        let security_support = fake_security_support();
906        let config = ClientConfig { owe_supported: true, ..Default::default() };
907        let bss = fake_bss_description!(Owe);
908        let security = OweAuthenticator;
909        let context = SecurityContext {
910            security: &security,
911            device: &device,
912            security_support: &security_support,
913            config: &config,
914            bss: &bss,
915        };
916        assert!(context.authenticator_supplicant_rsne().is_ok());
917        assert!(matches!(context.authentication_config(), Ok(auth::Config::Owe)));
918
919        let protection = Protection::try_from(context).unwrap();
920        assert_matches!(protection, Protection::Rsna(rsna) => {
921            assert_eq!(rsna.supplicant.get_auth_method(), auth::MethodName::Owe);
922        });
923    }
924}