| // 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 crate::auth; |
| use crate::key::exchange::{ |
| self, |
| handshake::{fourway::Fourway, group_key::GroupKey}, |
| Key, |
| }; |
| use crate::key::{gtk::Gtk, ptk::Ptk}; |
| use crate::rsna::{ |
| NegotiatedRsne, Role, SecAssocStatus, SecAssocUpdate, UpdateSink, VerifiedKeyFrame, |
| }; |
| use crate::state_machine::StateMachine; |
| use crate::Error; |
| use eapol; |
| use failure::{self, bail}; |
| use log::{error, info}; |
| |
| #[derive(Debug, PartialEq)] |
| enum Pmksa { |
| Initialized { method: auth::Method }, |
| Established { pmk: Vec<u8>, method: auth::Method }, |
| } |
| |
| impl Pmksa { |
| fn reset(self) -> Self { |
| match self { |
| Pmksa::Established { method, .. } | Pmksa::Initialized { method, .. } => { |
| Pmksa::Initialized { method } |
| } |
| } |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| enum Ptksa { |
| Uninitialized { cfg: exchange::Config }, |
| Initialized { method: exchange::Method }, |
| Established { method: exchange::Method, ptk: Ptk }, |
| } |
| |
| impl Ptksa { |
| fn initialize(self, pmk: Vec<u8>) -> Self { |
| match self { |
| Ptksa::Uninitialized { cfg } => match cfg { |
| exchange::Config::FourWayHandshake(method_cfg) => { |
| match Fourway::new(method_cfg.clone(), pmk) { |
| Err(e) => { |
| error!("error creating 4-Way Handshake from config: {}", e); |
| Ptksa::Uninitialized { |
| cfg: exchange::Config::FourWayHandshake(method_cfg), |
| } |
| } |
| Ok(method) => Ptksa::Initialized { |
| method: exchange::Method::FourWayHandshake(method), |
| }, |
| } |
| } |
| _ => { |
| panic!("unsupported method for PTKSA: {:?}", cfg); |
| } |
| }, |
| other => other, |
| } |
| } |
| |
| fn reset(self) -> Self { |
| match self { |
| Ptksa::Uninitialized { cfg } => Ptksa::Uninitialized { cfg }, |
| Ptksa::Initialized { method } | Ptksa::Established { method, .. } => { |
| Ptksa::Uninitialized { cfg: method.destroy() } |
| } |
| } |
| } |
| } |
| |
| /// A GTKSA is composed of a GTK and a key exchange method. |
| /// While a key is required for successfully establishing a GTKSA, the key exchange method is |
| /// optional as it's used only for re-keying the GTK. |
| #[derive(Debug, PartialEq)] |
| enum Gtksa { |
| Uninitialized { cfg: Option<exchange::Config> }, |
| Initialized { method: Option<exchange::Method> }, |
| Established { method: Option<exchange::Method>, gtk: Gtk }, |
| } |
| |
| impl Gtksa { |
| fn initialize(self, kck: &[u8], kek: &[u8]) -> Self { |
| match self { |
| Gtksa::Uninitialized { cfg } => match cfg { |
| None => Gtksa::Initialized { method: None }, |
| Some(exchange::Config::GroupKeyHandshake(method_cfg)) => { |
| match GroupKey::new(method_cfg.clone(), kck, kek) { |
| Err(e) => { |
| error!("error creating Group KeyHandshake from config: {}", e); |
| Gtksa::Uninitialized { |
| cfg: Some(exchange::Config::GroupKeyHandshake(method_cfg)), |
| } |
| } |
| Ok(method) => Gtksa::Initialized { |
| method: Some(exchange::Method::GroupKeyHandshake(method)), |
| }, |
| } |
| } |
| _ => { |
| panic!("unsupported method for GTKSA: {:?}", cfg); |
| } |
| }, |
| other => other, |
| } |
| } |
| |
| fn reset(self) -> Self { |
| match self { |
| Gtksa::Uninitialized { cfg } => Gtksa::Uninitialized { cfg }, |
| Gtksa::Initialized { method } | Gtksa::Established { method, .. } => { |
| Gtksa::Uninitialized { cfg: method.map(|m| m.destroy()) } |
| } |
| } |
| } |
| } |
| |
| /// An ESS Security Association is composed of three security associations, namely, PMKSA, PTKSA and |
| /// GTKSA. The individual security associations have dependencies on each other. For example, the |
| /// PMKSA must be established first as it yields the PMK used in the PTK and GTK key hierarchy. |
| /// Depending on the selected PTKSA, it can yield not just the PTK but also GTK, and thus leaving |
| /// the GTKSA's key exchange method only useful for re-keying. |
| #[derive(Debug, PartialEq)] |
| pub(crate) struct EssSa { |
| // Configuration. |
| role: Role, |
| pub negotiated_rsne: NegotiatedRsne, |
| key_replay_counter: u64, |
| |
| // Security associations. |
| pmksa: StateMachine<Pmksa>, |
| ptksa: StateMachine<Ptksa>, |
| gtksa: StateMachine<Gtksa>, |
| } |
| |
| // IEEE Std 802.11-2016, 12.6.1.3.2 |
| impl EssSa { |
| pub fn new( |
| role: Role, |
| negotiated_rsne: NegotiatedRsne, |
| auth_cfg: auth::Config, |
| ptk_exch_cfg: exchange::Config, |
| gtk_exch_cfg: Option<exchange::Config>, |
| ) -> Result<EssSa, failure::Error> { |
| info!("spawned ESSSA for: {:?}", role); |
| |
| let auth_method = auth::Method::from_config(auth_cfg)?; |
| let rsna = EssSa { |
| role, |
| negotiated_rsne, |
| key_replay_counter: 0, |
| pmksa: StateMachine::new(Pmksa::Initialized { method: auth_method }), |
| ptksa: StateMachine::new(Ptksa::Uninitialized { cfg: ptk_exch_cfg }), |
| gtksa: StateMachine::new(Gtksa::Uninitialized { cfg: gtk_exch_cfg }), |
| }; |
| Ok(rsna) |
| } |
| |
| pub fn initiate(&mut self, update_sink: &mut UpdateSink) -> Result<(), failure::Error> { |
| self.reset(); |
| info!("establishing ESSSA..."); |
| |
| // PSK allows deriving the PMK without exchanging |
| let pmk = match &self.pmksa.state() { |
| Pmksa::Initialized { method } => match method { |
| auth::Method::Psk(psk) => psk.to_vec(), |
| }, |
| _ => bail!("cannot initiate PMK more than once"), |
| }; |
| self.on_key_confirmed(update_sink, Key::Pmk(pmk))?; |
| |
| // TODO(hahnr): Support 802.1X authentication if STA is Authenticator and authentication |
| // method is not PSK. |
| |
| Ok(()) |
| } |
| |
| pub fn reset(&mut self) { |
| info!("resetting ESSSA"); |
| self.pmksa.replace_state(|state| state.reset()); |
| self.ptksa.replace_state(|state| state.reset()); |
| self.gtksa.replace_state(|state| state.reset()); |
| } |
| |
| fn is_established(&self) -> bool { |
| match (self.ptksa.state(), self.gtksa.state()) { |
| (Ptksa::Established { .. }, Gtksa::Established { .. }) => true, |
| _ => false, |
| } |
| } |
| |
| fn on_key_confirmed( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| key: Key, |
| ) -> Result<(), failure::Error> { |
| match key { |
| Key::Pmk(pmk) => { |
| self.pmksa.replace_state(|state| match state { |
| Pmksa::Initialized { method } => { |
| info!("established PMKSA"); |
| Pmksa::Established { method, pmk: pmk.clone() } |
| } |
| other => { |
| error!("received PMK with PMK already being established"); |
| other |
| } |
| }); |
| |
| self.ptksa.replace_state(|state| state.initialize(pmk)); |
| if let Ptksa::Initialized { method } = self.ptksa.mut_state() { |
| method.initiate(update_sink, self.key_replay_counter)?; |
| } else { |
| bail!("PTKSA not initialized"); |
| } |
| } |
| Key::Ptk(ptk) => { |
| // The PTK carries KEK and KCK which is used in the Group Key Handshake, thus, |
| // reset GTKSA whenever the PTK changed. |
| self.gtksa.replace_state(|state| state.reset().initialize(ptk.kck(), ptk.kek())); |
| |
| self.ptksa.replace_state(|state| match state { |
| Ptksa::Initialized { method } => { |
| info!("established PTKSA"); |
| Ptksa::Established { method, ptk } |
| } |
| Ptksa::Established { method, .. } => { |
| // PTK was already initialized. |
| info!("re-established new PTKSA; invalidating previous one"); |
| info!("(this is likely a result of using a wrong password)"); |
| Ptksa::Established { method, ptk } |
| } |
| other @ Ptksa::Uninitialized { .. } => { |
| error!("received PTK in unexpected PTKSA state"); |
| other |
| } |
| }); |
| } |
| Key::Gtk(gtk) => { |
| self.gtksa.replace_state(|state| match state { |
| Gtksa::Initialized { method } => { |
| info!("established GTKSA"); |
| Gtksa::Established { method, gtk } |
| } |
| Gtksa::Established { method, .. } => { |
| info!("re-established new GTKSA; invalidating previous one"); |
| Gtksa::Established { method, gtk } |
| } |
| Gtksa::Uninitialized { cfg } => { |
| error!("received GTK in unexpected GTKSA state"); |
| Gtksa::Uninitialized { cfg } |
| } |
| }); |
| } |
| _ => {} |
| }; |
| Ok(()) |
| } |
| |
| pub fn on_eapol_frame( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| frame: &eapol::Frame, |
| ) -> Result<(), failure::Error> { |
| // Only processes EAPOL Key frames. Drop all other frames silently. |
| let mut updates = match frame { |
| eapol::Frame::Key(key_frame) => { |
| let mut updates = UpdateSink::default(); |
| self.on_eapol_key_frame(&mut updates, &key_frame)?; |
| |
| // Authenticator updates its key replay counter with every outbound EAPOL frame. |
| if let Role::Authenticator = self.role { |
| for update in &updates { |
| if let SecAssocUpdate::TxEapolKeyFrame(frame) = update { |
| if frame.key_replay_counter <= self.key_replay_counter { |
| error!("tx EAPOL Key frame uses invalid key replay counter: {:?} ({:?})", |
| frame.key_replay_counter, |
| self.key_replay_counter); |
| } |
| self.key_replay_counter = frame.key_replay_counter; |
| } |
| } |
| } |
| |
| updates |
| } |
| }; |
| |
| // Process Key updates ourselves to correctly track security associations. |
| // If ESS-SA was not already established, wait with reporting PTK until GTK |
| // is also known. |
| let was_esssa_established = self.is_established(); |
| updates |
| .drain_filter(|update| match update { |
| SecAssocUpdate::Key(_) => true, |
| _ => false, |
| }) |
| .for_each(|update| { |
| if let SecAssocUpdate::Key(key) = update { |
| if let Err(e) = self.on_key_confirmed(update_sink, key.clone()) { |
| error!("error while processing key: {}", e); |
| }; |
| } |
| }); |
| update_sink.append(&mut updates); |
| |
| // Report keys once an ESSSA is established. |
| let state = (self.ptksa.state(), self.gtksa.state()); |
| if let (Ptksa::Established { ptk, .. }, Gtksa::Established { gtk, .. }) = state { |
| if !was_esssa_established { |
| info!("established ESSSA"); |
| update_sink.push(SecAssocUpdate::Key(Key::Ptk(ptk.clone()))); |
| update_sink.push(SecAssocUpdate::Key(Key::Gtk(gtk.clone()))); |
| update_sink.push(SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished)); |
| } else { |
| info!("rekey'ed some keys of an established ESSSA"); |
| update_sink.push(SecAssocUpdate::Key(Key::Ptk(ptk.clone()))); |
| update_sink.push(SecAssocUpdate::Key(Key::Gtk(gtk.clone()))); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn on_eapol_key_frame( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| frame: &eapol::KeyFrame, |
| ) -> Result<(), failure::Error> { |
| // Verify the frame complies with IEEE Std 802.11-2016, 12.7.2. |
| let result = VerifiedKeyFrame::from_key_frame( |
| frame, |
| &self.role, |
| &self.negotiated_rsne, |
| self.key_replay_counter, |
| ); |
| // TODO(hahnr): The status should not be pushed as an update but isntead as a Result. |
| let verified_frame = match result { |
| Err(e) => match e.as_fail().downcast_ref::<Error>() { |
| Some(Error::WrongAesKeywrapKey) => { |
| update_sink.push(SecAssocUpdate::Status(SecAssocStatus::WrongPassword)); |
| return Ok(()); |
| } |
| _ => bail!(e), |
| }, |
| other => other, |
| }?; |
| |
| // IEEE Std 802.11-2016, 12.7.2, d) |
| // Update key replay counter if MIC was set and is valid. Only applicable for Supplicant. |
| // TODO(hahnr): We should verify the MIC here and only increase the counter if the MIC |
| // is valid. |
| if frame.key_info.key_mic() { |
| if let Role::Supplicant = self.role { |
| self.key_replay_counter = frame.key_replay_counter; |
| } |
| } |
| |
| // Forward frame to correct security association. |
| // PMKSA must be established before any other security association can be established. |
| match self.pmksa.mut_state() { |
| Pmksa::Initialized { method } => { |
| return method.on_eapol_key_frame(update_sink, verified_frame); |
| } |
| Pmksa::Established { .. } => {} |
| }; |
| |
| // Once PMKSA was established PTKSA and GTKSA can process frames. |
| // IEEE Std 802.11-2016, 12.7.2 b.2) |
| if frame.key_info.key_type() == eapol::KEY_TYPE_PAIRWISE { |
| match self.ptksa.mut_state() { |
| Ptksa::Uninitialized { .. } => Ok(()), |
| Ptksa::Initialized { method } | Ptksa::Established { method, .. } => { |
| method.on_eapol_key_frame(update_sink, self.key_replay_counter, verified_frame) |
| } |
| } |
| } else if frame.key_info.key_type() == eapol::KEY_TYPE_GROUP_SMK { |
| match self.gtksa.mut_state() { |
| Gtksa::Uninitialized { .. } => Ok(()), |
| Gtksa::Initialized { method } | Gtksa::Established { method, .. } => match method { |
| Some(method) => method.on_eapol_key_frame( |
| update_sink, |
| self.key_replay_counter, |
| verified_frame, |
| ), |
| None => { |
| error!("received group key EAPOL Key frame with GTK re-keying disabled"); |
| Ok(()) |
| } |
| }, |
| } |
| } else { |
| error!("unsupported EAPOL Key frame key type: {:?}", frame.key_info.key_type()); |
| Ok(()) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::rsna::test_util; |
| use crate::Supplicant; |
| |
| const ANONCE: [u8; 32] = [0x1A; 32]; |
| const GTK: [u8; 16] = [0x1B; 16]; |
| const GTK_REKEY: [u8; 16] = [0x1F; 16]; |
| |
| #[test] |
| fn test_supplicant_with_authenticator() { |
| let mut supplicant = test_util::get_supplicant(); |
| let mut authenticator = test_util::get_authenticator(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Initiate Authenticator. |
| let mut a_updates = vec![]; |
| let result = authenticator.initiate(&mut a_updates); |
| assert!(result.is_ok(), "Authenticator failed initiating: {}", result.unwrap_err()); |
| assert_eq!(a_updates.len(), 1); |
| let msg1 = extract_eapol_resp(&a_updates).expect("Authenticator did not send msg #1"); |
| |
| // Send msg #1 to Supplicant and wait for response. |
| let mut s_updates = vec![]; |
| let result = supplicant.on_eapol_frame(&mut s_updates, &eapol::Frame::Key(msg1.clone())); |
| assert!(result.is_ok(), "Supplicant failed processing msg #1: {}", result.unwrap_err()); |
| assert_eq!(s_updates.len(), 1); |
| let msg2 = extract_eapol_resp(&s_updates).expect("Supplicant did not send msg #2"); |
| |
| // Send msg #2 to Authenticator and wait for response. |
| let mut a_updates = vec![]; |
| let result = authenticator.on_eapol_frame(&mut a_updates, &eapol::Frame::Key(msg2.clone())); |
| assert!(result.is_ok(), "Authenticator failed processing msg #2: {}", result.unwrap_err()); |
| assert_eq!(a_updates.len(), 1); |
| let msg3 = extract_eapol_resp(&a_updates).expect("Authenticator did not send msg #3"); |
| |
| // Send msg #3 to Supplicant and wait for response. |
| let mut s_updates = vec![]; |
| let result = supplicant.on_eapol_frame(&mut s_updates, &eapol::Frame::Key(msg3.clone())); |
| assert!(result.is_ok(), "Supplicant failed processing msg #3: {}", result.unwrap_err()); |
| assert_eq!(s_updates.len(), 4); |
| let msg4 = extract_eapol_resp(&s_updates).expect("Supplicant did not send msg #4"); |
| let s_ptk = extract_reported_ptk(&s_updates).expect("Supplicant did not send PTK"); |
| let s_gtk = extract_reported_gtk(&s_updates).expect("Supplicant did not send GTK"); |
| let s_status = extract_reported_status(&s_updates).expect("Supplicant did not send status"); |
| |
| // Send msg #4 to Authenticator. |
| let mut a_updates = vec![]; |
| let result = authenticator.on_eapol_frame(&mut a_updates, &eapol::Frame::Key(msg4.clone())); |
| assert!(result.is_ok(), "Authenticator failed processing msg #4: {}", result.unwrap_err()); |
| assert_eq!(a_updates.len(), 3); |
| let a_ptk = extract_reported_ptk(&a_updates).expect("Authenticator did not send PTK"); |
| let a_gtk = extract_reported_gtk(&a_updates).expect("Authenticator did not send GTK"); |
| let a_status = |
| extract_reported_status(&a_updates).expect("Authenticator did not send status"); |
| |
| // Verify derived keys match and status reports ESS-SA as established. |
| assert_eq!(a_ptk, s_ptk); |
| assert_eq!(a_gtk, s_gtk); |
| match (a_status, s_status) { |
| (SecAssocStatus::EssSaEstablished, SecAssocStatus::EssSaEstablished) => {} |
| (a, s) => panic!("Invalid status; Authenticator: {:?}, Supplicant: {:?}", a, s), |
| }; |
| } |
| #[test] |
| fn test_replay_first_message() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send first message of handshake. |
| let (result, _) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 1; |
| }); |
| assert!(result.is_ok()); |
| |
| // Replay first message which should restart the entire handshake. |
| // Verify the second message of the handshake was received. |
| let (result, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 3; |
| }); |
| assert!(result.is_ok()); |
| |
| // Extract second message response and verify Supplicant responded to the replayed first |
| // message. |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| assert_eq!(msg2.key_replay_counter, 3); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_msg1() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 0; |
| }); |
| assert!(result.is_ok()); |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| let ptk = derive_ptk(msg2); |
| |
| let (result, _) = send_msg3(&mut supplicant, &ptk, |_| {}); |
| assert!(result.is_ok()); |
| } |
| |
| #[test] |
| fn test_nonzero_key_replay_counter_msg1() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, _) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 1; |
| }); |
| assert!(result.is_ok()); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_lower_msg3_counter() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 1; |
| }); |
| assert!(result.is_ok()); |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| let ptk = derive_ptk(msg2); |
| |
| let (result, _) = send_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_replay_counter = 0; |
| }); |
| assert!(result.is_ok()); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_valid_msg3() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 0; |
| }); |
| assert!(result.is_ok()); |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| let ptk = derive_ptk(msg2); |
| |
| let (result, _) = send_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_replay_counter = 1; |
| }); |
| assert!(result.is_ok()); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_replayed_msg3() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 0; |
| }); |
| assert!(result.is_ok()); |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| let ptk = derive_ptk(msg2); |
| |
| let (result, _) = send_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_replay_counter = 2; |
| }); |
| assert!(result.is_ok()); |
| |
| // The just sent third message increased the key replay counter. |
| // All successive EAPOL frames are required to have a larger key replay counter. |
| |
| // Send an invalid message. |
| let (result, _) = send_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_replay_counter = 2; |
| }); |
| assert!(result.is_err()); |
| |
| // Send a valid message. |
| let (result, _) = send_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_replay_counter = 3; |
| }); |
| assert!(result.is_ok()); |
| } |
| |
| // Replays the first message of the 4-Way Handshake to verify that |
| // (1) the Supplicant discards the first derived PTK in favor of a new one, and |
| // (2) the Supplicant is not reusing a nonce from its previous message, |
| // (3) the Supplicant only reports a new PTK if the 4-Way Handshake was completed successfully. |
| #[test] |
| fn test_replayed_msg1_ptk_installation() { |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send 1st message of 4-Way Handshake for the first time and derive PTK. |
| let (_, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 1; |
| }); |
| assert_eq!(extract_reported_ptk(&updates[..]), None); |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| let first_ptk = derive_ptk(msg2); |
| let first_nonce = msg2.key_nonce; |
| |
| // Send 1st message of 4-Way Handshake a second time and derive PTK. |
| let (_, updates) = send_msg1(&mut supplicant, |msg1| { |
| msg1.key_replay_counter = 2; |
| }); |
| assert_eq!(extract_reported_ptk(&updates[..]), None); |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 2nd message"); |
| let second_ptk = derive_ptk(msg2); |
| let second_nonce = msg2.key_nonce; |
| |
| // Send 3rd message of 4-Way Handshake. |
| // The Supplicant now finished the 4-Way Handshake and should report its PTK. |
| let (_, updates) = send_msg3(&mut supplicant, &second_ptk, |msg3| { |
| msg3.key_replay_counter = 3; |
| }); |
| let installed_ptk = |
| extract_reported_ptk(&updates[..]).expect("Supplicant did not report PTK"); |
| |
| assert_ne!(first_nonce, second_nonce); |
| assert_ne!(&first_ptk, &second_ptk); |
| assert_eq!(installed_ptk, &second_ptk); |
| } |
| |
| // Integration test for WPA2 CCMP-128 PSK with a Supplicant role. |
| #[test] |
| fn test_supplicant_wpa2_ccmp128_psk() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send first message |
| let (result, updates) = send_msg1(&mut supplicant, |_| {}); |
| assert!(result.is_ok()); |
| |
| // Verify 2nd message. |
| let msg2 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 3nd message"); |
| let s_rsne = test_util::get_s_rsne(); |
| let s_rsne_data = test_util::get_rsne_bytes(&s_rsne); |
| assert_eq!(msg2.version, 1); |
| assert_eq!(msg2.packet_type, 3); |
| assert_eq!(msg2.packet_body_len as usize, msg2.len() - 4); |
| assert_eq!(msg2.descriptor_type, 2); |
| assert_eq!(msg2.key_info.value(), 0x010A); |
| assert_eq!(msg2.key_len, 0); |
| assert_eq!(msg2.key_replay_counter, 1); |
| assert!(!test_util::is_zero(&msg2.key_nonce[..])); |
| assert!(test_util::is_zero(&msg2.key_iv[..])); |
| assert_eq!(msg2.key_rsc, 0); |
| assert!(!test_util::is_zero(&msg2.key_mic[..])); |
| assert_eq!(msg2.key_mic.len(), test_util::mic_len()); |
| assert_eq!(msg2.key_data.len(), msg2.key_data_len as usize); |
| assert_eq!(msg2.key_data.len(), s_rsne_data.len()); |
| assert_eq!(&msg2.key_data[..], &s_rsne_data[..]); |
| |
| // Send 3rd message. |
| let ptk = derive_ptk(msg2); |
| let (result, updates) = send_msg3(&mut supplicant, &ptk, |_| {}); |
| assert!(result.is_ok()); |
| |
| // Verify 4th message was received and is correct. |
| let msg4 = |
| extract_eapol_resp(&updates[..]).expect("Supplicant did not respond with 4th message"); |
| assert_eq!(msg4.version, 1); |
| assert_eq!(msg4.packet_type, 3); |
| assert_eq!(msg4.packet_body_len as usize, msg4.len() - 4); |
| assert_eq!(msg4.descriptor_type, 2); |
| assert_eq!(msg4.key_info.value(), 0x030A); |
| assert_eq!(msg4.key_len, 0); |
| assert_eq!(msg4.key_replay_counter, 2); |
| assert!(test_util::is_zero(&msg4.key_nonce[..])); |
| assert!(test_util::is_zero(&msg4.key_iv[..])); |
| assert_eq!(msg4.key_rsc, 0); |
| assert!(!test_util::is_zero(&msg4.key_mic[..])); |
| assert_eq!(msg4.key_mic.len(), test_util::mic_len()); |
| assert_eq!(msg4.key_data.len(), 0); |
| assert!(test_util::is_zero(&msg4.key_data[..])); |
| // Verify the message's MIC. |
| let mic = test_util::compute_mic(ptk.kck(), &msg4); |
| assert_eq!(&msg4.key_mic[..], &mic[..]); |
| |
| // Verify PTK was reported. |
| let reported_ptk = |
| extract_reported_ptk(&updates[..]).expect("Supplicant did not report PTK"); |
| assert_eq!(ptk.ptk, reported_ptk.ptk); |
| |
| // Verify GTK was reported. |
| let reported_gtk = |
| extract_reported_gtk(&updates[..]).expect("Supplicant did not report GTK"); |
| assert_eq!(>K[..], &reported_gtk.gtk[..]); |
| |
| // Verify ESS was reported to be established. |
| let reported_status = |
| extract_reported_status(&updates[..]).expect("Supplicant did not report any status"); |
| match reported_status { |
| SecAssocStatus::EssSaEstablished => {} |
| _ => assert!(false), |
| }; |
| |
| // Cause re-keying of GTK via Group-Key Handshake. |
| |
| let (result, updates) = send_group_key_msg1(&mut supplicant, &ptk, |_| {}); |
| assert!(result.is_ok()); |
| |
| // Verify 2th message was received and is correct. |
| let msg2 = extract_eapol_resp(&updates[..]) |
| .expect("Supplicant did not respond with 2nd message of group key handshake"); |
| assert_eq!(msg2.version, 1); |
| assert_eq!(msg2.packet_type, 3); |
| assert_eq!(msg2.packet_body_len as usize, msg2.len() - 4); |
| assert_eq!(msg2.descriptor_type, 2); |
| assert_eq!(msg2.key_info.value(), 0x0302); |
| assert_eq!(msg2.key_len, 0); |
| assert_eq!(msg2.key_replay_counter, 3); |
| assert!(test_util::is_zero(&msg2.key_nonce[..])); |
| assert!(test_util::is_zero(&msg2.key_iv[..])); |
| assert_eq!(msg2.key_rsc, 0); |
| assert!(!test_util::is_zero(&msg2.key_mic[..])); |
| assert_eq!(msg2.key_mic.len(), test_util::mic_len()); |
| assert_eq!(msg2.key_data.len(), 0); |
| assert!(test_util::is_zero(&msg2.key_data[..])); |
| // Verify the message's MIC. |
| let mic = test_util::compute_mic(ptk.kck(), &msg2); |
| assert_eq!(&msg2.key_mic[..], &mic[..]); |
| |
| // Verify GTK was reported. |
| let reported_gtk = |
| extract_reported_gtk(&updates[..]).expect("Supplicant did not report re-key'ed GTK"); |
| assert_eq!(>K_REKEY[..], &reported_gtk.gtk[..]); |
| } |
| |
| // TODO(hahnr): Add additional tests to validate replay attacks, |
| // invalid messages from Authenticator, timeouts, nonce reuse, |
| // (in)-compatible protocol and RSNE versions, etc. |
| |
| fn derive_ptk(msg2: &eapol::KeyFrame) -> Ptk { |
| let snonce = msg2.key_nonce; |
| test_util::get_ptk(&ANONCE[..], &snonce[..]) |
| } |
| |
| fn extract_eapol_resp(updates: &[SecAssocUpdate]) -> Option<&eapol::KeyFrame> { |
| updates |
| .iter() |
| .filter_map(|u| match u { |
| SecAssocUpdate::TxEapolKeyFrame(resp) => Some(resp), |
| _ => None, |
| }) |
| .next() |
| } |
| |
| fn extract_reported_ptk(updates: &[SecAssocUpdate]) -> Option<&Ptk> { |
| updates |
| .iter() |
| .filter_map(|u| match u { |
| SecAssocUpdate::Key(Key::Ptk(ptk)) => Some(ptk), |
| _ => None, |
| }) |
| .next() |
| } |
| |
| fn extract_reported_gtk(updates: &[SecAssocUpdate]) -> Option<&Gtk> { |
| updates |
| .iter() |
| .filter_map(|u| match u { |
| SecAssocUpdate::Key(Key::Gtk(gtk)) => Some(gtk), |
| _ => None, |
| }) |
| .next() |
| } |
| |
| fn extract_reported_status(updates: &[SecAssocUpdate]) -> Option<&SecAssocStatus> { |
| updates |
| .iter() |
| .filter_map(|u| match u { |
| SecAssocUpdate::Status(status) => Some(status), |
| _ => None, |
| }) |
| .next() |
| } |
| |
| fn send_msg1<F>( |
| supplicant: &mut Supplicant, |
| msg_modifier: F, |
| ) -> (Result<(), failure::Error>, UpdateSink) |
| where |
| F: Fn(&mut eapol::KeyFrame), |
| { |
| let msg = test_util::get_4whs_msg1(&ANONCE[..], msg_modifier); |
| let mut update_sink = UpdateSink::default(); |
| let result = supplicant.on_eapol_frame(&mut update_sink, &eapol::Frame::Key(msg)); |
| (result, update_sink) |
| } |
| |
| fn send_msg3<F>( |
| supplicant: &mut Supplicant, |
| ptk: &Ptk, |
| msg_modifier: F, |
| ) -> (Result<(), failure::Error>, UpdateSink) |
| where |
| F: Fn(&mut eapol::KeyFrame), |
| { |
| let msg = test_util::get_4whs_msg3(ptk, &ANONCE[..], >K[..], msg_modifier); |
| let mut update_sink = UpdateSink::default(); |
| let result = supplicant.on_eapol_frame(&mut update_sink, &eapol::Frame::Key(msg)); |
| (result, update_sink) |
| } |
| |
| fn send_group_key_msg1<F>( |
| supplicant: &mut Supplicant, |
| ptk: &Ptk, |
| msg_modifier: F, |
| ) -> (Result<(), failure::Error>, UpdateSink) |
| where |
| F: Fn(&mut eapol::KeyFrame), |
| { |
| let msg = test_util::get_group_key_hs_msg1(ptk, >K_REKEY[..], msg_modifier); |
| let mut update_sink = UpdateSink::default(); |
| let result = supplicant.on_eapol_frame(&mut update_sink, &eapol::Frame::Key(msg)); |
| (result, update_sink) |
| } |
| } |