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    Owe,
212    Wep,
213    Wpa(wpa::WpaDescriptor),
214}
215
216impl SecurityDescriptor {
217    /// Open (no user authentication nor traffic encryption).
218    pub const OPEN: Self = SecurityDescriptor::Open;
219    /// Open **and encrypted** (no user authentication).
220    pub const OWE: Self = SecurityDescriptor::Owe;
221    /// WEP (trivially insecure; for legacy support only).
222    ///
223    /// This protocol is not configurable beyond the format of credentials used to authenticate.
224    /// WEP provides no protection and is provided for legacy support only.
225    pub const WEP: Self = SecurityDescriptor::Wep;
226    /// Legacy WPA (WPA1).
227    ///
228    /// This protocol is not configurable beyond the format of credentials used to authenticate.
229    pub const WPA1: Self = SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa1 { credentials: () });
230    /// WPA2 Personal.
231    ///
232    /// Describes the personal variant of the WPA2 protocol. This descriptor does not specify a
233    /// pairwise cipher.
234    pub const WPA2_PERSONAL: Self = SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
235        cipher: None,
236        authentication: wpa::Authentication::Personal(()),
237    });
238    /// WPA3 Personal.
239    ///
240    /// Describes the personal variant of the WPA3 protocol. This descriptor does not specify a
241    /// pairwise cipher.
242    pub const WPA3_PERSONAL: Self = SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
243        cipher: None,
244        authentication: wpa::Authentication::Personal(()),
245    });
246
247    /// Binds bare credentials to a descriptor to form an authenticator.
248    ///
249    /// A security descriptor only describes a protocol and bare credentials provide authentication
250    /// data without completely describing a protocol. When compatible, a descriptor and
251    /// credentials form the components of an authenticator, and this function attempts to form an
252    /// authenticator by binding these components together.
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if the bare credentials are incompatible with the descriptor.
257    pub fn bind(
258        self,
259        credentials: Option<BareCredentials>,
260    ) -> Result<SecurityAuthenticator, SecurityError> {
261        match self {
262            SecurityDescriptor::Open if credentials.is_none() => Ok(SecurityAuthenticator::Open),
263            SecurityDescriptor::Owe if credentials.is_none() => Ok(SecurityAuthenticator::Owe),
264            SecurityDescriptor::Wep => match credentials {
265                Some(BareCredentials::WepKey(key)) => {
266                    Ok(SecurityAuthenticator::Wep(wep::WepAuthenticator { key }))
267                }
268                _ => Err(SecurityError::Incompatible),
269            },
270            SecurityDescriptor::Wpa(wpa) => match credentials {
271                Some(credentials) => wpa.bind(credentials).map(SecurityAuthenticator::Wpa),
272                _ => Err(SecurityError::Incompatible),
273            },
274            _ => Err(SecurityError::Incompatible),
275        }
276    }
277
278    pub fn is_open(&self) -> bool {
279        matches!(self, SecurityDescriptor::Open)
280    }
281
282    pub fn is_owe(&self) -> bool {
283        matches!(self, SecurityDescriptor::Owe)
284    }
285
286    pub fn is_wep(&self) -> bool {
287        matches!(self, SecurityDescriptor::Wep)
288    }
289
290    pub fn is_wpa(&self) -> bool {
291        matches!(self, SecurityDescriptor::Wpa(_))
292    }
293}
294
295impl From<fidl_security::Protocol> for SecurityDescriptor {
296    fn from(protocol: fidl_security::Protocol) -> Self {
297        match protocol {
298            fidl_security::Protocol::Open => SecurityDescriptor::Open,
299            fidl_security::Protocol::Owe => SecurityDescriptor::Owe,
300            fidl_security::Protocol::Wep => SecurityDescriptor::Wep,
301            fidl_security::Protocol::Wpa1 => {
302                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa1 { credentials: () })
303            }
304            fidl_security::Protocol::Wpa2Personal => {
305                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
306                    authentication: wpa::Authentication::Personal(()),
307                    cipher: None,
308                })
309            }
310            fidl_security::Protocol::Wpa2Enterprise => {
311                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
312                    authentication: wpa::Authentication::Enterprise(()),
313                    cipher: None,
314                })
315            }
316            fidl_security::Protocol::Wpa3Personal => {
317                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
318                    authentication: wpa::Authentication::Personal(()),
319                    cipher: None,
320                })
321            }
322            fidl_security::Protocol::Wpa3Enterprise => {
323                SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
324                    authentication: wpa::Authentication::Enterprise(()),
325                    cipher: None,
326                })
327            }
328            _ => panic!("unknown FIDL security protocol variant"),
329        }
330    }
331}
332
333impl From<SecurityDescriptor> for fidl_security::Protocol {
334    fn from(descriptor: SecurityDescriptor) -> Self {
335        match descriptor {
336            SecurityDescriptor::Open => fidl_security::Protocol::Open,
337            SecurityDescriptor::Owe => fidl_security::Protocol::Owe,
338            SecurityDescriptor::Wep => fidl_security::Protocol::Wep,
339            SecurityDescriptor::Wpa(wpa) => match wpa {
340                wpa::WpaDescriptor::Wpa1 { .. } => fidl_security::Protocol::Wpa1,
341                wpa::WpaDescriptor::Wpa2 { authentication, .. } => match authentication {
342                    wpa::Authentication::Personal(_) => fidl_security::Protocol::Wpa2Personal,
343                    wpa::Authentication::Enterprise(_) => fidl_security::Protocol::Wpa2Enterprise,
344                },
345                wpa::WpaDescriptor::Wpa3 { authentication, .. } => match authentication {
346                    wpa::Authentication::Personal(_) => fidl_security::Protocol::Wpa3Personal,
347                    wpa::Authentication::Enterprise(_) => fidl_security::Protocol::Wpa3Enterprise,
348                },
349            },
350        }
351    }
352}
353
354impl From<wpa::WpaDescriptor> for SecurityDescriptor {
355    fn from(descriptor: wpa::WpaDescriptor) -> Self {
356        SecurityDescriptor::Wpa(descriptor)
357    }
358}
359
360/// Credentials and configuration for authenticating using a particular wireless network security
361/// protocol.
362#[derive(Clone, Debug, Eq, PartialEq)]
363pub enum SecurityAuthenticator {
364    Open,
365    Owe,
366    Wep(wep::WepAuthenticator),
367    Wpa(wpa::WpaAuthenticator),
368}
369
370impl SecurityAuthenticator {
371    /// Converts an authenticator into a descriptor with no payload (the unit type `()`). Any
372    /// payload (i.e., credentials) are dropped.
373    pub fn into_descriptor(self) -> SecurityDescriptor {
374        match self {
375            SecurityAuthenticator::Open => SecurityDescriptor::Open,
376            SecurityAuthenticator::Owe => SecurityDescriptor::Owe,
377            SecurityAuthenticator::Wep(_) => SecurityDescriptor::Wep,
378            SecurityAuthenticator::Wpa(authenticator) => {
379                SecurityDescriptor::Wpa(authenticator.into_descriptor())
380            }
381        }
382    }
383
384    pub fn into_wep(self) -> Option<wep::WepAuthenticator> {
385        match self {
386            SecurityAuthenticator::Wep(authenticator) => Some(authenticator),
387            _ => None,
388        }
389    }
390
391    pub fn into_wpa(self) -> Option<wpa::WpaAuthenticator> {
392        match self {
393            SecurityAuthenticator::Wpa(authenticator) => Some(authenticator),
394            _ => None,
395        }
396    }
397
398    /// Converts the authenticator to bare credentials, if any.
399    ///
400    /// Returns `None` if the authenticator is `SecurityAuthenticator::None`, as there are no
401    /// corresponding credentials in this case.
402    pub fn to_credentials(&self) -> Option<BareCredentials> {
403        match self {
404            SecurityAuthenticator::Open | SecurityAuthenticator::Owe => None,
405            SecurityAuthenticator::Wep(wep::WepAuthenticator { key }) => Some(key.clone().into()),
406            SecurityAuthenticator::Wpa(wpa) => Some(wpa.to_credentials().into()),
407        }
408    }
409
410    pub fn as_wep(&self) -> Option<&wep::WepAuthenticator> {
411        match self {
412            SecurityAuthenticator::Wep(authenticator) => Some(authenticator),
413            _ => None,
414        }
415    }
416
417    pub fn as_wpa(&self) -> Option<&wpa::WpaAuthenticator> {
418        match self {
419            SecurityAuthenticator::Wpa(authenticator) => Some(authenticator),
420            _ => None,
421        }
422    }
423
424    pub fn is_open(&self) -> bool {
425        matches!(self, SecurityAuthenticator::Open)
426    }
427
428    pub fn is_owe(&self) -> bool {
429        matches!(self, SecurityAuthenticator::Owe)
430    }
431
432    pub fn is_wep(&self) -> bool {
433        matches!(self, SecurityAuthenticator::Wep(_))
434    }
435
436    pub fn is_wpa(&self) -> bool {
437        matches!(self, SecurityAuthenticator::Wpa(_))
438    }
439}
440
441impl From<wep::WepAuthenticator> for SecurityAuthenticator {
442    fn from(authenticator: wep::WepAuthenticator) -> Self {
443        SecurityAuthenticator::Wep(authenticator)
444    }
445}
446
447impl From<wpa::WpaAuthenticator> for SecurityAuthenticator {
448    fn from(authenticator: wpa::WpaAuthenticator) -> Self {
449        SecurityAuthenticator::Wpa(authenticator)
450    }
451}
452
453impl From<SecurityAuthenticator> for fidl_security::Authentication {
454    fn from(authenticator: SecurityAuthenticator) -> Self {
455        match authenticator {
456            SecurityAuthenticator::Open => fidl_security::Authentication {
457                protocol: fidl_security::Protocol::Open,
458                credentials: None,
459            },
460            SecurityAuthenticator::Owe => fidl_security::Authentication {
461                protocol: fidl_security::Protocol::Owe,
462                credentials: None,
463            },
464            SecurityAuthenticator::Wep(wep) => fidl_security::Authentication {
465                protocol: fidl_security::Protocol::Wep,
466                credentials: Some(Box::new(fidl_security::Credentials::Wep(wep.into()))),
467            },
468            SecurityAuthenticator::Wpa(wpa) => {
469                use wpa::Authentication::{Enterprise, Personal};
470                use wpa::Wpa::{Wpa1, Wpa2, Wpa3};
471
472                let protocol = match (&wpa, wpa.to_credentials()) {
473                    (Wpa1 { .. }, _) => fidl_security::Protocol::Wpa1,
474                    (Wpa2 { .. }, Personal(_)) => fidl_security::Protocol::Wpa2Personal,
475                    (Wpa2 { .. }, Enterprise(_)) => fidl_security::Protocol::Wpa2Enterprise,
476                    (Wpa3 { .. }, Personal(_)) => fidl_security::Protocol::Wpa3Personal,
477                    (Wpa3 { .. }, Enterprise(_)) => fidl_security::Protocol::Wpa3Enterprise,
478                };
479                fidl_security::Authentication {
480                    protocol,
481                    // TODO(https://fxbug.dev/42174395): This panics when encountering WPA Enterprise.
482                    credentials: Some(Box::new(fidl_security::Credentials::Wpa(
483                        wpa.into_credentials().into(),
484                    ))),
485                }
486            }
487        }
488    }
489}
490
491/// Converts an `Authentication` FIDL datagram into a `SecurityAuthenticator`.
492///
493/// This conversion should be preferred where possible.
494///
495/// # Errors
496///
497/// Returns an error if the `Authentication` datagram is invalid, such as specifying contradictory
498/// protocols or encoding incompatible or invalid credentials.
499impl TryFrom<fidl_security::Authentication> for SecurityAuthenticator {
500    type Error = SecurityError;
501
502    fn try_from(authentication: fidl_security::Authentication) -> Result<Self, Self::Error> {
503        let fidl_security::Authentication { protocol, credentials } = authentication;
504        match protocol {
505            fidl_security::Protocol::Open => match credentials {
506                None => Ok(SecurityAuthenticator::Open),
507                _ => Err(SecurityError::Incompatible),
508            },
509            fidl_security::Protocol::Owe => match credentials {
510                None => Ok(SecurityAuthenticator::Owe),
511                _ => Err(SecurityError::Incompatible),
512            },
513            fidl_security::Protocol::Wep => credentials
514                .ok_or(SecurityError::Incompatible)? // No credentials.
515                .into_wep()
516                .map(wep::WepAuthenticator::try_from)
517                .transpose()? // Conversion failure.
518                .map(From::from)
519                .ok_or(SecurityError::Incompatible), // Non-WEP credentials.
520            fidl_security::Protocol::Wpa1 => credentials
521                .ok_or(SecurityError::Incompatible)? // No credentials.
522                .into_wpa()
523                .map(wpa::Wpa1Credentials::try_from)
524                .transpose()? // Conversion failure.
525                .map(|credentials| wpa::WpaAuthenticator::Wpa1 { credentials })
526                .map(From::from)
527                .ok_or(SecurityError::Incompatible), // Non-WPA credentials.
528            fidl_security::Protocol::Wpa2Personal => credentials
529                .ok_or(SecurityError::Incompatible)? // No credentials.
530                .into_wpa()
531                .map(wpa::Wpa2PersonalCredentials::try_from)
532                .transpose()? // Conversion failure.
533                .map(From::from)
534                .map(|authentication| wpa::WpaAuthenticator::Wpa2 { cipher: None, authentication })
535                .map(From::from)
536                .ok_or(SecurityError::Incompatible), // Non-WPA credentials.
537            fidl_security::Protocol::Wpa3Personal => credentials
538                .ok_or(SecurityError::Incompatible)? // No credentials.
539                .into_wpa()
540                .map(wpa::Wpa3PersonalCredentials::try_from)
541                .transpose()? // Conversion failure.
542                .map(From::from)
543                .map(|authentication| wpa::WpaAuthenticator::Wpa3 { cipher: None, authentication })
544                .map(From::from)
545                .ok_or(SecurityError::Incompatible), // Non-WPA credentials.
546            // TODO(https://fxbug.dev/42174395): This returns an error when encountering WPA Enterprise
547            //                        protocols. Some conversions of composing types panic, but
548            //                        this top-level conversion insulates client code from this and
549            //                        instead yields an error.
550            _ => Err(SecurityError::Incompatible),
551        }
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use fidl_fuchsia_wlan_common_security as fidl_security;
558
559    use test_case::test_case;
560
561    use crate::security::wpa::{self, Authentication, Wpa2PersonalCredentials};
562    use crate::security::{SecurityAuthenticator, SecurityError};
563
564    pub trait AuthenticationTestCase: Sized {
565        fn wpa2_personal_psk() -> Self;
566        fn wpa3_personal_psk() -> Self;
567        fn wpa3_personal_wep_key() -> Self;
568        fn wpa3_personal_no_credentials() -> Self;
569    }
570
571    impl AuthenticationTestCase for fidl_security::Authentication {
572        fn wpa2_personal_psk() -> Self {
573            fidl_security::Authentication {
574                protocol: fidl_security::Protocol::Wpa2Personal,
575                credentials: Some(Box::new(fidl_security::Credentials::Wpa(
576                    fidl_security::WpaCredentials::Psk([0u8; 32]),
577                ))),
578            }
579        }
580
581        // Invalid: WPA3 with PSK.
582        fn wpa3_personal_psk() -> Self {
583            fidl_security::Authentication {
584                protocol: fidl_security::Protocol::Wpa3Personal,
585                credentials: Some(Box::new(fidl_security::Credentials::Wpa(
586                    fidl_security::WpaCredentials::Psk([0u8; 32]),
587                ))),
588            }
589        }
590
591        // Invalid: WPA3 with WEP key.
592        fn wpa3_personal_wep_key() -> Self {
593            fidl_security::Authentication {
594                protocol: fidl_security::Protocol::Wpa3Personal,
595                credentials: Some(Box::new(fidl_security::Credentials::Wep(
596                    fidl_security::WepCredentials { key: vec![0u8; 13] },
597                ))),
598            }
599        }
600
601        // Invalid: WPA3 with no credentials.
602        fn wpa3_personal_no_credentials() -> Self {
603            fidl_security::Authentication {
604                protocol: fidl_security::Protocol::Wpa3Personal,
605                credentials: None,
606            }
607        }
608    }
609
610    // TODO(seanolson): Move this assertion into a `SecurityAuthenticatorAssertion` trait (a la
611    //                  `AuthenticationTestCase`) and test via the `using` pattern in the
612    //                  `test-case` 2.0.0 series.
613    #[test_case(AuthenticationTestCase::wpa2_personal_psk() => matches
614        Ok(SecurityAuthenticator::Wpa(wpa::Wpa::Wpa2 {
615            authentication: Authentication::Personal(Wpa2PersonalCredentials::Psk(_)),
616            ..
617        }))
618    )]
619    #[test_case(AuthenticationTestCase::wpa3_personal_psk() => Err(SecurityError::Incompatible))]
620    #[test_case(AuthenticationTestCase::wpa3_personal_wep_key() => Err(SecurityError::Incompatible))]
621    #[test_case(AuthenticationTestCase::wpa3_personal_no_credentials() => Err(SecurityError::Incompatible))]
622    fn security_authenticator_from_authentication_fidl(
623        authentication: fidl_security::Authentication,
624    ) -> Result<SecurityAuthenticator, SecurityError> {
625        SecurityAuthenticator::try_from(authentication)
626    }
627
628    #[test]
629    fn authentication_fidl_from_security_authenticator() {
630        let authenticator = SecurityAuthenticator::Wpa(wpa::WpaAuthenticator::Wpa3 {
631            authentication: wpa::Authentication::Personal(
632                wpa::Wpa3PersonalCredentials::Passphrase("roflcopter".try_into().unwrap()),
633            ),
634            cipher: None,
635        });
636        let authentication = fidl_security::Authentication::from(authenticator);
637        assert_eq!(
638            authentication,
639            fidl_security::Authentication {
640                protocol: fidl_security::Protocol::Wpa3Personal,
641                credentials: Some(Box::new(fidl_security::Credentials::Wpa(
642                    fidl_security::WpaCredentials::Passphrase(b"roflcopter".as_slice().into()),
643                ))),
644            }
645        );
646    }
647}