blob: 4a8ab1d1961066585debdc13b4ac640474436ce2 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Wireless network security descriptors and authenticators.
//!
//! This module describes wireless network security protocols as well as credentials used to
//! authenticate using those protocols. Types in this module provide two important features:
//! conversions from and into FIDL types used within the WLAN stack and consistency. Here,
//! consistency means that types have no invalid values; given an instance of a type, it is always
//! valid. For example, a `SecurityAuthenticator` always represents a valid protocol-credential
//! pair.
//!
//! The `SecurityDescriptor` and `SecurityAuthenticator` types form the primary API of this module.
//! A _descriptor_ merely describes (names) a security protocol such as WPA2 Personal using TKIP.
//! An _authenticator_ describes a security protocol and additionally contains credentials used to
//! authenticate using that protocol such as WPA Personal using TKIP with a PSK credential.
//! Authenticators can be converted into descriptors (which drops any associated credentials).
//!
//! # FIDL
//!
//! Types in this module support conversion into and from datagrams in the
//! `fuchsia.wlan.common.security` package. When interacting with FIDL, most code should prefer
//! conversions between FIDL types and this module's `SecurityDescriptor` and
//! `SecurityAuthenticator` types, though conversions for intermediate types are also provided.
//!
//! The models used by this module and the FIDL package differ, so types do not always have a
//! direct analog, but the table below describes the most straightforward and important mappings.
//!
//! | Rust Type | FIDL Type | Rust to FIDL | FIDL to Rust |
//! |-------------------------|------------------|--------------|--------------|
//! | `SecurityDescriptor` | `Protocol` | `From` | `From` |
//! | `SecurityAuthenticator` | `Authentication` | `From` | `TryFrom` |
//!
//! # Cryptography and RSN
//!
//! This module does **not** implement any security protocols nor cryptography and only describes
//! them with limited detail as needed at API boundaries and in code that merely interacts with
//! these protocols. See the `rsn` crate for implementations of the IEEE Std 802.11-2016 Robust
//! Security Network (RSN).
// NOTE: At the time of this writing, the WLAN stack does not support WPA Enterprise. One
// consequence of this is that conversions between Rust and FIDL types may return errors or
// panic when they involve WPA Enterprise representations. See fxbug.dev/92693 and the TODOs
// in this module.
pub mod wep;
pub mod wpa;
use fidl_fuchsia_wlan_common_security as fidl_security;
use std::convert::TryFrom;
use thiserror::Error;
use crate::security::{
wep::WepKey,
wpa::credential::{Passphrase, PassphraseError, Psk, PskError},
};
/// Extension methods for the `Credentials` FIDL datagram.
pub trait CredentialsExt {
fn into_wep(self) -> Option<fidl_security::WepCredentials>;
fn into_wpa(self) -> Option<fidl_security::WpaCredentials>;
}
impl CredentialsExt for fidl_security::Credentials {
fn into_wep(self) -> Option<fidl_security::WepCredentials> {
if let fidl_security::Credentials::Wep(credentials) = self {
Some(credentials)
} else {
None
}
}
fn into_wpa(self) -> Option<fidl_security::WpaCredentials> {
if let fidl_security::Credentials::Wpa(credentials) = self {
Some(credentials)
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum SecurityError {
#[error(transparent)]
Wep(#[from] wep::WepError),
#[error(transparent)]
Wpa(#[from] wpa::WpaError),
/// This error occurs when there is an incompatibility between security protocols, features,
/// and/or credentials.
///
/// Note that this is distinct from `SecurityError::Unsupported`.
#[error("incompatible protocol or features")]
Incompatible,
/// This error occurs when a specified security protocol, features, and/or credentials are
/// **not** supported.
///
/// Note that this is distinct from `SecurityError::Incompatible`. Unsupported features may be
/// compatible and specified in IEEE Std. 802.11-2016.
#[error("unsupported protocol or features")]
Unsupported,
}
impl From<PassphraseError> for SecurityError {
fn from(error: PassphraseError) -> Self {
SecurityError::Wpa(error.into())
}
}
impl From<PskError> for SecurityError {
fn from(error: PskError) -> Self {
SecurityError::Wpa(error.into())
}
}
// TODO(fxbug.dev/96416): In a term longer than fxbug.dev/95873, this type should probably be
// removed. In general, code should not deal in bare credentials and instead
// should use authenticators (or descriptors).
/// General credential data that is not explicitly coupled to a particular security protocol.
///
/// The variants of this enumeration are particular to general protocols (i.e., WEP and WPA), but
/// don't provide any more details or validation. For WPA credential data, this means that the
/// version of the WPA security protocol is entirely unknown.
///
/// This type is meant for code and APIs that accept such bare credentials and must incorporate
/// additional information or apply heuristics to negotiate a specific protocol. For example, this
/// occurs in code that communicates directly with SME without support from the Policy layer to
/// derive this information.
///
/// The FIDL analogue of this type is `fuchsia.wlan.common.security.Credentials`, into and from
/// which this type can be infallibly converted.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BareCredentials {
/// WEP key.
WepKey(WepKey),
/// WPA passphrase.
///
/// Passphrases can be used to authenticate with WPA1, WPA2, and WPA3.
WpaPassphrase(Passphrase),
/// WPA PSK.
///
/// PSKs are distinct from passphrases and can be used to authenticate with WPA1 and WPA2. A
/// PSK cannot be used to authenticate with WPA3.
WpaPsk(Psk),
}
impl From<BareCredentials> for fidl_security::Credentials {
fn from(credentials: BareCredentials) -> Self {
match credentials {
BareCredentials::WepKey(key) => {
fidl_security::Credentials::Wep(fidl_security::WepCredentials { key: key.into() })
}
BareCredentials::WpaPassphrase(passphrase) => fidl_security::Credentials::Wpa(
fidl_security::WpaCredentials::Passphrase(passphrase.into()),
),
BareCredentials::WpaPsk(psk) => {
fidl_security::Credentials::Wpa(fidl_security::WpaCredentials::Psk(psk.into()))
}
}
}
}
impl TryFrom<fidl_security::Credentials> for BareCredentials {
type Error = SecurityError;
fn try_from(credentials: fidl_security::Credentials) -> Result<Self, Self::Error> {
match credentials {
fidl_security::Credentials::Wep(fidl_security::WepCredentials { key }) => {
WepKey::try_from_literal_bytes(key.as_slice())
.map(|key| BareCredentials::WepKey(key))
.map_err(From::from)
}
fidl_security::Credentials::Wpa(credentials) => match credentials {
fidl_security::WpaCredentials::Passphrase(passphrase) => {
Passphrase::try_from(passphrase)
.map(|passphrase| BareCredentials::WpaPassphrase(passphrase))
.map_err(From::from)
}
fidl_security::WpaCredentials::Psk(psk) => Psk::try_from(psk.as_slice())
.map(|psk| BareCredentials::WpaPsk(psk))
.map_err(From::from),
// Unknown variant.
_ => Err(SecurityError::Incompatible),
},
// Unknown variant.
_ => Err(SecurityError::Incompatible),
}
}
}
/// Description of a wireless network security protocol.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SecurityDescriptor {
Open,
Wep,
Wpa(wpa::WpaDescriptor),
}
impl SecurityDescriptor {
pub fn is_open(&self) -> bool {
matches!(self, SecurityDescriptor::Open)
}
pub fn is_wep(&self) -> bool {
matches!(self, SecurityDescriptor::Wep)
}
pub fn is_wpa(&self) -> bool {
matches!(self, SecurityDescriptor::Wpa(_))
}
}
impl From<fidl_security::Protocol> for SecurityDescriptor {
fn from(protocol: fidl_security::Protocol) -> Self {
match protocol {
fidl_security::Protocol::Open => SecurityDescriptor::Open,
fidl_security::Protocol::Wep => SecurityDescriptor::Wep,
fidl_security::Protocol::Wpa1 => {
SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa1 { credentials: () })
}
fidl_security::Protocol::Wpa2Personal => {
SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
authentication: wpa::Authentication::Personal(()),
cipher: None,
})
}
fidl_security::Protocol::Wpa2Enterprise => {
SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa2 {
authentication: wpa::Authentication::Enterprise(()),
cipher: None,
})
}
fidl_security::Protocol::Wpa3Personal => {
SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
authentication: wpa::Authentication::Personal(()),
cipher: None,
})
}
fidl_security::Protocol::Wpa3Enterprise => {
SecurityDescriptor::Wpa(wpa::WpaDescriptor::Wpa3 {
authentication: wpa::Authentication::Enterprise(()),
cipher: None,
})
}
_ => panic!("unknown FIDL security protocol variant"),
}
}
}
impl From<SecurityDescriptor> for fidl_security::Protocol {
fn from(descriptor: SecurityDescriptor) -> Self {
match descriptor {
SecurityDescriptor::Open => fidl_security::Protocol::Open,
SecurityDescriptor::Wep => fidl_security::Protocol::Wep,
SecurityDescriptor::Wpa(wpa) => match wpa {
wpa::WpaDescriptor::Wpa1 { .. } => fidl_security::Protocol::Wpa1,
wpa::WpaDescriptor::Wpa2 { authentication, .. } => match authentication {
wpa::Authentication::Personal(_) => fidl_security::Protocol::Wpa2Personal,
wpa::Authentication::Enterprise(_) => fidl_security::Protocol::Wpa2Enterprise,
},
wpa::WpaDescriptor::Wpa3 { authentication, .. } => match authentication {
wpa::Authentication::Personal(_) => fidl_security::Protocol::Wpa3Personal,
wpa::Authentication::Enterprise(_) => fidl_security::Protocol::Wpa3Enterprise,
},
},
}
}
}
impl From<wpa::WpaDescriptor> for SecurityDescriptor {
fn from(descriptor: wpa::WpaDescriptor) -> Self {
SecurityDescriptor::Wpa(descriptor)
}
}
/// Credentials and configuration for authenticating using a particular wireless network security
/// protocol.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SecurityAuthenticator {
Open,
Wep(wep::WepAuthenticator),
Wpa(wpa::WpaAuthenticator),
}
impl SecurityAuthenticator {
/// Converts an authenticator into a descriptor with no payload (the unit type `()`). Any
/// payload (i.e., credentials) are dropped.
pub fn into_descriptor(self) -> SecurityDescriptor {
match self {
SecurityAuthenticator::Open => SecurityDescriptor::Open,
SecurityAuthenticator::Wep(_) => SecurityDescriptor::Wep,
SecurityAuthenticator::Wpa(authenticator) => {
SecurityDescriptor::Wpa(authenticator.into_descriptor())
}
}
}
pub fn into_wep(self) -> Option<wep::WepAuthenticator> {
match self {
SecurityAuthenticator::Wep(authenticator) => Some(authenticator),
_ => None,
}
}
pub fn into_wpa(self) -> Option<wpa::WpaAuthenticator> {
match self {
SecurityAuthenticator::Wpa(authenticator) => Some(authenticator),
_ => None,
}
}
/// Converts the authenticator to bare credentials, if any.
///
/// Returns `None` if the authenticator is `SecurityAuthenticator::None`, as there are no
/// corresponding credentials in this case.
pub fn to_credentials(&self) -> Option<BareCredentials> {
match self {
SecurityAuthenticator::Open => None,
SecurityAuthenticator::Wep(wep::WepAuthenticator { ref key }) => {
Some(key.clone().into())
}
SecurityAuthenticator::Wpa(ref wpa) => Some(wpa.to_credentials().into()),
}
}
pub fn as_wep(&self) -> Option<&wep::WepAuthenticator> {
match self {
SecurityAuthenticator::Wep(ref authenticator) => Some(authenticator),
_ => None,
}
}
pub fn as_wpa(&self) -> Option<&wpa::WpaAuthenticator> {
match self {
SecurityAuthenticator::Wpa(ref authenticator) => Some(authenticator),
_ => None,
}
}
pub fn is_open(&self) -> bool {
matches!(self, SecurityAuthenticator::Open)
}
pub fn is_wep(&self) -> bool {
matches!(self, SecurityAuthenticator::Wep(_))
}
pub fn is_wpa(&self) -> bool {
matches!(self, SecurityAuthenticator::Wpa(_))
}
}
impl From<wep::WepAuthenticator> for SecurityAuthenticator {
fn from(authenticator: wep::WepAuthenticator) -> Self {
SecurityAuthenticator::Wep(authenticator)
}
}
impl From<wpa::WpaAuthenticator> for SecurityAuthenticator {
fn from(authenticator: wpa::WpaAuthenticator) -> Self {
SecurityAuthenticator::Wpa(authenticator)
}
}
impl From<SecurityAuthenticator> for fidl_security::Authentication {
fn from(authenticator: SecurityAuthenticator) -> Self {
match authenticator {
SecurityAuthenticator::Open => fidl_security::Authentication {
protocol: fidl_security::Protocol::Open,
credentials: None,
},
SecurityAuthenticator::Wep(wep) => fidl_security::Authentication {
protocol: fidl_security::Protocol::Wep,
credentials: Some(Box::new(fidl_security::Credentials::Wep(wep.into()))),
},
SecurityAuthenticator::Wpa(wpa) => {
use wpa::Authentication::{Enterprise, Personal};
use wpa::Wpa::{Wpa1, Wpa2, Wpa3};
let protocol = match (&wpa, wpa.to_credentials()) {
(Wpa1 { .. }, _) => fidl_security::Protocol::Wpa1,
(Wpa2 { .. }, Personal(_)) => fidl_security::Protocol::Wpa2Personal,
(Wpa2 { .. }, Enterprise(_)) => fidl_security::Protocol::Wpa2Enterprise,
(Wpa3 { .. }, Personal(_)) => fidl_security::Protocol::Wpa3Personal,
(Wpa3 { .. }, Enterprise(_)) => fidl_security::Protocol::Wpa3Enterprise,
};
fidl_security::Authentication {
protocol,
// TODO(fxbug.dev/92693): This panics when encountering WPA Enterprise.
credentials: Some(Box::new(fidl_security::Credentials::Wpa(
wpa.into_credentials().into(),
))),
}
}
}
}
}
/// Converts an `Authentication` FIDL datagram into a `SecurityAuthenticator`.
///
/// This conversion should be preferred where possible.
///
/// # Errors
///
/// Returns an error if the `Authentication` datagram is invalid, such as specifying contradictory
/// protocols or encoding incompatible or invalid credentials.
impl TryFrom<fidl_security::Authentication> for SecurityAuthenticator {
type Error = SecurityError;
fn try_from(authentication: fidl_security::Authentication) -> Result<Self, Self::Error> {
let fidl_security::Authentication { protocol, credentials } = authentication;
match protocol {
fidl_security::Protocol::Open => match credentials {
None => Ok(SecurityAuthenticator::Open),
_ => Err(SecurityError::Incompatible),
},
fidl_security::Protocol::Wep => credentials
.ok_or_else(|| SecurityError::Incompatible)? // No credentials.
.into_wep()
.map(wep::WepAuthenticator::try_from)
.transpose()? // Conversion failure.
.map(From::from)
.ok_or_else(|| SecurityError::Incompatible), // Non-WEP credentials.
fidl_security::Protocol::Wpa1 => credentials
.ok_or_else(|| SecurityError::Incompatible)? // No credentials.
.into_wpa()
.map(wpa::Wpa1Credentials::try_from)
.transpose()? // Conversion failure.
.map(|credentials| wpa::WpaAuthenticator::Wpa1 { credentials })
.map(From::from)
.ok_or_else(|| SecurityError::Incompatible), // Non-WPA credentials.
fidl_security::Protocol::Wpa2Personal => credentials
.ok_or_else(|| SecurityError::Incompatible)? // No credentials.
.into_wpa()
.map(wpa::Wpa2PersonalCredentials::try_from)
.transpose()? // Conversion failure.
.map(From::from)
.map(|authentication| wpa::WpaAuthenticator::Wpa2 { cipher: None, authentication })
.map(From::from)
.ok_or_else(|| SecurityError::Incompatible), // Non-WPA credentials.
fidl_security::Protocol::Wpa3Personal => credentials
.ok_or_else(|| SecurityError::Incompatible)? // No credentials.
.into_wpa()
.map(wpa::Wpa3PersonalCredentials::try_from)
.transpose()? // Conversion failure.
.map(From::from)
.map(|authentication| wpa::WpaAuthenticator::Wpa3 { cipher: None, authentication })
.map(From::from)
.ok_or_else(|| SecurityError::Incompatible), // Non-WPA credentials.
// TODO(fxbug.dev/92693): This returns an error when encountering WPA Enterprise
// protocols. Some conversions of composing types panic, but
// this top-level conversion insulates client code from this and
// instead yields an error.
_ => Err(SecurityError::Incompatible),
}
}
}
#[cfg(test)]
mod tests {
use fidl_fuchsia_wlan_common_security as fidl_security;
use std::convert::{TryFrom, TryInto};
use test_case::test_case;
use crate::security::{
wpa::{self, Authentication, Wpa2PersonalCredentials},
SecurityAuthenticator, SecurityError,
};
pub trait AuthenticationTestCase: Sized {
fn wpa2_personal_psk() -> Self;
fn wpa3_personal_psk() -> Self;
fn wpa3_personal_wep_key() -> Self;
fn wpa3_personal_no_credentials() -> Self;
}
impl AuthenticationTestCase for fidl_security::Authentication {
fn wpa2_personal_psk() -> Self {
fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa2Personal,
credentials: Some(Box::new(fidl_security::Credentials::Wpa(
fidl_security::WpaCredentials::Psk([0u8; 32]),
))),
}
}
// Invalid: WPA3 with PSK.
fn wpa3_personal_psk() -> Self {
fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa3Personal,
credentials: Some(Box::new(fidl_security::Credentials::Wpa(
fidl_security::WpaCredentials::Psk([0u8; 32]),
))),
}
}
// Invalid: WPA3 with WEP key.
fn wpa3_personal_wep_key() -> Self {
fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa3Personal,
credentials: Some(Box::new(fidl_security::Credentials::Wep(
fidl_security::WepCredentials { key: vec![0u8; 13] },
))),
}
}
// Invalid: WPA3 with no credentials.
fn wpa3_personal_no_credentials() -> Self {
fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa3Personal,
credentials: None,
}
}
}
// TODO(seanolson): Move this assertion into a `SecurityAuthenticatorAssertion` trait (a la
// `AuthenticationTestCase`) and test via the `using` pattern in the
// `test-case` 2.0.0 series.
#[test_case(AuthenticationTestCase::wpa2_personal_psk() => matches
Ok(SecurityAuthenticator::Wpa(wpa::Wpa::Wpa2 {
authentication: Authentication::Personal(Wpa2PersonalCredentials::Psk(_)),
..
}))
)]
#[test_case(AuthenticationTestCase::wpa3_personal_psk() => Err(SecurityError::Incompatible))]
#[test_case(AuthenticationTestCase::wpa3_personal_wep_key() => Err(SecurityError::Incompatible))]
#[test_case(AuthenticationTestCase::wpa3_personal_no_credentials() => Err(SecurityError::Incompatible))]
fn security_authenticator_from_authentication_fidl(
authentication: fidl_security::Authentication,
) -> Result<SecurityAuthenticator, SecurityError> {
SecurityAuthenticator::try_from(authentication)
}
#[test]
fn authentication_fidl_from_security_authenticator() {
let authenticator = SecurityAuthenticator::Wpa(wpa::WpaAuthenticator::Wpa3 {
authentication: wpa::Authentication::Personal(
wpa::Wpa3PersonalCredentials::Passphrase("roflcopter".try_into().unwrap()),
),
cipher: None,
});
let authentication = fidl_security::Authentication::from(authenticator);
assert_eq!(
authentication,
fidl_security::Authentication {
protocol: fidl_security::Protocol::Wpa3Personal,
credentials: Some(Box::new(fidl_security::Credentials::Wpa(
fidl_security::WpaCredentials::Passphrase(b"roflcopter".as_slice().into()),
))),
}
);
}
}