blob: cd3b6a2f99b1497fc0f66eba9674353ebf5b11b1 [file] [log] [blame]
// Copyright 2019 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.
use anyhow::format_err;
use fidl_fuchsia_wlan_mlme::{BssDescription, DeviceInfo};
use fidl_fuchsia_wlan_sme as fidl_sme;
use wlan_common::bss::BssDescriptionExt;
use wlan_common::ie::rsn::{akm, cipher};
use wlan_common::ie::wpa::WpaIe;
use wlan_common::organization::Oui;
use wlan_rsn::{self, nonce::NonceReader, NegotiatedProtection, ProtectionInfo};
use super::rsn::{compute_psk, Rsna};
use crate::client::protection::Protection;
/// According to the WiFi Alliance WPA standard (2004), only TKIP support is required. We allow
/// CCMP if the AP requests it.
/// Only supported AKM is PSK
pub fn is_legacy_wpa_compatible(a_wpa: &WpaIe) -> bool {
let multicast_supported = a_wpa.multicast_cipher.has_known_usage()
&& (a_wpa.multicast_cipher.suite_type == cipher::TKIP
|| a_wpa.multicast_cipher.suite_type == cipher::CCMP_128);
let unicast_supported = a_wpa.unicast_cipher_list.iter().any(|c| {
c.has_known_usage() && (c.suite_type == cipher::TKIP || c.suite_type == cipher::CCMP_128)
});
let akm_supported =
a_wpa.akm_list.iter().any(|a| a.has_known_algorithm() && a.suite_type == akm::PSK);
multicast_supported && unicast_supported && akm_supported
}
/// Builds a supplicant for establishing a WPA1 association. WPA Assocation is not an official term,
/// but is used here to refer to the modified RSNA used by WPA1.
pub fn get_legacy_wpa_association(
device_info: &DeviceInfo,
credential: &fidl_sme::Credential,
bss: &BssDescription,
) -> Result<Protection, anyhow::Error> {
let a_wpa = bss.get_wpa_ie()?;
if !is_legacy_wpa_compatible(&a_wpa) {
return Err(format_err!("incompatible legacy WPA {:?}", a_wpa));
}
let s_wpa = construct_s_wpa(&a_wpa);
let negotiated_protection = NegotiatedProtection::from_legacy_wpa(&s_wpa)?;
let psk = compute_psk(credential, &bss.ssid[..])?;
let supplicant = wlan_rsn::Supplicant::new_wpa_personal(
// Note: There should be one Reader per device, not per SME.
// Follow-up with improving on this.
NonceReader::new(&device_info.mac_addr[..])?,
psk,
device_info.mac_addr,
ProtectionInfo::LegacyWpa(s_wpa),
bss.bssid,
ProtectionInfo::LegacyWpa(a_wpa),
)
.map_err(|e| format_err!("failed to create ESS-SA: {:?}", e))?;
Ok(Protection::LegacyWpa(Rsna { negotiated_protection, supplicant: Box::new(supplicant) }))
}
/// Construct a supplicant WPA1 IE with:
/// The same multicast and unicast ciphers as the AP WPA1 IE
/// PSK as the AKM
fn construct_s_wpa(a_wpa: &WpaIe) -> WpaIe {
// Use CCMP if supported, otherwise default to TKIP.
let unicast_cipher = if a_wpa
.unicast_cipher_list
.iter()
.any(|c| c.has_known_usage() && c.suite_type == cipher::CCMP_128)
{
cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::CCMP_128 }
} else {
cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::TKIP }
};
WpaIe {
multicast_cipher: a_wpa.multicast_cipher.clone(),
unicast_cipher_list: vec![unicast_cipher],
akm_list: vec![akm::Akm { oui: Oui::MSFT, suite_type: akm::PSK }],
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{fake_device_info, make_wpa1_ie};
use wlan_common::fake_bss;
const CLIENT_ADDR: [u8; 6] = [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67];
#[test]
fn test_incompatible_multicast_cipher() {
let mut a_wpa = make_wpa1_ie();
a_wpa.multicast_cipher = cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::WEP_40 };
assert_eq!(is_legacy_wpa_compatible(&a_wpa), false);
}
#[test]
fn test_incompatible_unicast_cipher() {
let mut a_wpa = make_wpa1_ie();
a_wpa.unicast_cipher_list =
vec![cipher::Cipher { oui: Oui::DOT11, suite_type: cipher::WEP_40 }];
assert_eq!(is_legacy_wpa_compatible(&a_wpa), false);
}
#[test]
fn test_incompatible_akm() {
let mut a_wpa = make_wpa1_ie();
a_wpa.akm_list = vec![akm::Akm { oui: Oui::DOT11, suite_type: akm::EAP }];
assert_eq!(is_legacy_wpa_compatible(&a_wpa), false);
}
#[test]
fn get_supplicant_ie_tkip() {
let mut a_wpa = make_wpa1_ie();
a_wpa.unicast_cipher_list =
vec![cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::TKIP }];
let s_wpa = construct_s_wpa(&a_wpa);
assert_eq!(
s_wpa.unicast_cipher_list,
vec![cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::TKIP }]
);
}
#[test]
fn get_supplicant_ie_ccmp() {
let mut a_wpa = make_wpa1_ie();
a_wpa.unicast_cipher_list = vec![
cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::TKIP },
cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::CCMP_128 },
];
let s_wpa = construct_s_wpa(&a_wpa);
assert_eq!(
s_wpa.unicast_cipher_list,
vec![cipher::Cipher { oui: Oui::MSFT, suite_type: cipher::CCMP_128 }]
);
}
#[test]
fn test_no_unicast_cipher() {
let mut a_wpa = make_wpa1_ie();
a_wpa.unicast_cipher_list = vec![];
assert_eq!(is_legacy_wpa_compatible(&a_wpa), false);
}
#[test]
fn test_no_akm() {
let mut a_wpa = make_wpa1_ie();
a_wpa.akm_list = vec![];
assert_eq!(is_legacy_wpa_compatible(&a_wpa), false);
}
#[test]
fn test_get_wpa_password_for_unprotected_network() {
let bss = fake_bss!(Open);
let credential = fidl_sme::Credential::Password("somepass".as_bytes().to_vec());
get_legacy_wpa_association(&fake_device_info(CLIENT_ADDR), &credential, &bss)
.expect_err("expect error when password is supplied for unprotected network");
}
#[test]
fn test_get_wpa_no_password_for_protected_network() {
let bss = fake_bss!(Wpa1);
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
get_legacy_wpa_association(&fake_device_info(CLIENT_ADDR), &credential, &bss)
.expect_err("expect error when no password is supplied for protected network");
}
#[test]
fn test_get_wpa_for_rsna_protected_network() {
let bss = fake_bss!(Wpa1);
let credential = fidl_sme::Credential::None(fidl_sme::Empty);
get_legacy_wpa_association(&fake_device_info(CLIENT_ADDR), &credential, &bss)
.expect_err("expect error when treating RSNA as WPA association");
}
#[test]
fn test_get_wpa_psk() {
let bss = fake_bss!(Wpa1);
let credential = fidl_sme::Credential::Psk(vec![0xAA; 32]);
get_legacy_wpa_association(&fake_device_info(CLIENT_ADDR), &credential, &bss)
.expect("expected successful RSNA with valid PSK");
}
#[test]
fn test_get_wpa_invalid_psk() {
let bss = fake_bss!(Wpa1);
// PSK too short
let credential = fidl_sme::Credential::Psk(vec![0xAA; 31]);
get_legacy_wpa_association(&fake_device_info(CLIENT_ADDR), &credential, &bss)
.expect_err("expected RSNA failure with invalid PSK");
}
}