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