wlan_common/security/wpa/
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//! IEEE Std 802.11-2016 WPA descriptors and credentials.
6//!
7//! This module describes WPA security protocols. Its primary API is composed of the
8//! [`WpaDescriptor`] and [`WpaAuthenticator`] types.
9//!
10//! WPA is more complex than WEP and several versions and authentication suites are provided. Types
11//! in this module fall into two broad categories: _specific_ and _general_. The general types
12//! provide representations of WPA primitives regardless of version and suite, and provide more
13//! ergonomic APIs. An example of a general type is [`Cipher`]. General types are exposed by
14//! specific types, which diverge across versions and suites and have no invalid representations.
15//! An example of a specific type is [`Wpa3Cipher`].
16//!
17//! Note that [`SecurityDescriptor`] and [`SecurityAuthenticator`] are always valid specifications
18//! of security protocols and authentication. When interacting with WPA, it is typically enough to
19//! use functions like [`WpaAuthenticator::to_credentials`] and [`WpaDescriptor::cipher`]. While
20//! the types returned from these functions are general, they are derived from a valid security
21//! protocol specification.
22//!
23//! [`Cipher`]: crate::security::wpa::Cipher
24//! [`SecurityAuthenticator`]: crate::security::SecurityAuthenticator
25//! [`SecurityDescriptor`]: crate::security::SecurityDescriptor
26//! [`Wpa3Cipher`]: crate::security::wpa::Wpa3Cipher
27//! [`WpaAuthenticator`]: crate::security::wpa::WpaAuthenticator
28//! [`WpaAuthenticator::to_credentials`]: crate::security::wpa::Wpa::to_credentials
29//! [`WpaDescriptor`]: crate::security::wpa::WpaDescriptor
30//! [`WpaDescriptor::cipher`]: crate::security::wpa::Wpa::cipher
31
32pub mod credential;
33mod data;
34
35use derivative::Derivative;
36use fidl_fuchsia_wlan_common_security as fidl_security;
37use std::fmt::Debug;
38use std::hash::{Hash, Hasher};
39use thiserror::Error;
40
41use crate::security::wpa::credential::{Passphrase, PassphraseError, Psk, PskError};
42use crate::security::wpa::data::{CredentialData, EnterpriseData, PersonalData};
43use crate::security::{BareCredentials, SecurityError};
44
45pub use crate::security::wpa::data::AuthenticatorData;
46
47#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
48#[non_exhaustive]
49pub enum WpaError {
50    #[error(transparent)]
51    Psk(#[from] PskError),
52    #[error(transparent)]
53    Passphrase(#[from] PassphraseError),
54}
55
56/// WPA authentication suite.
57///
58/// WPA authentication is divided into two broad suites: WPA Personal and WPA Enterprise. The
59/// credentials and mechanisms for each suite differ and both WPA descriptors and authenticators
60/// discriminate on this basis.
61#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
62pub enum Authentication<P = (), E = ()> {
63    Personal(P),
64    Enterprise(E),
65}
66
67pub type AuthenticationDescriptor = Authentication<(), ()>;
68
69/// General WPA credentials.
70///
71/// Provides credentials keyed by WPA Personal and WPA Enterprise suite. This type is general and
72/// can represent credentials used across versions of WPA.
73pub type Credentials = Authentication<PersonalCredentials, EnterpriseCredentials>;
74
75impl<P, E> Authentication<P, E> {
76    /// Converts an `Authentication` into a descriptor with no payload (the unit type `()`). Any
77    /// payload (i.e., credentials) are dropped.
78    pub fn into_descriptor(self) -> Authentication<(), ()> {
79        match self {
80            Authentication::Personal(_) => Authentication::Personal(()),
81            Authentication::Enterprise(_) => Authentication::Enterprise(()),
82        }
83    }
84
85    /// Converts an `Authentication` describing a particular WPA version into a general type that
86    /// describes credentials used across versions of WPA.
87    pub fn into_credentials(self) -> Credentials
88    where
89        PersonalCredentials: From<P>,
90        EnterpriseCredentials: From<E>,
91    {
92        match self {
93            Authentication::Personal(personal) => Authentication::Personal(personal.into()),
94            #[allow(unreachable_code)]
95            Authentication::Enterprise(enterprise) => Authentication::Enterprise(enterprise.into()),
96        }
97    }
98
99    pub fn into_personal(self) -> Option<P> {
100        if let Authentication::Personal(personal) = self { Some(personal) } else { None }
101    }
102
103    pub fn into_enterprise(self) -> Option<E> {
104        if let Authentication::Enterprise(enterprise) = self { Some(enterprise) } else { None }
105    }
106
107    pub fn is_personal(&self) -> bool {
108        matches!(self, Authentication::Personal(_))
109    }
110
111    pub fn is_enterprise(&self) -> bool {
112        matches!(self, Authentication::Enterprise(_))
113    }
114
115    pub fn as_ref(&self) -> Authentication<&P, &E> {
116        match self {
117            Authentication::Personal(ref personal) => Authentication::Personal(personal),
118            Authentication::Enterprise(ref enterprise) => Authentication::Enterprise(enterprise),
119        }
120    }
121}
122
123impl Default for Authentication<(), ()> {
124    fn default() -> Self {
125        Authentication::Personal(())
126    }
127}
128
129impl From<Wpa1Credentials> for Authentication<Wpa1Credentials, ()> {
130    fn from(credentials: Wpa1Credentials) -> Self {
131        Authentication::Personal(credentials)
132    }
133}
134
135// TODO(https://fxbug.dev/42174395): Specify the WPA2 Enterprise type.
136impl From<Wpa2PersonalCredentials> for Authentication<Wpa2PersonalCredentials, ()> {
137    fn from(credentials: Wpa2PersonalCredentials) -> Self {
138        Authentication::Personal(credentials)
139    }
140}
141
142// TODO(https://fxbug.dev/42174395): Specify the WPA3 Enterprise type.
143impl From<Wpa3PersonalCredentials> for Authentication<Wpa3PersonalCredentials, ()> {
144    fn from(credentials: Wpa3PersonalCredentials) -> Self {
145        Authentication::Personal(credentials)
146    }
147}
148
149impl From<EnterpriseCredentials> for Credentials {
150    fn from(enterprise: EnterpriseCredentials) -> Self {
151        Credentials::Enterprise(enterprise)
152    }
153}
154
155impl From<PersonalCredentials> for Credentials {
156    fn from(personal: PersonalCredentials) -> Self {
157        Credentials::Personal(personal)
158    }
159}
160
161impl<P, E> From<Authentication<P, E>> for fidl_security::WpaCredentials
162where
163    P: Into<fidl_security::WpaCredentials>,
164    E: Into<fidl_security::WpaCredentials>,
165{
166    fn from(authentication: Authentication<P, E>) -> Self {
167        match authentication {
168            Authentication::Personal(personal) => personal.into(),
169            // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
170            Authentication::Enterprise(_) => panic!("WPA Enterprise is unsupported"),
171        }
172    }
173}
174
175/// Conversion of general WPA credentials into bare credentials.
176impl From<Credentials> for BareCredentials {
177    fn from(credentials: Credentials) -> Self {
178        match credentials {
179            Credentials::Personal(personal) => match personal {
180                PersonalCredentials::Passphrase(passphrase) => {
181                    BareCredentials::WpaPassphrase(passphrase)
182                }
183                PersonalCredentials::Psk(psk) => BareCredentials::WpaPsk(psk),
184            },
185            // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
186            Credentials::Enterprise(_) => panic!("WPA Enterprise is unsupported"),
187        }
188    }
189}
190
191/// General WPA Personal credentials.
192///
193/// Enumerates credential data used across the family of WPA versions to authenticate with the WPA
194/// Personal suite. This is a superset of WPA Personal credentials for each version of WPA.
195///
196/// See [`Authentication`] and the [`Credentials`] type definition.
197///
198/// [`Authentication`]: crate::security::wpa::Authentication
199/// [`Credentials`]: crate::security::wpa::Credentials
200#[derive(Clone, Debug, Eq, PartialEq)]
201pub enum PersonalCredentials {
202    /// Pre-shared key (PSK).
203    Psk(Psk),
204    /// Passphrase.
205    Passphrase(Passphrase),
206}
207
208impl AsRef<[u8]> for PersonalCredentials {
209    fn as_ref(&self) -> &[u8] {
210        match self {
211            PersonalCredentials::Psk(ref psk) => psk.as_ref(),
212            PersonalCredentials::Passphrase(ref passphrase) => passphrase.as_ref(),
213        }
214    }
215}
216
217impl From<Wpa1Credentials> for PersonalCredentials {
218    fn from(credentials: Wpa1Credentials) -> Self {
219        match credentials {
220            Wpa1Credentials::Psk(psk) => PersonalCredentials::Psk(psk),
221            Wpa1Credentials::Passphrase(passphrase) => PersonalCredentials::Passphrase(passphrase),
222        }
223    }
224}
225
226impl From<Wpa2PersonalCredentials> for PersonalCredentials {
227    fn from(credentials: Wpa2PersonalCredentials) -> Self {
228        match credentials {
229            Wpa2PersonalCredentials::Psk(psk) => PersonalCredentials::Psk(psk),
230            Wpa2PersonalCredentials::Passphrase(passphrase) => {
231                PersonalCredentials::Passphrase(passphrase)
232            }
233        }
234    }
235}
236
237impl From<Wpa3PersonalCredentials> for PersonalCredentials {
238    fn from(credentials: Wpa3PersonalCredentials) -> Self {
239        match credentials {
240            Wpa3PersonalCredentials::Passphrase(passphrase) => {
241                PersonalCredentials::Passphrase(passphrase)
242            }
243        }
244    }
245}
246
247impl From<PersonalCredentials> for fidl_security::WpaCredentials {
248    fn from(credentials: PersonalCredentials) -> Self {
249        match credentials {
250            PersonalCredentials::Psk(psk) => fidl_security::WpaCredentials::Psk(psk.into()),
251            PersonalCredentials::Passphrase(passphrase) => {
252                fidl_security::WpaCredentials::Passphrase(passphrase.into())
253            }
254        }
255    }
256}
257
258#[derive(Clone, Debug, Eq, PartialEq)]
259pub enum Wpa1Credentials {
260    Psk(Psk),
261    Passphrase(Passphrase),
262}
263
264impl AsRef<[u8]> for Wpa1Credentials {
265    fn as_ref(&self) -> &[u8] {
266        match self {
267            Wpa1Credentials::Psk(ref psk) => psk.as_ref(),
268            Wpa1Credentials::Passphrase(ref passphrase) => passphrase.as_ref(),
269        }
270    }
271}
272
273impl From<Passphrase> for Wpa1Credentials {
274    fn from(passphrase: Passphrase) -> Self {
275        Wpa1Credentials::Passphrase(passphrase)
276    }
277}
278
279impl From<Psk> for Wpa1Credentials {
280    fn from(psk: Psk) -> Self {
281        Wpa1Credentials::Psk(psk)
282    }
283}
284
285impl From<Wpa1Credentials> for fidl_security::WpaCredentials {
286    fn from(credentials: Wpa1Credentials) -> Self {
287        PersonalCredentials::from(credentials).into()
288    }
289}
290
291// This is implemented (infallibly) via `TryFrom`, because the set of accepted WPA credentials may
292// expand with revisions to IEEE Std 802.11 and conversions from the general enumeration to the
293// specific enumeration should be handled in a fallible and defensive way by client code.
294impl TryFrom<PersonalCredentials> for Wpa1Credentials {
295    type Error = SecurityError;
296
297    fn try_from(credentials: PersonalCredentials) -> Result<Self, Self::Error> {
298        match credentials {
299            PersonalCredentials::Psk(psk) => Ok(Wpa1Credentials::Psk(psk)),
300            PersonalCredentials::Passphrase(passphrase) => {
301                Ok(Wpa1Credentials::Passphrase(passphrase))
302            }
303        }
304    }
305}
306
307impl TryFrom<fidl_security::WpaCredentials> for Wpa1Credentials {
308    type Error = SecurityError;
309
310    fn try_from(credentials: fidl_security::WpaCredentials) -> Result<Self, Self::Error> {
311        match credentials {
312            fidl_security::WpaCredentials::Psk(psk) => Ok(Wpa1Credentials::Psk(Psk::from(psk))),
313            fidl_security::WpaCredentials::Passphrase(passphrase) => {
314                let passphrase = Passphrase::try_from(passphrase)?;
315                Ok(Wpa1Credentials::Passphrase(passphrase))
316            }
317            _ => panic!("unknown FIDL credentials variant"),
318        }
319    }
320}
321
322#[derive(Clone, Debug, Eq, PartialEq)]
323pub enum Wpa2PersonalCredentials {
324    Psk(Psk),
325    Passphrase(Passphrase),
326}
327
328impl AsRef<[u8]> for Wpa2PersonalCredentials {
329    fn as_ref(&self) -> &[u8] {
330        match self {
331            Wpa2PersonalCredentials::Psk(ref psk) => psk.as_ref(),
332            Wpa2PersonalCredentials::Passphrase(ref passphrase) => passphrase.as_ref(),
333        }
334    }
335}
336
337impl From<Passphrase> for Wpa2PersonalCredentials {
338    fn from(passphrase: Passphrase) -> Self {
339        Wpa2PersonalCredentials::Passphrase(passphrase)
340    }
341}
342
343impl From<Psk> for Wpa2PersonalCredentials {
344    fn from(psk: Psk) -> Self {
345        Wpa2PersonalCredentials::Psk(psk)
346    }
347}
348
349impl From<Wpa2PersonalCredentials> for fidl_security::WpaCredentials {
350    fn from(credentials: Wpa2PersonalCredentials) -> Self {
351        PersonalCredentials::from(credentials).into()
352    }
353}
354
355// This is implemented (infallibly) via `TryFrom`, because the set of accepted WPA credentials may
356// expand with revisions to IEEE Std 802.11 and conversions from the general enumeration to the
357// specific enumeration should be handled in a fallible and defensive way by client code.
358impl TryFrom<PersonalCredentials> for Wpa2PersonalCredentials {
359    type Error = SecurityError;
360
361    fn try_from(credentials: PersonalCredentials) -> Result<Self, Self::Error> {
362        match credentials {
363            PersonalCredentials::Psk(psk) => Ok(Wpa2PersonalCredentials::Psk(psk)),
364            PersonalCredentials::Passphrase(passphrase) => {
365                Ok(Wpa2PersonalCredentials::Passphrase(passphrase))
366            }
367        }
368    }
369}
370
371impl TryFrom<fidl_security::WpaCredentials> for Wpa2PersonalCredentials {
372    type Error = SecurityError;
373
374    fn try_from(credentials: fidl_security::WpaCredentials) -> Result<Self, Self::Error> {
375        match credentials {
376            fidl_security::WpaCredentials::Psk(psk) => {
377                Ok(Wpa2PersonalCredentials::Psk(Psk::from(psk)))
378            }
379            fidl_security::WpaCredentials::Passphrase(passphrase) => {
380                let passphrase = Passphrase::try_from(passphrase)?;
381                Ok(Wpa2PersonalCredentials::Passphrase(passphrase))
382            }
383            _ => panic!("unknown FIDL credentials variant"),
384        }
385    }
386}
387
388#[derive(Clone, Debug, Eq, PartialEq)]
389pub enum Wpa3PersonalCredentials {
390    Passphrase(Passphrase),
391}
392
393impl AsRef<[u8]> for Wpa3PersonalCredentials {
394    fn as_ref(&self) -> &[u8] {
395        match self {
396            Wpa3PersonalCredentials::Passphrase(ref passphrase) => passphrase.as_ref(),
397        }
398    }
399}
400
401impl From<Passphrase> for Wpa3PersonalCredentials {
402    fn from(passphrase: Passphrase) -> Self {
403        Wpa3PersonalCredentials::Passphrase(passphrase)
404    }
405}
406
407impl From<Wpa3PersonalCredentials> for fidl_security::WpaCredentials {
408    fn from(credentials: Wpa3PersonalCredentials) -> Self {
409        PersonalCredentials::from(credentials).into()
410    }
411}
412
413impl TryFrom<PersonalCredentials> for Wpa3PersonalCredentials {
414    type Error = SecurityError;
415
416    fn try_from(credentials: PersonalCredentials) -> Result<Self, Self::Error> {
417        match credentials {
418            PersonalCredentials::Passphrase(passphrase) => {
419                Ok(Wpa3PersonalCredentials::Passphrase(passphrase))
420            }
421            _ => Err(SecurityError::Incompatible),
422        }
423    }
424}
425
426impl TryFrom<fidl_security::WpaCredentials> for Wpa3PersonalCredentials {
427    type Error = SecurityError;
428
429    fn try_from(credentials: fidl_security::WpaCredentials) -> Result<Self, Self::Error> {
430        match credentials {
431            fidl_security::WpaCredentials::Psk(_) => Err(SecurityError::Incompatible),
432            fidl_security::WpaCredentials::Passphrase(passphrase) => {
433                let passphrase = Passphrase::try_from(passphrase)?;
434                Ok(Wpa3PersonalCredentials::Passphrase(passphrase))
435            }
436            _ => panic!("unknown FIDL credentials variant"),
437        }
438    }
439}
440
441// TODO(https://fxbug.dev/42174395): Add variants to `EnterpriseCredentials` as needed and implement
442//                        conversions.
443/// General WPA Enterprise credentials.
444///
445/// Enumerates credential data used across the family of WPA versions to authenticate with the WPA
446/// Enterprise suite. This is a superset of WPA Enterprise credentials for each version of WPA.
447///
448/// See [`Authentication`] and the [`Credentials`] type definition.
449///
450/// [`Authentication`]: crate::security::wpa::Authentication
451/// [`Credentials`]: crate::security::wpa::Credentials
452#[derive(Clone, Debug, Eq, PartialEq)]
453pub enum EnterpriseCredentials {}
454
455impl From<()> for EnterpriseCredentials {
456    fn from(_: ()) -> Self {
457        // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
458        panic!("WPA Enterprise is unsupported")
459    }
460}
461
462impl From<EnterpriseCredentials> for fidl_security::WpaCredentials {
463    fn from(_: EnterpriseCredentials) -> Self {
464        // TODO(https://fxbug.dev/42174395): Implement conversions for WPA Enterprise.
465        panic!("WPA Enterprise is unsupported")
466    }
467}
468
469/// General WPA cipher.
470///
471/// Names a cipher used across the family of WPA versions. Some versions of WPA may not support
472/// some of these algorithms.
473///
474/// Note that no types in this crate or module implement these ciphers nor encryption algorithms in
475/// any way; this type is strictly nominal.
476#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
477#[repr(u8)]
478pub enum Cipher {
479    TKIP = 0,
480    CCMP = 1,
481    GCMP = 2,
482}
483
484impl From<Wpa2Cipher> for Cipher {
485    fn from(cipher: Wpa2Cipher) -> Self {
486        match cipher {
487            Wpa2Cipher::TKIP => Cipher::TKIP,
488            Wpa2Cipher::CCMP => Cipher::CCMP,
489        }
490    }
491}
492
493impl From<Wpa3Cipher> for Cipher {
494    fn from(cipher: Wpa3Cipher) -> Self {
495        match cipher {
496            Wpa3Cipher::CCMP => Cipher::CCMP,
497            Wpa3Cipher::GCMP => Cipher::GCMP,
498        }
499    }
500}
501
502/// WPA2 cipher.
503#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
504#[repr(u8)]
505pub enum Wpa2Cipher {
506    TKIP = 0,
507    CCMP = 1,
508}
509
510impl TryFrom<Cipher> for Wpa2Cipher {
511    type Error = SecurityError;
512
513    fn try_from(cipher: Cipher) -> Result<Self, Self::Error> {
514        match cipher {
515            Cipher::TKIP => Ok(Wpa2Cipher::TKIP),
516            Cipher::CCMP => Ok(Wpa2Cipher::CCMP),
517            _ => Err(SecurityError::Incompatible),
518        }
519    }
520}
521
522/// WPA3 cipher.
523#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
524#[repr(u8)]
525pub enum Wpa3Cipher {
526    CCMP = 1,
527    GCMP = 2,
528}
529
530impl TryFrom<Cipher> for Wpa3Cipher {
531    type Error = SecurityError;
532
533    fn try_from(cipher: Cipher) -> Result<Self, Self::Error> {
534        match cipher {
535            Cipher::CCMP => Ok(Wpa3Cipher::CCMP),
536            Cipher::GCMP => Ok(Wpa3Cipher::GCMP),
537            _ => Err(SecurityError::Incompatible),
538        }
539    }
540}
541
542/// General description of WPA and its authentication.
543///
544/// This type is typically used via the [`WpaAuthenticator`] and [`WpaDescriptor`] type
545/// definitions, which represent WPA authenticators and descriptors, respectively. It is not
546/// generally necessary to interact with this type directly.
547///
548/// This type constructor describes the configuration of a WPA security protocol and additionally
549/// contains authentication (credential) data defined by its type parameter. For authenticators,
550/// this data represents credentials, such as a PSK or passphrase. For descriptors, this data is
551/// the unit type `()`, and so no authentication data is available. See the [`data`] module and
552/// [`CredentialData`] trait for more.
553///
554/// [`CredentialData`]: crate::security::wpa::data::CredentialData
555/// [`data`]: crate::security::wpa::data
556/// [`WpaAuthenticator`]: crate::security::wpa::WpaAuthenticator
557/// [`WpaDescriptor`]: crate::security::wpa::WpaDescriptor
558#[derive(Derivative)]
559#[derivative(
560    Clone(bound = ""),
561    Copy(bound = "
562        <C::Personal as PersonalData>::Wpa1: Copy,
563        <C::Personal as PersonalData>::Wpa2: Copy,
564        <C::Personal as PersonalData>::Wpa3: Copy,
565        <C::Enterprise as EnterpriseData>::Wpa2: Copy,
566        <C::Enterprise as EnterpriseData>::Wpa3: Copy,
567    "),
568    Debug(bound = ""),
569    Eq(bound = ""),
570    PartialEq(bound = "")
571)]
572pub enum Wpa<C = ()>
573where
574    C: CredentialData,
575{
576    Wpa1 {
577        credentials: <C::Personal as PersonalData>::Wpa1,
578    },
579    Wpa2 {
580        cipher: Option<Wpa2Cipher>,
581        authentication: Authentication<
582            <C::Personal as PersonalData>::Wpa2,
583            <C::Enterprise as EnterpriseData>::Wpa2,
584        >,
585    },
586    Wpa3 {
587        cipher: Option<Wpa3Cipher>,
588        authentication: Authentication<
589            <C::Personal as PersonalData>::Wpa3,
590            <C::Enterprise as EnterpriseData>::Wpa3,
591        >,
592    },
593}
594
595impl<C> Wpa<C>
596where
597    C: CredentialData,
598{
599    /// Converts a `Wpa` into a descriptor with no payload (the unit type `()`). Any payload (i.e.,
600    /// credentials) are dropped.
601    pub fn into_descriptor(self) -> Wpa<()> {
602        match self {
603            Wpa::Wpa1 { .. } => Wpa::Wpa1 { credentials: () },
604            Wpa::Wpa2 { cipher, authentication } => {
605                Wpa::Wpa2 { cipher, authentication: authentication.into_descriptor() }
606            }
607            Wpa::Wpa3 { cipher, authentication } => {
608                Wpa::Wpa3 { cipher, authentication: authentication.into_descriptor() }
609            }
610        }
611    }
612
613    /// Gets the configured cipher.
614    ///
615    /// This function coalesces the ciphers supported by various versions of WPA and returns the
616    /// generalized `Cipher` type.
617    ///
618    /// Note that WPA1 is not configurable in this way and always uses TKIP (and so this function
619    /// will always return `Some(Cipher::TKIP)` for WPA1). For other versions of WPA, the
620    /// configured cipher may not be known and this function returns `None` in that case.
621    /// Importantly, this does **not** mean that no cipher is used, but rather that the specific
622    /// cipher is unspecified or unknown.
623    pub fn cipher(&self) -> Option<Cipher> {
624        match self {
625            Wpa::Wpa1 { .. } => Some(Cipher::TKIP),
626            Wpa::Wpa2 { cipher, .. } => cipher.map(Into::into),
627            Wpa::Wpa3 { cipher, .. } => cipher.map(Into::into),
628        }
629    }
630}
631
632impl<C> From<Wpa<C>> for fidl_security::Protocol
633where
634    C: CredentialData,
635{
636    fn from(wpa: Wpa<C>) -> Self {
637        match wpa {
638            Wpa::Wpa1 { .. } => fidl_security::Protocol::Wpa1,
639            Wpa::Wpa2 { authentication, .. } => match authentication {
640                Authentication::Personal(_) => fidl_security::Protocol::Wpa2Personal,
641                Authentication::Enterprise(_) => fidl_security::Protocol::Wpa2Enterprise,
642            },
643            Wpa::Wpa3 { authentication, .. } => match authentication {
644                Authentication::Personal(_) => fidl_security::Protocol::Wpa3Personal,
645                Authentication::Enterprise(_) => fidl_security::Protocol::Wpa3Enterprise,
646            },
647        }
648    }
649}
650
651/// WPA descriptor.
652///
653/// Describes the configuration of the WPA security protocol. WPA descriptors optionally specify a
654/// pairwise cipher. Descriptors that lack this information are simply less specific than
655/// descriptors that include it.
656pub type WpaDescriptor = Wpa<()>;
657
658impl WpaDescriptor {
659    pub fn bind(self, credentials: BareCredentials) -> Result<WpaAuthenticator, SecurityError> {
660        match credentials {
661            BareCredentials::WpaPassphrase(passphrase) => match self {
662                WpaDescriptor::Wpa1 { .. } => {
663                    Ok(WpaAuthenticator::Wpa1 { credentials: passphrase.into() })
664                }
665                WpaDescriptor::Wpa2 { cipher, authentication } => Ok(WpaAuthenticator::Wpa2 {
666                    cipher,
667                    authentication: match authentication {
668                        Authentication::Personal(_) => {
669                            Ok(Authentication::Personal(passphrase.into()))
670                        }
671                        Authentication::Enterprise(_) => Err(SecurityError::Unsupported),
672                    }?,
673                }),
674                WpaDescriptor::Wpa3 { cipher, authentication } => Ok(WpaAuthenticator::Wpa3 {
675                    cipher,
676                    authentication: match authentication {
677                        Authentication::Personal(_) => {
678                            Ok(Authentication::Personal(passphrase.into()))
679                        }
680                        Authentication::Enterprise(_) => Err(SecurityError::Unsupported),
681                    }?,
682                }),
683            },
684            BareCredentials::WpaPsk(psk) => match self {
685                WpaDescriptor::Wpa1 { .. } => {
686                    Ok(WpaAuthenticator::Wpa1 { credentials: psk.into() })
687                }
688                WpaDescriptor::Wpa2 { cipher, authentication } => Ok(WpaAuthenticator::Wpa2 {
689                    cipher,
690                    authentication: match authentication {
691                        Authentication::Personal(_) => Ok(Authentication::Personal(psk.into())),
692                        Authentication::Enterprise(_) => Err(SecurityError::Unsupported),
693                    }?,
694                }),
695                WpaDescriptor::Wpa3 { .. } => Err(SecurityError::Incompatible),
696            },
697            _ => Err(SecurityError::Incompatible),
698        }
699    }
700}
701
702impl Hash for WpaDescriptor {
703    fn hash<H>(&self, state: &mut H)
704    where
705        H: Hasher,
706    {
707        match self {
708            WpaDescriptor::Wpa1 { ref credentials } => {
709                credentials.hash(state);
710            }
711            WpaDescriptor::Wpa2 { ref cipher, ref authentication } => {
712                cipher.hash(state);
713                authentication.hash(state);
714            }
715            WpaDescriptor::Wpa3 { ref cipher, ref authentication } => {
716                cipher.hash(state);
717                authentication.hash(state);
718            }
719        }
720    }
721}
722
723/// WPA authenticator.
724///
725/// Provides credentials for authenticating against the described configuration of the WPA security
726/// protocol.
727pub type WpaAuthenticator = Wpa<AuthenticatorData>;
728
729impl WpaAuthenticator {
730    /// Converts a WPA authenticator into its credentials.
731    ///
732    /// The output of this function is general and describes all versions of WPA. This means it
733    /// cannot be used to determine which version of WPA has been specified. Note that WPA1
734    /// specifies its credentials as WPA1 Personal via [`Authentication::Personal`] even though
735    /// WPA1 has no Enterprise suite.
736    ///
737    /// [`Authentication::Personal`]: crate::security::wpa::Authentication::Personal
738    pub fn into_credentials(self) -> Credentials {
739        match self {
740            Wpa::Wpa1 { credentials } => Authentication::Personal(credentials.into()),
741            Wpa::Wpa2 { authentication, .. } => authentication.into_credentials(),
742            Wpa::Wpa3 { authentication, .. } => authentication.into_credentials(),
743        }
744    }
745
746    pub fn to_credentials(&self) -> Credentials {
747        match self {
748            Wpa::Wpa1 { ref credentials } => Authentication::Personal(credentials.clone().into()),
749            Wpa::Wpa2 { ref authentication, .. } => authentication.clone().into_credentials(),
750            Wpa::Wpa3 { ref authentication, .. } => authentication.clone().into_credentials(),
751        }
752    }
753}
754
755#[cfg(test)]
756mod tests {
757    use fidl_fuchsia_wlan_common_security as fidl_security;
758
759    use test_case::test_case;
760
761    use crate::security::wep::{WEP40_KEY_BYTES, WepKey};
762    use crate::security::wpa::credential::{PSK_SIZE_BYTES, Passphrase, Psk};
763    use crate::security::wpa::{self};
764    use crate::security::{BareCredentials, SecurityError};
765
766    fn wep_key() -> WepKey {
767        [170u8; WEP40_KEY_BYTES].into()
768    }
769
770    fn wpa_psk() -> Psk {
771        [170u8; PSK_SIZE_BYTES].into()
772    }
773
774    fn wpa_passphrase() -> Passphrase {
775        Passphrase::try_from("password").unwrap()
776    }
777
778    trait PersonalCredentialsTestCase: Sized {
779        fn psk() -> Self;
780        fn passphrase() -> Self;
781    }
782
783    impl PersonalCredentialsTestCase for wpa::PersonalCredentials {
784        fn psk() -> Self {
785            wpa::PersonalCredentials::Psk(wpa_psk())
786        }
787
788        fn passphrase() -> Self {
789            wpa::PersonalCredentials::Passphrase(wpa_passphrase())
790        }
791    }
792
793    trait WpaCredentialsTestCase: Sized {
794        fn psk() -> Self;
795        fn passphrase() -> Self;
796    }
797
798    impl WpaCredentialsTestCase for fidl_security::WpaCredentials {
799        fn psk() -> Self {
800            fidl_security::WpaCredentials::Psk(wpa_psk().0)
801        }
802
803        fn passphrase() -> Self {
804            fidl_security::WpaCredentials::Passphrase(wpa_passphrase().into())
805        }
806    }
807
808    trait BareCredentialsTestCase: Sized {
809        fn wep_key() -> Self;
810        fn psk() -> Self;
811        fn passphrase() -> Self;
812    }
813
814    impl BareCredentialsTestCase for BareCredentials {
815        fn wep_key() -> Self {
816            BareCredentials::WepKey(wep_key())
817        }
818
819        fn psk() -> Self {
820            BareCredentials::WpaPsk(wpa_psk())
821        }
822
823        fn passphrase() -> Self {
824            BareCredentials::WpaPassphrase(wpa_passphrase())
825        }
826    }
827
828    trait WpaDescriptorTestCase: Sized {
829        const WPA1: Self;
830        const WPA2_PERSONAL: Self;
831        const WPA3_PERSONAL: Self;
832    }
833
834    impl WpaDescriptorTestCase for wpa::WpaDescriptor {
835        const WPA1: Self = wpa::WpaDescriptor::Wpa1 { credentials: () };
836        const WPA2_PERSONAL: Self = wpa::WpaDescriptor::Wpa2 {
837            cipher: None,
838            authentication: wpa::Authentication::Personal(()),
839        };
840        const WPA3_PERSONAL: Self = wpa::WpaDescriptor::Wpa3 {
841            cipher: None,
842            authentication: wpa::Authentication::Personal(()),
843        };
844    }
845
846    #[test_case(WpaCredentialsTestCase::psk() => matches Ok(wpa::Wpa1Credentials::Psk(_)))]
847    #[test_case(WpaCredentialsTestCase::passphrase() => matches
848        Ok(wpa::Wpa1Credentials::Passphrase(_))
849    )]
850    fn wpa1_credentials_from_credentials_fidl(
851        credentials: fidl_security::WpaCredentials,
852    ) -> Result<wpa::Wpa1Credentials, SecurityError> {
853        credentials.try_into()
854    }
855
856    #[test_case(WpaCredentialsTestCase::psk() => matches Ok(wpa::Wpa2PersonalCredentials::Psk(_)))]
857    #[test_case(WpaCredentialsTestCase::passphrase() => matches
858        Ok(wpa::Wpa2PersonalCredentials::Passphrase(_))
859    )]
860    fn wpa2_personal_credentials_from_credentials_fidl(
861        credentials: fidl_security::WpaCredentials,
862    ) -> Result<wpa::Wpa2PersonalCredentials, SecurityError> {
863        credentials.try_into()
864    }
865
866    #[test_case(WpaCredentialsTestCase::psk() => Err(SecurityError::Incompatible))]
867    #[test_case(WpaCredentialsTestCase::passphrase() => matches
868        Ok(wpa::Wpa3PersonalCredentials::Passphrase(_))
869    )]
870    fn wpa3_personal_credentials_from_credentials_fidl(
871        credentials: fidl_security::WpaCredentials,
872    ) -> Result<wpa::Wpa3PersonalCredentials, SecurityError> {
873        credentials.try_into()
874    }
875
876    #[test_case(PersonalCredentialsTestCase::psk() => matches Ok(wpa::Wpa1Credentials::Psk(_)))]
877    #[test_case(PersonalCredentialsTestCase::passphrase() => matches
878        Ok(wpa::Wpa1Credentials::Passphrase(_))
879    )]
880    fn wpa1_personal_credentials_from_personal_credentials(
881        credentials: wpa::PersonalCredentials,
882    ) -> Result<wpa::Wpa1Credentials, SecurityError> {
883        credentials.try_into()
884    }
885
886    #[test_case(PersonalCredentialsTestCase::psk() => Err(SecurityError::Incompatible))]
887    #[test_case(PersonalCredentialsTestCase::passphrase() => matches
888        Ok(wpa::Wpa3PersonalCredentials::Passphrase(_))
889    )]
890    fn wpa3_personal_credentials_from_personal_credentials(
891        credentials: wpa::PersonalCredentials,
892    ) -> Result<wpa::Wpa3PersonalCredentials, SecurityError> {
893        credentials.try_into()
894    }
895
896    #[test_case(wpa::Cipher::TKIP => Ok(wpa::Wpa2Cipher::TKIP))]
897    #[test_case(wpa::Cipher::CCMP => Ok(wpa::Wpa2Cipher::CCMP))]
898    #[test_case(wpa::Cipher::GCMP => Err(SecurityError::Incompatible))]
899    fn wpa2_cipher_from_cipher(cipher: wpa::Cipher) -> Result<wpa::Wpa2Cipher, SecurityError> {
900        cipher.try_into()
901    }
902
903    #[test_case(wpa::Cipher::TKIP => Err(SecurityError::Incompatible))]
904    #[test_case(wpa::Cipher::CCMP => Ok(wpa::Wpa3Cipher::CCMP))]
905    #[test_case(wpa::Cipher::GCMP => Ok(wpa::Wpa3Cipher::GCMP))]
906    fn wpa3_cipher_from_cipher(cipher: wpa::Cipher) -> Result<wpa::Wpa3Cipher, SecurityError> {
907        cipher.try_into()
908    }
909
910    #[test_case(WpaDescriptorTestCase::WPA1, BareCredentialsTestCase::psk() =>
911        Ok(wpa::WpaAuthenticator::Wpa1 {
912            credentials: wpa::Wpa1Credentials::Psk(wpa_psk()),
913        })
914    )]
915    #[test_case(WpaDescriptorTestCase::WPA2_PERSONAL, BareCredentialsTestCase::psk() =>
916        Ok(wpa::WpaAuthenticator::Wpa2 {
917            cipher: None,
918            authentication: wpa::Authentication::Personal(
919                wpa::Wpa2PersonalCredentials::Psk(wpa_psk())
920            ),
921        })
922    )]
923    #[test_case(WpaDescriptorTestCase::WPA3_PERSONAL, BareCredentialsTestCase::passphrase() =>
924        Ok(wpa::WpaAuthenticator::Wpa3 {
925            cipher: None,
926            authentication: wpa::Authentication::Personal(
927                wpa::Wpa3PersonalCredentials::Passphrase(wpa_passphrase())
928            ),
929        })
930    )]
931    #[test_case(WpaDescriptorTestCase::WPA2_PERSONAL, BareCredentialsTestCase::wep_key() =>
932        Err(SecurityError::Incompatible)
933    )]
934    #[test_case(WpaDescriptorTestCase::WPA3_PERSONAL, BareCredentialsTestCase::psk() =>
935        Err(SecurityError::Incompatible)
936    )]
937    fn wpa_bind_descriptor(
938        descriptor: wpa::WpaDescriptor,
939        credentials: BareCredentials,
940    ) -> Result<wpa::WpaAuthenticator, SecurityError> {
941        descriptor.bind(credentials)
942    }
943}