blob: a81c69c3531dc7261171eb38a2c89b328e800c92 [file] [log] [blame]
// Copyright 2018 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 failure::{bail, ensure};
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use log::error;
use std::boxed::Box;
use std::collections::HashMap;
use wlan_rsn::{
key::exchange::Key,
rsna::{SecAssocStatus, SecAssocUpdate, UpdateSink},
};
use crate::{
ap::{
aid::{self, AssociationId},
authenticator::Authenticator,
event::{self, ClientEvent, Event},
Context,
},
timer::EventId,
MacAddr, MlmeRequest,
};
#[derive(Debug)]
pub struct RemoteClient {
pub addr: MacAddr,
pub aid: AssociationId,
pub authenticator: Option<Box<Authenticator>>,
key_exchange_timeout: Option<EventId>,
}
impl RemoteClient {
fn new(addr: MacAddr, aid: AssociationId, authenticator: Option<Box<Authenticator>>) -> Self {
RemoteClient { addr, aid, authenticator, key_exchange_timeout: None }
}
pub fn handle_timeout(&mut self, event_id: EventId, event: ClientEvent, ctx: &mut Context) {
match event {
ClientEvent::KeyExchangeTimeout { attempt }
if triggered(&self.key_exchange_timeout, event_id) =>
{
if attempt < event::KEY_EXCHANGE_MAX_ATTEMPTS {
self.initiate_key_exchange(ctx, attempt + 1);
} else {
cancel(&mut self.key_exchange_timeout);
ctx.mlme_sink.send(MlmeRequest::Deauthenticate(
fidl_mlme::DeauthenticateRequest {
peer_sta_address: self.addr.clone(),
reason_code: fidl_mlme::ReasonCode::FourwayHandshakeTimeout,
},
));
}
}
_ => (),
}
}
pub fn handle_eapol_ind(
&mut self,
ind: fidl_mlme::EapolIndication,
ctx: &mut Context,
) -> Result<(), failure::Error> {
let authenticator = match self.authenticator.as_mut() {
Some(authenticator) => authenticator,
None => bail!("ignoring EapolInd msg; BSS is not protected"),
};
let mic_size = authenticator.get_negotiated_rsne().mic_size;
match eapol::key_frame_from_bytes(&ind.data, mic_size).to_full_result() {
Ok(key_frame) => {
let frame = eapol::Frame::Key(key_frame);
let mut update_sink = UpdateSink::default();
match authenticator.on_eapol_frame(&mut update_sink, &frame) {
Ok(()) => self.process_authenticator_updates(&update_sink, ctx),
Err(e) => bail!("failed processing EAPoL key frame: {}", e),
}
}
Err(_) => bail!("error parsing EAPoL key frame"),
}
Ok(())
}
pub fn initiate_key_exchange(&mut self, ctx: &mut Context, attempt: u32) {
let event_id = self.schedule_timer(ClientEvent::KeyExchangeTimeout { attempt }, ctx);
self.key_exchange_timeout.replace(event_id);
match self.authenticator.as_mut() {
Some(authenticator) => {
let mut update_sink = UpdateSink::default();
match authenticator.initiate(&mut update_sink) {
Ok(()) => self.process_authenticator_updates(&update_sink, ctx),
Err(e) => error!("error initiating key exchange: {}", e),
}
}
None => error!("authenticator not found for {:?}", self.addr),
}
}
fn schedule_timer(&self, event: ClientEvent, ctx: &mut Context) -> EventId {
ctx.timer.schedule(Event::Client { addr: self.addr.clone(), event })
}
fn process_authenticator_updates(&mut self, update_sink: &UpdateSink, ctx: &mut Context) {
for update in update_sink {
match update {
SecAssocUpdate::TxEapolKeyFrame(frame) => {
let mut buf = Vec::with_capacity(frame.len());
frame.as_bytes(false, &mut buf);
let a_addr = ctx.device_info.addr.clone();
ctx.mlme_sink.send(MlmeRequest::Eapol(fidl_mlme::EapolRequest {
src_addr: a_addr,
dst_addr: self.addr.clone(),
data: buf,
}));
}
SecAssocUpdate::Key(key) => self.send_key(key, ctx),
SecAssocUpdate::Status(status) => match status {
SecAssocStatus::EssSaEstablished => {
cancel(&mut self.key_exchange_timeout);
ctx.mlme_sink.send(MlmeRequest::SetCtrlPort(
fidl_mlme::SetControlledPortRequest {
peer_sta_address: self.addr.clone(),
state: fidl_mlme::ControlledPortState::Open,
},
));
}
_ => (),
},
}
}
}
fn send_key(&mut self, key: &Key, ctx: &mut Context) {
let set_key_descriptor = match key {
Key::Ptk(ptk) => fidl_mlme::SetKeyDescriptor {
key: ptk.tk().to_vec(),
key_id: 0,
key_type: fidl_mlme::KeyType::Pairwise,
address: self.addr.clone(),
rsc: [0u8; 8],
cipher_suite_oui: eapol::to_array(&ptk.cipher.oui[..]),
cipher_suite_type: ptk.cipher.suite_type,
},
Key::Gtk(gtk) => fidl_mlme::SetKeyDescriptor {
key: gtk.tk().to_vec(),
key_id: gtk.key_id() as u16,
key_type: fidl_mlme::KeyType::Group,
address: [0xFFu8; 6],
rsc: [0u8; 8],
cipher_suite_oui: eapol::to_array(&gtk.cipher.oui[..]),
cipher_suite_type: gtk.cipher.suite_type,
},
_ => {
error!("unsupported key type in UpdateSink");
return;
}
};
ctx.mlme_sink.send(MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest {
keylist: vec![set_key_descriptor],
}));
}
}
fn triggered(id: &Option<EventId>, received_id: EventId) -> bool {
id.map_or(false, |id| id == received_id)
}
fn cancel(event_id: &mut Option<EventId>) {
let _ = event_id.take();
}
#[derive(Default)]
pub struct Map {
clients: HashMap<MacAddr, RemoteClient>,
aid_map: aid::Map,
}
impl Map {
pub fn add_client(
&mut self,
addr: MacAddr,
authenticator: Option<Box<Authenticator>>,
) -> Result<AssociationId, failure::Error> {
ensure!(self.get_client(&addr).is_none(), "client already exists in map");
let aid = self.aid_map.assign_aid()?;
let remote_client = RemoteClient::new(addr, aid, authenticator);
self.clients.insert(addr, remote_client);
Ok(aid)
}
pub fn get_client(&self, addr: &MacAddr) -> Option<&RemoteClient> {
self.clients.get(addr)
}
pub fn get_mut_client(&mut self, addr: &MacAddr) -> Option<&mut RemoteClient> {
self.clients.get_mut(addr)
}
pub fn remove_client(&mut self, addr: &MacAddr) -> Option<RemoteClient> {
let remote_client = self.clients.remove(addr);
remote_client.as_ref().map(|rc| self.aid_map.release_aid(rc.aid));
remote_client
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ap::{event::Event, test_utils::MockAuthenticator, TimeStream},
sink::MlmeSink,
test_utils, timer, DeviceInfo, MacAddr, MlmeStream,
};
use {
futures::channel::mpsc,
std::{
error::Error,
sync::{Arc, Mutex},
},
};
const AP_ADDR: MacAddr = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
const CLIENT_ADDR: MacAddr = [0x01, 0x07, 0x77, 0x77, 0x70, 0x10];
const AID: AssociationId = 1;
#[test]
fn test_remote_client_key_handshake() {
let (mut remote_client, mock_auth) = remote_client();
let (mut ctx, mut mlme_stream, _) = make_env();
// Return EAPOL key frame when Authenticator is initiated.
let update = SecAssocUpdate::TxEapolKeyFrame(test_utils::eapol_key_frame());
mock_auth.set_initiate_results(vec![update]);
remote_client.initiate_key_exchange(&mut ctx, 1);
// Verify that remote client sent out an EAPOL.request for the EAPOL frame returned by the
// Authenticator
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::Eapol(eapol_req) => {
assert_eq!(eapol_req.src_addr, AP_ADDR);
assert_eq!(eapol_req.dst_addr, CLIENT_ADDR);
assert_eq!(eapol_req.data, test_utils::eapol_key_frame_bytes());
}
_ => panic!("expect eapol response sent to MLME"),
}
// On handling EAPOL indication, authenticator derives some keys and signal that it's done
let ptk_update = SecAssocUpdate::Key(Key::Ptk(test_utils::ptk()));
let gtk_update = SecAssocUpdate::Key(Key::Gtk(test_utils::gtk()));
let done_update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
mock_auth.set_on_eapol_frame_results(vec![ptk_update, gtk_update, done_update]);
let eapol_ind = fidl_mlme::EapolIndication {
src_addr: CLIENT_ADDR,
dst_addr: AP_ADDR,
data: test_utils::eapol_key_frame_bytes(),
};
remote_client
.handle_eapol_ind(eapol_ind, &mut ctx)
.expect("expect handle_eapol_ind to succeed");
// Verify that remote client then send out request to set those keys
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetKeys(set_keys_req) => {
assert_eq!(set_keys_req.keylist.len(), 1);
let k = set_keys_req.keylist.get(0).expect("expect key descriptor");
assert_eq!(k.key, vec![0xCCu8; test_utils::cipher().tk_bytes().unwrap()]);
assert_eq!(k.key_id, 0);
assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
assert_eq!(k.address, CLIENT_ADDR);
assert_eq!(k.rsc, [0u8; 8]);
assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
assert_eq!(k.cipher_suite_type, 4);
}
_ => panic!("expect set keys req to MLME"),
}
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetKeys(set_keys_req) => {
assert_eq!(set_keys_req.keylist.len(), 1);
let k = set_keys_req.keylist.get(0).expect("expect key descriptor");
assert_eq!(k.key, test_utils::gtk_bytes());
assert_eq!(k.key_id, 2);
assert_eq!(k.key_type, fidl_mlme::KeyType::Group);
assert_eq!(k.address, [0xFFu8; 6]);
assert_eq!(k.rsc, [0u8; 8]);
assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
assert_eq!(k.cipher_suite_type, 4);
}
_ => panic!("expect set keys req to MLME"),
}
// Verify that remote client tells MLME to open controlled port
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetCtrlPort(set_ctrl_port_req) => {
assert_eq!(set_ctrl_port_req.peer_sta_address, CLIENT_ADDR);
assert_eq!(set_ctrl_port_req.state, fidl_mlme::ControlledPortState::Open);
}
_ => panic!("expect set ctrl port req to MLME"),
}
}
#[test]
fn test_remote_client_key_handshake_timeout() {
let (mut remote_client, mock_auth) = remote_client();
let (mut ctx, mut mlme_stream, mut time_stream) = make_env();
let update = SecAssocUpdate::TxEapolKeyFrame(test_utils::eapol_key_frame());
mock_auth.set_initiate_results(vec![update]);
remote_client.initiate_key_exchange(&mut ctx, 1);
for i in 1..=4 {
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::Eapol(eapol_req) => {
assert_eq!(eapol_req.src_addr, AP_ADDR);
assert_eq!(eapol_req.dst_addr, CLIENT_ADDR);
assert_eq!(eapol_req.data, test_utils::eapol_key_frame_bytes());
}
_ => panic!("expect eapol response sent to MLME - attempt {}", i),
}
// Verify timed event was scheduled and use it to trigger timeout
let (_, timed_event) = time_stream.try_next().unwrap().expect("expect timed event");
match timed_event.event {
Event::Client { addr, event } => {
assert_eq!(addr, CLIENT_ADDR);
let update = SecAssocUpdate::TxEapolKeyFrame(test_utils::eapol_key_frame());
mock_auth.set_initiate_results(vec![update]);
remote_client.handle_timeout(timed_event.id, event, &mut ctx);
}
_ => panic!("expect client timed event"),
}
}
// On the 4th timeout, remote client sends out a deauth request instead
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::Deauthenticate(deauth_req) => {
assert_eq!(deauth_req.peer_sta_address, CLIENT_ADDR);
assert_eq!(deauth_req.reason_code, fidl_mlme::ReasonCode::FourwayHandshakeTimeout);
}
_ => panic!("expect deauth req to MLME"),
}
}
#[test]
fn test_remote_client_ignore_timeout_if_key_handshake_succeeds() {
let (mut remote_client, mock_auth) = remote_client();
let (mut ctx, mut mlme_stream, mut time_stream) = make_env();
let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
mock_auth.set_initiate_results(vec![update]);
remote_client.initiate_key_exchange(&mut ctx, 1);
// Clear out the SetCtrlPort request
match mlme_stream.try_next().unwrap().expect("expect mlme message") {
MlmeRequest::SetCtrlPort(..) => (), // expected path
_ => panic!("expect set ctrl port req to MLME"),
}
// Verify timed event was scheduled and use it to try to trigger timeout
let (_, timed_event) = time_stream.try_next().unwrap().expect("expect timed event");
match timed_event.event {
Event::Client { event, .. } => {
let update = SecAssocUpdate::TxEapolKeyFrame(test_utils::eapol_key_frame());
mock_auth.set_initiate_results(vec![update]);
remote_client.handle_timeout(timed_event.id, event, &mut ctx);
}
_ => panic!("expect client timed event"),
}
// Since EssSa was already established, timeout did not trigger.
match mlme_stream.try_next() {
Err(e) => assert_eq!(e.description(), "receiver channel is empty"),
_ => panic!("unexpected event in mlme stream"),
}
}
#[test]
fn test_remote_client_handle_eapol_ind_invalid() {
let (mut remote_client, _) = remote_client();
let (mut ctx, _, _) = make_env();
let eapol_ind = fidl_mlme::EapolIndication {
src_addr: CLIENT_ADDR,
dst_addr: AP_ADDR,
data: vec![0x66u8, 20],
};
match remote_client.handle_eapol_ind(eapol_ind, &mut ctx) {
Err(e) => assert_eq!(format!("{}", e), "error parsing EAPoL key frame"),
_ => panic!("expect handle_eapol_ind to fail"),
}
}
#[test]
fn test_remote_client_map() {
let mut client_map: Map = Default::default();
let client_addr1 = addr(1);
let client_addr2 = addr(2);
let client_addr3 = addr(3);
assert_eq!(add_client(&mut client_map, client_addr1).unwrap(), 1);
assert_eq!(client_map.get_client(&client_addr1).unwrap().aid, 1);
assert_eq!(add_client(&mut client_map, client_addr2).unwrap(), 2);
client_map.remove_client(&client_addr1);
assert_eq!(add_client(&mut client_map, client_addr3).unwrap(), 1);
}
#[test]
fn test_add_client_multiple_times() {
let mut client_map: Map = Default::default();
assert!(add_client(&mut client_map, addr(1)).is_ok());
let result = add_client(&mut client_map, addr(1));
assert!(result.is_err());
assert_eq!(format!("{}", result.unwrap_err()), "client already exists in map");
}
fn add_client(client_map: &mut Map, addr: MacAddr) -> Result<AssociationId, failure::Error> {
client_map.add_client(addr, None)
}
fn addr(id: u32) -> MacAddr {
// impl doesn't matter, just need a unique address for each id for our test
use std::mem;
let mac_addr: [u8; 4] = unsafe { mem::transmute(id) };
[mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], 0, 0]
}
fn remote_client() -> (RemoteClient, MockAuthenticatorController) {
let mock_initiate = Arc::new(Mutex::new(UpdateSink::default()));
let mock_on_eapol_frame = Arc::new(Mutex::new(UpdateSink::default()));
let authenticator =
MockAuthenticator::new(mock_initiate.clone(), mock_on_eapol_frame.clone());
let remote_client = RemoteClient::new(CLIENT_ADDR, AID, Some(Box::new(authenticator)));
(remote_client, MockAuthenticatorController { mock_initiate, mock_on_eapol_frame })
}
fn make_env() -> (Context, MlmeStream, TimeStream) {
let device_info = DeviceInfo { addr: AP_ADDR, bands: vec![] };
let (mlme_sink, mlme_stream) = mpsc::unbounded();
let (timer, time_stream) = timer::create_timer();
let ctx = Context { device_info, mlme_sink: MlmeSink::new(mlme_sink), timer };
(ctx, mlme_stream, time_stream)
}
struct MockAuthenticatorController {
mock_initiate: Arc<Mutex<UpdateSink>>,
mock_on_eapol_frame: Arc<Mutex<UpdateSink>>,
}
impl MockAuthenticatorController {
fn set_initiate_results(&self, updates: UpdateSink) {
*self.mock_initiate.lock().unwrap() = updates;
}
fn set_on_eapol_frame_results(&self, updates: UpdateSink) {
*self.mock_on_eapol_frame.lock().unwrap() = updates;
}
}
}