blob: e5ab2ab9fdad61637e7b37f4a58cdc65a71a8cdd [file] [log] [blame]
use bytes::Bytes;
use eapol;
use failure::{bail, ensure, format_err};
use fidl_fuchsia_wlan_mlme::BssDescription;
use std::boxed::Box;
use wlan_rsn::{
self, akm, cipher,
nonce::NonceReader,
psk,
rsna::UpdateSink,
rsne::{self, Rsne},
NegotiatedRsne, OUI,
};
use crate::DeviceInfo;
#[derive(Debug)]
pub struct Rsna {
pub negotiated_rsne: NegotiatedRsne,
pub supplicant: Box<Supplicant>,
}
impl PartialEq for Rsna {
fn eq(&self, other: &Self) -> bool {
self.negotiated_rsne == other.negotiated_rsne
}
}
pub trait Supplicant: std::fmt::Debug + std::marker::Send {
fn start(&mut self) -> Result<(), failure::Error>;
fn reset(&mut self);
fn on_eapol_frame(
&mut self,
update_sink: &mut UpdateSink,
frame: &eapol::Frame,
) -> Result<(), failure::Error>;
}
impl Supplicant for wlan_rsn::Supplicant {
fn start(&mut self) -> Result<(), failure::Error> {
wlan_rsn::Supplicant::start(self)
}
fn reset(&mut self) {
wlan_rsn::Supplicant::reset(self)
}
fn on_eapol_frame(
&mut self,
update_sink: &mut UpdateSink,
frame: &eapol::Frame,
) -> Result<(), failure::Error> {
wlan_rsn::Supplicant::on_eapol_frame(self, update_sink, frame)
}
}
/// Supported Ciphers and AKMs:
/// Group Data Ciphers: CCMP-128, TKIP
/// Pairwise Cipher: CCMP-128
/// AKM: PSK
pub fn is_rsn_compatible(a_rsne: &Rsne) -> bool {
let group_data_supported = a_rsne.group_data_cipher_suite.as_ref().map_or(false, |c| {
// IEEE allows TKIP usage only in GTKSAs for compatibility reasons.
// TKIP is considered broken and should never be used in a PTKSA or IGTKSA.
c.has_known_usage() && (c.suite_type == cipher::CCMP_128 || c.suite_type == cipher::TKIP)
});
let pairwise_supported = a_rsne
.pairwise_cipher_suites
.iter()
.any(|c| c.has_known_usage() && c.suite_type == cipher::CCMP_128);
let akm_supported =
a_rsne.akm_suites.iter().any(|a| a.has_known_algorithm() && a.suite_type == akm::PSK);
let caps_supported = a_rsne.rsn_capabilities.as_ref().map_or(true, |caps| {
!(caps.no_pairwise()
|| caps.mgmt_frame_protection_req()
|| caps.joint_multiband()
|| caps.peerkey_enabled()
|| caps.ssp_amsdu_req()
|| caps.pbac()
|| caps.extended_key_id())
});
group_data_supported && pairwise_supported && akm_supported && caps_supported
}
pub fn get_rsna(
device_info: &DeviceInfo,
password: &[u8],
bss: &BssDescription,
) -> Result<Option<Rsna>, failure::Error> {
ensure!(bss.rsn.is_none() == password.is_empty(), "invalid password param for BSS");
let a_rsne_bytes = match &bss.rsn {
None => return Ok(None),
Some(x) => x,
};
let a_rsne = rsne::from_bytes(&a_rsne_bytes[..])
.to_full_result()
.map_err(|e| format_err!("invalid RSNE {:?}: {:?}", &a_rsne_bytes[..], e))?;
let s_rsne = derive_s_rsne(&a_rsne)?;
let negotiated_rsne = NegotiatedRsne::from_rsne(&s_rsne)?;
let supplicant = wlan_rsn::Supplicant::new_wpa2psk_ccmp128(
// Note: There should be one Reader per device, not per SME.
// Follow-up with improving on this.
NonceReader::new(&device_info.addr[..])?,
psk::compute(&password[..], &bss.ssid[..])?,
device_info.addr,
s_rsne,
bss.bssid,
a_rsne,
)
.map_err(|e| format_err!("failed to create ESS-SA: {:?}", e))?;
Ok(Some(Rsna { negotiated_rsne, supplicant: Box::new(supplicant) }))
}
/// Constructs Supplicant's RSNE with:
/// Group Data Cipher: same as A-RSNE (CCMP-128 or TKIP)
/// Pairwise Cipher: CCMP-128
/// AKM: PSK
fn derive_s_rsne(a_rsne: &Rsne) -> Result<Rsne, failure::Error> {
if !is_rsn_compatible(&a_rsne) {
bail!("incompatible RSNE {:?}", a_rsne);
}
// If Authenticator's RSNE is supported, construct Supplicant's RSNE.
let mut s_rsne = Rsne::new();
s_rsne.group_data_cipher_suite = a_rsne.group_data_cipher_suite.clone();
let pairwise_cipher =
cipher::Cipher { oui: Bytes::from(&OUI[..]), suite_type: cipher::CCMP_128 };
s_rsne.pairwise_cipher_suites.push(pairwise_cipher);
let akm = akm::Akm { oui: Bytes::from(&OUI[..]), suite_type: akm::PSK };
s_rsne.akm_suites.push(akm);
s_rsne.rsn_capabilities = a_rsne.rsn_capabilities.clone();
Ok(s_rsne)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
client::test_utils::{fake_protected_bss_description, fake_unprotected_bss_description},
test_utils::{make_rsne, rsne_as_bytes, wpa2_psk_ccmp_rsne_with_caps},
};
use wlan_rsn::rsne::RsnCapabilities;
const CLIENT_ADDR: [u8; 6] = [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67];
#[test]
fn test_rsn_capabilities() {
let a_rsne = wpa2_psk_ccmp_rsne_with_caps(RsnCapabilities(0x000C));
assert!(is_rsn_compatible(&a_rsne));
let a_rsne = wpa2_psk_ccmp_rsne_with_caps(RsnCapabilities(0));
assert!(is_rsn_compatible(&a_rsne));
let a_rsne = wpa2_psk_ccmp_rsne_with_caps(RsnCapabilities(1));
assert!(is_rsn_compatible(&a_rsne));
let a_rsne = wpa2_psk_ccmp_rsne_with_caps(RsnCapabilities(2));
assert!(!is_rsn_compatible(&a_rsne));
}
#[test]
fn test_incompatible_group_data_cipher() {
let a_rsne = make_rsne(Some(cipher::GCMP_256), vec![cipher::CCMP_128], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_incompatible_pairwise_cipher() {
let a_rsne = make_rsne(Some(cipher::CCMP_128), vec![cipher::BIP_CMAC_256], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_tkip_pairwise_cipher() {
let a_rsne = make_rsne(Some(cipher::CCMP_128), vec![cipher::TKIP], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_tkip_group_data_cipher() {
let a_rsne = make_rsne(Some(cipher::TKIP), vec![cipher::CCMP_128], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), true);
let s_rsne = derive_s_rsne(&a_rsne).unwrap();
let expected_rsne_bytes =
vec![48, 18, 1, 0, 0, 15, 172, 2, 1, 0, 0, 15, 172, 4, 1, 0, 0, 15, 172, 2];
assert_eq!(rsne_as_bytes(s_rsne), expected_rsne_bytes);
}
#[test]
fn test_ccmp128_group_data_pairwise_cipher_psk() {
let a_rsne = make_rsne(Some(cipher::CCMP_128), vec![cipher::CCMP_128], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), true);
let s_rsne = derive_s_rsne(&a_rsne).unwrap();
let expected_rsne_bytes =
vec![48, 18, 1, 0, 0, 15, 172, 4, 1, 0, 0, 15, 172, 4, 1, 0, 0, 15, 172, 2];
assert_eq!(rsne_as_bytes(s_rsne), expected_rsne_bytes);
}
#[test]
fn test_mixed_mode() {
let a_rsne = make_rsne(
Some(cipher::CCMP_128),
vec![cipher::CCMP_128, cipher::TKIP],
vec![akm::PSK, akm::FT_PSK],
);
assert_eq!(is_rsn_compatible(&a_rsne), true);
let s_rsne = derive_s_rsne(&a_rsne).unwrap();
let expected_rsne_bytes =
vec![48, 18, 1, 0, 0, 15, 172, 4, 1, 0, 0, 15, 172, 4, 1, 0, 0, 15, 172, 2];
assert_eq!(rsne_as_bytes(s_rsne), expected_rsne_bytes);
}
#[test]
fn test_no_group_data_cipher() {
let a_rsne = make_rsne(None, vec![cipher::CCMP_128], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_no_pairwise_cipher() {
let a_rsne = make_rsne(Some(cipher::CCMP_128), vec![], vec![akm::PSK]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_no_akm() {
let a_rsne = make_rsne(Some(cipher::CCMP_128), vec![cipher::CCMP_128], vec![]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_incompatible_akm() {
let a_rsne = make_rsne(Some(cipher::CCMP_128), vec![cipher::CCMP_128], vec![akm::EAP]);
assert_eq!(is_rsn_compatible(&a_rsne), false);
}
#[test]
fn test_get_rsna_password_for_unprotected_network() {
let bss = fake_unprotected_bss_description(b"foo_bss".to_vec());
let rsna = get_rsna(&make_device_info(), "somepass".as_bytes(), &bss);
assert!(rsna.is_err(), "expect error when password is supplied for unprotected network")
}
#[test]
fn test_get_rsna_no_password_for_protected_network() {
let bss = fake_protected_bss_description(b"foo_bss".to_vec());
let rsna = get_rsna(&make_device_info(), "".as_bytes(), &bss);
assert!(rsna.is_err(), "expect error when no password is supplied for protected network")
}
fn make_device_info() -> DeviceInfo {
DeviceInfo { addr: CLIENT_ADDR, bands: vec![] }
}
}