wlan_common/security/
mod.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Wireless network security descriptors and authenticators.
6//!
7//! This module describes wireless network security protocols as well as credentials used to
8//! authenticate using those protocols. Types in this module provide two important features:
9//! conversions from and into FIDL types used within the WLAN stack and consistency. Here,
10//! consistency means that types have no invalid values; given an instance of a type, it is always
11//! valid. For example, a `SecurityAuthenticator` always represents a valid protocol-credential
12//! pair.
13//!
14//! The `SecurityDescriptor` and `SecurityAuthenticator` types form the primary API of this module.
15//! A _descriptor_ merely describes (names) a security protocol such as WPA2 Personal using TKIP.
16//! An _authenticator_ describes a security protocol and additionally contains credentials used to
17//! authenticate using that protocol such as WPA Personal using TKIP with a PSK credential.
18//! Authenticators can be converted into descriptors (which drops any associated credentials).
19//!
20//! # FIDL
21//!
22//! Types in this module support conversion into and from datagrams in the
23//! `fuchsia.wlan.common.security` package. When interacting with FIDL, most code should prefer
24//! conversions between FIDL types and this module's `SecurityDescriptor` and
25//! `SecurityAuthenticator` types, though conversions for intermediate types are also provided.
26//!
27//! The models used by this module and the FIDL package differ, so types do not always have a
28//! direct analog, but the table below describes the most straightforward and important mappings.
29//!
30//! | Rust Type               | FIDL Type        | Rust to FIDL | FIDL to Rust |
31//! |-------------------------|------------------|--------------|--------------|
32//! | `SecurityDescriptor`    | `Protocol`       | `From`       | `From`       |
33//! | `SecurityAuthenticator` | `Authentication` | `From`       | `TryFrom`    |
34//!
35//! # Cryptography and RSN
36//!
37//! This module does **not** implement any security protocols nor cryptography and only describes
38//! them with limited detail as needed at API boundaries and in code that merely interacts with
39//! these protocols. See the `rsn` crate for implementations of the IEEE Std 802.11-2016 Robust
40//! Security Network (RSN).
41
42// NOTE: At the time of this writing, the WLAN stack does not support WPA Enterprise. One
43//       consequence of this is that conversions between Rust and FIDL types may return errors or
44//       panic when they involve WPA Enterprise representations. See https://fxbug.dev/42174395 and the TODOs
45//       in this module.
46
47pub mod wep;
48pub mod wpa;
49
50use fidl_fuchsia_wlan_common_security as fidl_security;
51use thiserror::Error;
52
53use crate::security::wep::WepKey;
54use crate::security::wpa::credential::{Passphrase, PassphraseError, Psk, PskError};
55
56/// Extension methods for the `Credentials` FIDL datagram.
57pub trait CredentialsExt {
58    fn into_wep(self) -> Option<fidl_security::WepCredentials>;
59    fn into_wpa(self) -> Option<fidl_security::WpaCredentials>;
60}
61
62impl CredentialsExt for fidl_security::Credentials {
63    fn into_wep(self) -> Option<fidl_security::WepCredentials> {
64        if let fidl_security::Credentials::Wep(credentials) = self {
65            Some(credentials)
66        } else {
67            None
68        }
69    }
70
71    fn into_wpa(self) -> Option<fidl_security::WpaCredentials> {
72        if let fidl_security::Credentials::Wpa(credentials) = self {
73            Some(credentials)
74        } else {
75            None
76        }
77    }
78}
79
80#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
81#[non_exhaustive]
82pub enum SecurityError {
83    #[error(transparent)]
84    Wep(#[from] wep::WepError),
85    #[error(transparent)]
86    Wpa(#[from] wpa::WpaError),
87    /// This error occurs when there is an incompatibility between security protocols, features,
88    /// and/or credentials.
89    ///
90    /// Note that this is distinct from `SecurityError::Unsupported`.
91    #[error("incompatible protocol or features")]
92    Incompatible,
93    /// This error occurs when a specified security protocol, features, and/or credentials are
94    /// **not** supported.
95    ///
96    /// Note that this is distinct from `SecurityError::Incompatible`. Unsupported features may be
97    /// compatible and specified in IEEE Std. 802.11-2016.
98    #[error("unsupported protocol or features")]
99    Unsupported,
100}
101
102impl From<PassphraseError> for SecurityError {
103    fn from(error: PassphraseError) -> Self {
104        SecurityError::Wpa(error.into())
105    }
106}
107
108impl From<PskError> for SecurityError {
109    fn from(error: PskError) -> Self {
110        SecurityError::Wpa(error.into())
111    }
112}
113
114/// General credential data that is not explicitly coupled to a particular security protocol.
115///
116/// The variants of this enumeration are particular to general protocols (i.e., WEP and WPA), but
117/// don't provide any more details or validation. For WPA credential data, this means that the
118/// version of the WPA security protocol is entirely unknown.
119///
120/// This type is meant for code and APIs that accept such bare credentials and must incorporate
121/// additional information or apply heuristics to negotiate a specific protocol. For example, this
122/// occurs in code that communicates directly with SME without support from the Policy layer to
123/// derive this information.
124///
125/// The FIDL analogue of this type is `fuchsia.wlan.common.security.Credentials`, into and from
126/// which this type can be infallibly converted.
127#[derive(Clone, Debug, Eq, PartialEq)]
128pub enum BareCredentials {
129    /// WEP key.
130    WepKey(WepKey),
131    /// WPA passphrase.
132    ///
133    /// Passphrases can be used to authenticate with WPA1, WPA2, and WPA3.
134    WpaPassphrase(Passphrase),
135    /// WPA PSK.
136    ///
137    /// PSKs are distinct from passphrases and can be used to authenticate with WPA1 and WPA2. A
138    /// PSK cannot be used to authenticate with WPA3.
139    WpaPsk(Psk),
140}
141
142impl From<BareCredentials> for fidl_security::Credentials {
143    fn from(credentials: BareCredentials) -> Self {
144        match credentials {
145            BareCredentials::WepKey(key) => {
146                fidl_security::Credentials::Wep(fidl_security::WepCredentials { key: key.into() })
147            }
148            BareCredentials::WpaPassphrase(passphrase) => fidl_security::Credentials::Wpa(
149                fidl_security::WpaCredentials::Passphrase(passphrase.into()),
150            ),
151            BareCredentials::WpaPsk(psk) => {
152                fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Psk(psk.into()))
153            }
154        }
155    }
156}
157
158impl TryFrom<fidl_security::Credentials> for BareCredentials {
159    type Error = SecurityError;
160
161    fn try_from(credentials: fidl_security::Credentials) -> Result<Self, Self::Error> {
162        match credentials {
163            fidl_security::Credentials::Wep(fidl_security::WepCredentials { key }) => {
164                WepKey::try_from_literal_bytes(key.as_slice())
165                    .map(|key| BareCredentials::WepKey(key))
166                    .map_err(From::from)
167            }
168            fidl_security::Credentials::Wpa(credentials) => match credentials {
169                fidl_security::WpaCredentials::Passphrase(passphrase) => {
170                    Passphrase::try_from(passphrase)
171                        .map(|passphrase| BareCredentials::WpaPassphrase(passphrase))
172                        .map_err(From::from)
173                }
174                fidl_security::WpaCredentials::Psk(psk) => Psk::try_from(psk.as_slice())
175                    .map(|psk| BareCredentials::WpaPsk(psk))
176                    .map_err(From::from),
177                // Unknown variant.
178                _ => Err(SecurityError::Incompatible),
179            },
180            // Unknown variant.
181            _ => Err(SecurityError::Incompatible),
182        }
183    }
184}
185
186/// Conversion from a WPA passphrase into bare credentials.
187impl From<Passphrase> for BareCredentials {
188    fn from(passphrase: Passphrase) -> Self {
189        BareCredentials::WpaPassphrase(passphrase)
190    }
191}
192
193/// Conversion from a WPA PSK into bare credentials.
194impl From<Psk> for BareCredentials {
195    fn from(psk: Psk) -> Self {
196        BareCredentials::WpaPsk(psk)
197    }
198}
199
200/// Conversion from a WEP key into bare credentials.
201impl From<WepKey> for BareCredentials {
202    fn from(key: WepKey) -> Self {
203        BareCredentials::WepKey(key)
204    }
205}
206
207/// Description of a wireless network security protocol.
208#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
209pub enum SecurityDescriptor {
210    Open,
211    Wep,
212    Wpa(wpa::WpaDescriptor),
213}
214
215impl SecurityDescriptor {
216    /// Open (no user authentication nor traffic encryption).
217    pub const OPEN: Self = SecurityDescriptor::Open;
218    /// WEP (trivially insecure; for legacy support only).
219    ///
220    /// This protocol is not configurable beyond the format of credentials used to authenticate.
221    /// WEP provides no protection and is provided for legacy support only.
222    pub const WEP: Self = SecurityDescriptor::Wep;
223    /// Legacy WPA (WPA1).
224    ///
225    /// This protocol is not configurable beyond the format of credentials used to authenticate.
226    pub const WPA1: Self = SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa1 { credentials: () });
227    /// WPA2 Personal.
228    ///
229    /// Describes the personal variant of the WPA2 protocol. This descriptor does not specify a
230    /// pairwise cipher.
231    pub const WPA2_PERSONAL: Self = SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
232        cipher: None,
233        authentication: wpa::Authentication::Personal(()),
234    });
235    /// WPA3 Personal.
236    ///
237    /// Describes the personal variant of the WPA3 protocol. This descriptor does not specify a
238    /// pairwise cipher.
239    pub const WPA3_PERSONAL: Self = SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
240        cipher: None,
241        authentication: wpa::Authentication::Personal(()),
242    });
243
244    /// Binds bare credentials to a descriptor to form an authenticator.
245    ///
246    /// A security descriptor only describes a protocol and bare credentials provide authentication
247    /// data without completely describing a protocol. When compatible, a descriptor and
248    /// credentials form the components of an authenticator, and this function attempts to form an
249    /// authenticator by binding these components together.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if the bare credentials are incompatible with the descriptor.
254    pub fn bind(
255        self,
256        credentials: Option<BareCredentials>,
257    ) -> Result<SecurityAuthenticator, SecurityError> {
258        match self {
259            SecurityDescriptor::Open if credentials.is_none() => Ok(SecurityAuthenticator::Open),
260            SecurityDescriptor::Wep => match credentials {
261                Some(BareCredentials::WepKey(key)) => {
262                    Ok(SecurityAuthenticator::Wep(wep::WepAuthenticator { key }))
263                }
264                _ => Err(SecurityError::Incompatible),
265            },
266            SecurityDescriptor::Wpa(wpa) => match credentials {
267                Some(credentials) => wpa.bind(credentials).map(SecurityAuthenticator::Wpa),
268                _ => Err(SecurityError::Incompatible),
269            },
270            _ => Err(SecurityError::Incompatible),
271        }
272    }
273
274    pub fn is_open(&self) -> bool {
275        matches!(self, SecurityDescriptor::Open)
276    }
277
278    pub fn is_wep(&self) -> bool {
279        matches!(self, SecurityDescriptor::Wep)
280    }
281
282    pub fn is_wpa(&self) -> bool {
283        matches!(self, SecurityDescriptor::Wpa(_))
284    }
285}
286
287impl From<fidl_security::Protocol> for SecurityDescriptor {
288    fn from(protocol: fidl_security::Protocol) -> Self {
289        match protocol {
290            fidl_security::Protocol::Open => SecurityDescriptor::Open,
291            fidl_security::Protocol::Wep => SecurityDescriptor::Wep,
292            fidl_security::Protocol::Wpa1 => {
293                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa1 { credentials: () })
294            }
295            fidl_security::Protocol::Wpa2Personal => {
296                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
297                    authentication: wpa::Authentication::Personal(()),
298                    cipher: None,
299                })
300            }
301            fidl_security::Protocol::Wpa2Enterprise => {
302                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
303                    authentication: wpa::Authentication::Enterprise(()),
304                    cipher: None,
305                })
306            }
307            fidl_security::Protocol::Wpa3Personal => {
308                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
309                    authentication: wpa::Authentication::Personal(()),
310                    cipher: None,
311                })
312            }
313            fidl_security::Protocol::Wpa3Enterprise => {
314                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
315                    authentication: wpa::Authentication::Enterprise(()),
316                    cipher: None,
317                })
318            }
319            _ => panic!("unknown FIDL security protocol variant"),
320        }
321    }
322}
323
324impl From<SecurityDescriptor> for fidl_security::Protocol {
325    fn from(descriptor: SecurityDescriptor) -> Self {
326        match descriptor {
327            SecurityDescriptor::Open => fidl_security::Protocol::Open,
328            SecurityDescriptor::Wep => fidl_security::Protocol::Wep,
329            SecurityDescriptor::Wpa(wpa) => match wpa {
330                wpa::WpaDescriptor::Wpa1 { .. } => fidl_security::Protocol::Wpa1,
331                wpa::WpaDescriptor::Wpa2 { authentication, .. } => match authentication {
332                    wpa::Authentication::Personal(_) => fidl_security::Protocol::Wpa2Personal,
333                    wpa::Authentication::Enterprise(_) => fidl_security::Protocol::Wpa2Enterprise,
334                },
335                wpa::WpaDescriptor::Wpa3 { authentication, .. } => match authentication {
336                    wpa::Authentication::Personal(_) => fidl_security::Protocol::Wpa3Personal,
337                    wpa::Authentication::Enterprise(_) => fidl_security::Protocol::Wpa3Enterprise,
338                },
339            },
340        }
341    }
342}
343
344impl From<wpa::WpaDescriptor> for SecurityDescriptor {
345    fn from(descriptor: wpa::WpaDescriptor) -> Self {
346        SecurityDescriptor::Wpa(descriptor)
347    }
348}
349
350/// Credentials and configuration for authenticating using a particular wireless network security
351/// protocol.
352#[derive(Clone, Debug, Eq, PartialEq)]
353pub enum SecurityAuthenticator {
354    Open,
355    Wep(wep::WepAuthenticator),
356    Wpa(wpa::WpaAuthenticator),
357}
358
359impl SecurityAuthenticator {
360    /// Converts an authenticator into a descriptor with no payload (the unit type `()`). Any
361    /// payload (i.e., credentials) are dropped.
362    pub fn into_descriptor(self) -> SecurityDescriptor {
363        match self {
364            SecurityAuthenticator::Open => SecurityDescriptor::Open,
365            SecurityAuthenticator::Wep(_) => SecurityDescriptor::Wep,
366            SecurityAuthenticator::Wpa(authenticator) => {
367                SecurityDescriptor::Wpa(authenticator.into_descriptor())
368            }
369        }
370    }
371
372    pub fn into_wep(self) -> Option<wep::WepAuthenticator> {
373        match self {
374            SecurityAuthenticator::Wep(authenticator) => Some(authenticator),
375            _ => None,
376        }
377    }
378
379    pub fn into_wpa(self) -> Option<wpa::WpaAuthenticator> {
380        match self {
381            SecurityAuthenticator::Wpa(authenticator) => Some(authenticator),
382            _ => None,
383        }
384    }
385
386    /// Converts the authenticator to bare credentials, if any.
387    ///
388    /// Returns `None` if the authenticator is `SecurityAuthenticator::None`, as there are no
389    /// corresponding credentials in this case.
390    pub fn to_credentials(&self) -> Option<BareCredentials> {
391        match self {
392            SecurityAuthenticator::Open => None,
393            SecurityAuthenticator::Wep(wep::WepAuthenticator { ref key }) => {
394                Some(key.clone().into())
395            }
396            SecurityAuthenticator::Wpa(ref wpa) => Some(wpa.to_credentials().into()),
397        }
398    }
399
400    pub fn as_wep(&self) -> Option<&wep::WepAuthenticator> {
401        match self {
402            SecurityAuthenticator::Wep(ref authenticator) => Some(authenticator),
403            _ => None,
404        }
405    }
406
407    pub fn as_wpa(&self) -> Option<&wpa::WpaAuthenticator> {
408        match self {
409            SecurityAuthenticator::Wpa(ref authenticator) => Some(authenticator),
410            _ => None,
411        }
412    }
413
414    pub fn is_open(&self) -> bool {
415        matches!(self, SecurityAuthenticator::Open)
416    }
417
418    pub fn is_wep(&self) -> bool {
419        matches!(self, SecurityAuthenticator::Wep(_))
420    }
421
422    pub fn is_wpa(&self) -> bool {
423        matches!(self, SecurityAuthenticator::Wpa(_))
424    }
425}
426
427impl From<wep::WepAuthenticator> for SecurityAuthenticator {
428    fn from(authenticator: wep::WepAuthenticator) -> Self {
429        SecurityAuthenticator::Wep(authenticator)
430    }
431}
432
433impl From<wpa::WpaAuthenticator> for SecurityAuthenticator {
434    fn from(authenticator: wpa::WpaAuthenticator) -> Self {
435        SecurityAuthenticator::Wpa(authenticator)
436    }
437}
438
439impl From<SecurityAuthenticator> for fidl_security::Authentication {
440    fn from(authenticator: SecurityAuthenticator) -> Self {
441        match authenticator {
442            SecurityAuthenticator::Open => fidl_security::Authentication {
443                protocol: fidl_security::Protocol::Open,
444                credentials: None,
445            },
446            SecurityAuthenticator::Wep(wep) => fidl_security::Authentication {
447                protocol: fidl_security::Protocol::Wep,
448                credentials: Some(Box::new(fidl_security::Credentials::Wep(wep.into()))),
449            },
450            SecurityAuthenticator::Wpa(wpa) => {
451                use wpa::Authentication::{Enterprise, Personal};
452                use wpa::Wpa::{Wpa1, Wpa2, Wpa3};
453
454                let protocol = match (&wpa, wpa.to_credentials()) {
455                    (Wpa1 { .. }, _) => fidl_security::Protocol::Wpa1,
456                    (Wpa2 { .. }, Personal(_)) => fidl_security::Protocol::Wpa2Personal,
457                    (Wpa2 { .. }, Enterprise(_)) => fidl_security::Protocol::Wpa2Enterprise,
458                    (Wpa3 { .. }, Personal(_)) => fidl_security::Protocol::Wpa3Personal,
459                    (Wpa3 { .. }, Enterprise(_)) => fidl_security::Protocol::Wpa3Enterprise,
460                };
461                fidl_security::Authentication {
462                    protocol,
463                    // TODO(https://fxbug.dev/42174395): This panics when encountering WPA Enterprise.
464                    credentials: Some(Box::new(fidl_security::Credentials::Wpa(
465                        wpa.into_credentials().into(),
466                    ))),
467                }
468            }
469        }
470    }
471}
472
473/// Converts an `Authentication` FIDL datagram into a `SecurityAuthenticator`.
474///
475/// This conversion should be preferred where possible.
476///
477/// # Errors
478///
479/// Returns an error if the `Authentication` datagram is invalid, such as specifying contradictory
480/// protocols or encoding incompatible or invalid credentials.
481impl TryFrom<fidl_security::Authentication> for SecurityAuthenticator {
482    type Error = SecurityError;
483
484    fn try_from(authentication: fidl_security::Authentication) -> Result<Self, Self::Error> {
485        let fidl_security::Authentication { protocol, credentials } = authentication;
486        match protocol {
487            fidl_security::Protocol::Open => match credentials {
488                None => Ok(SecurityAuthenticator::Open),
489                _ => Err(SecurityError::Incompatible),
490            },
491            fidl_security::Protocol::Wep => credentials
492                .ok_or(SecurityError::Incompatible)? // No credentials.
493                .into_wep()
494                .map(wep::WepAuthenticator::try_from)
495                .transpose()? // Conversion failure.
496                .map(From::from)
497                .ok_or(SecurityError::Incompatible), // Non-WEP credentials.
498            fidl_security::Protocol::Wpa1 => credentials
499                .ok_or(SecurityError::Incompatible)? // No credentials.
500                .into_wpa()
501                .map(wpa::Wpa1Credentials::try_from)
502                .transpose()? // Conversion failure.
503                .map(|credentials| wpa::WpaAuthenticator::Wpa1 { credentials })
504                .map(From::from)
505                .ok_or(SecurityError::Incompatible), // Non-WPA credentials.
506            fidl_security::Protocol::Wpa2Personal => credentials
507                .ok_or(SecurityError::Incompatible)? // No credentials.
508                .into_wpa()
509                .map(wpa::Wpa2PersonalCredentials::try_from)
510                .transpose()? // Conversion failure.
511                .map(From::from)
512                .map(|authentication| wpa::WpaAuthenticator::Wpa2 { cipher: None, authentication })
513                .map(From::from)
514                .ok_or(SecurityError::Incompatible), // Non-WPA credentials.
515            fidl_security::Protocol::Wpa3Personal => credentials
516                .ok_or(SecurityError::Incompatible)? // No credentials.
517                .into_wpa()
518                .map(wpa::Wpa3PersonalCredentials::try_from)
519                .transpose()? // Conversion failure.
520                .map(From::from)
521                .map(|authentication| wpa::WpaAuthenticator::Wpa3 { cipher: None, authentication })
522                .map(From::from)
523                .ok_or(SecurityError::Incompatible), // Non-WPA credentials.
524            // TODO(https://fxbug.dev/42174395): This returns an error when encountering WPA Enterprise
525            //                        protocols. Some conversions of composing types panic, but
526            //                        this top-level conversion insulates client code from this and
527            //                        instead yields an error.
528            _ => Err(SecurityError::Incompatible),
529        }
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use fidl_fuchsia_wlan_common_security as fidl_security;
536
537    use test_case::test_case;
538
539    use crate::security::wpa::{self, Authentication, Wpa2PersonalCredentials};
540    use crate::security::{SecurityAuthenticator, SecurityError};
541
542    pub trait AuthenticationTestCase: Sized {
543        fn wpa2_personal_psk() -> Self;
544        fn wpa3_personal_psk() -> Self;
545        fn wpa3_personal_wep_key() -> Self;
546        fn wpa3_personal_no_credentials() -> Self;
547    }
548
549    impl AuthenticationTestCase for fidl_security::Authentication {
550        fn wpa2_personal_psk() -> Self {
551            fidl_security::Authentication {
552                protocol: fidl_security::Protocol::Wpa2Personal,
553                credentials: Some(Box::new(fidl_security::Credentials::Wpa(
554                    fidl_security::WpaCredentials::Psk([0u8; 32]),
555                ))),
556            }
557        }
558
559        // Invalid: WPA3 with PSK.
560        fn wpa3_personal_psk() -> Self {
561            fidl_security::Authentication {
562                protocol: fidl_security::Protocol::Wpa3Personal,
563                credentials: Some(Box::new(fidl_security::Credentials::Wpa(
564                    fidl_security::WpaCredentials::Psk([0u8; 32]),
565                ))),
566            }
567        }
568
569        // Invalid: WPA3 with WEP key.
570        fn wpa3_personal_wep_key() -> Self {
571            fidl_security::Authentication {
572                protocol: fidl_security::Protocol::Wpa3Personal,
573                credentials: Some(Box::new(fidl_security::Credentials::Wep(
574                    fidl_security::WepCredentials { key: vec![0u8; 13] },
575                ))),
576            }
577        }
578
579        // Invalid: WPA3 with no credentials.
580        fn wpa3_personal_no_credentials() -> Self {
581            fidl_security::Authentication {
582                protocol: fidl_security::Protocol::Wpa3Personal,
583                credentials: None,
584            }
585        }
586    }
587
588    // TODO(seanolson): Move this assertion into a `SecurityAuthenticatorAssertion` trait (a la
589    //                  `AuthenticationTestCase`) and test via the `using` pattern in the
590    //                  `test-case` 2.0.0 series.
591    #[test_case(AuthenticationTestCase::wpa2_personal_psk() => matches
592        Ok(SecurityAuthenticator::Wpa(wpa::Wpa::Wpa2 {
593            authentication: Authentication::Personal(Wpa2PersonalCredentials::Psk(_)),
594            ..
595        }))
596    )]
597    #[test_case(AuthenticationTestCase::wpa3_personal_psk() => Err(SecurityError::Incompatible))]
598    #[test_case(AuthenticationTestCase::wpa3_personal_wep_key() => Err(SecurityError::Incompatible))]
599    #[test_case(AuthenticationTestCase::wpa3_personal_no_credentials() => Err(SecurityError::Incompatible))]
600    fn security_authenticator_from_authentication_fidl(
601        authentication: fidl_security::Authentication,
602    ) -> Result<SecurityAuthenticator, SecurityError> {
603        SecurityAuthenticator::try_from(authentication)
604    }
605
606    #[test]
607    fn authentication_fidl_from_security_authenticator() {
608        let authenticator = SecurityAuthenticator::Wpa(wpa::WpaAuthenticator::Wpa3 {
609            authentication: wpa::Authentication::Personal(
610                wpa::Wpa3PersonalCredentials::Passphrase("roflcopter".try_into().unwrap()),
611            ),
612            cipher: None,
613        });
614        let authentication = fidl_security::Authentication::from(authenticator);
615        assert_eq!(
616            authentication,
617            fidl_security::Authentication {
618                protocol: fidl_security::Protocol::Wpa3Personal,
619                credentials: Some(Box::new(fidl_security::Credentials::Wpa(
620                    fidl_security::WpaCredentials::Passphrase(b"roflcopter".as_slice().into()),
621                ))),
622            }
623        );
624    }
625}