| // 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. |
| |
| use { |
| crate::{ |
| format_rsn_err, |
| key::{ |
| exchange::{ |
| self, |
| handshake::{fourway::Fourway, group_key::GroupKey}, |
| Key, |
| }, |
| gtk::Gtk, |
| igtk::Igtk, |
| ptk::Ptk, |
| }, |
| rsna::{ |
| Dot11VerifiedKeyFrame, NegotiatedProtection, Role, SecAssocStatus, SecAssocUpdate, |
| UpdateSink, |
| }, |
| Error, |
| }, |
| eapol, |
| fidl_fuchsia_wlan_mlme::EapolResultCode, |
| log::{error, info}, |
| std::collections::HashSet, |
| wlan_statemachine::StateMachine, |
| zerocopy::ByteSlice, |
| }; |
| |
| const MAX_KEY_FRAME_RETRIES: u32 = 3; |
| |
| #[derive(Debug)] |
| enum Pmksa { |
| Initialized { pmk: Option<Vec<u8>> }, |
| Established { pmk: Vec<u8> }, |
| } |
| |
| impl Pmksa { |
| fn reset(self) -> Self { |
| match self { |
| Pmksa::Established { pmk } | Pmksa::Initialized { pmk: Some(pmk) } => { |
| Pmksa::Initialized { pmk: Some(pmk) } |
| } |
| _ => Pmksa::Initialized { pmk: None }, |
| } |
| } |
| } |
| |
| #[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>, |
| // A key history of previously installed group keys. |
| // Keys which have been previously installed must never be re-installed. |
| installed_gtks: HashSet<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()) } |
| } |
| } |
| } |
| } |
| |
| /// Igtksa is super simple because there's currently no method for populating it other than an adjacent Gtksa. |
| #[derive(Debug)] |
| enum Igtksa { |
| Uninitialized, |
| Established { installed_igtks: HashSet<Igtk> }, |
| } |
| |
| impl Igtksa { |
| fn reset(self) -> Self { |
| Igtksa::Uninitialized |
| } |
| } |
| |
| /// 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. |
| /// |
| /// Each association should spawn one ESSSA instance only. |
| /// The security association correctly tracks and handles replays for robustness and |
| /// prevents key re-installation to mitigate attacks such as described in KRACK. |
| #[derive(Debug)] |
| pub(crate) struct EssSa { |
| // Determines the device's role (Supplicant or Authenticator). |
| role: Role, |
| // The protection used for this association. |
| pub negotiated_protection: NegotiatedProtection, |
| // The last valid key replay counter. Messages with a key replay counter lower than this counter |
| // value will be dropped. |
| key_replay_counter: u64, |
| // A retry counter and key frame to resend if a timeout is received while waiting for a response. |
| last_key_frame_buf: Option<(u32, eapol::KeyFrameBuf)>, |
| // Updates to send after we receive an eapol send confirmation. This will contain an empty update |
| // sink if we're awaiting a confirm but do not have any subsequent updates to send. |
| updates_awaiting_confirm: Option<UpdateSink>, |
| |
| // Individual Security Associations. |
| pmksa: StateMachine<Pmksa>, |
| ptksa: StateMachine<Ptksa>, |
| gtksa: StateMachine<Gtksa>, |
| igtksa: StateMachine<Igtksa>, |
| } |
| |
| // IEEE Std 802.11-2016, 12.6.1.3.2 |
| impl EssSa { |
| pub fn new( |
| role: Role, |
| pmk: Option<Vec<u8>>, |
| negotiated_protection: NegotiatedProtection, |
| ptk_exch_cfg: exchange::Config, |
| gtk_exch_cfg: Option<exchange::Config>, |
| ) -> Result<EssSa, anyhow::Error> { |
| info!("spawned ESSSA for: {:?}", role); |
| |
| let rsna = EssSa { |
| role, |
| negotiated_protection, |
| key_replay_counter: 0, |
| last_key_frame_buf: None, |
| updates_awaiting_confirm: None, |
| pmksa: StateMachine::new(Pmksa::Initialized { pmk }), |
| ptksa: StateMachine::new(Ptksa::Uninitialized { cfg: ptk_exch_cfg }), |
| gtksa: StateMachine::new(Gtksa::Uninitialized { cfg: gtk_exch_cfg }), |
| igtksa: StateMachine::new(Igtksa::Uninitialized), |
| }; |
| Ok(rsna) |
| } |
| |
| pub fn initiate(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> { |
| // This function will not succeed unless called on a new Esssa or one that was reset. |
| match (self.ptksa.as_ref(), self.gtksa.as_ref(), self.igtksa.as_ref()) { |
| (Ptksa::Uninitialized { .. }, Gtksa::Uninitialized { .. }, Igtksa::Uninitialized) => (), |
| // TODO (fxbug.dev/69388): The Ptksa can be in the Initialized state |
| // if the auth method was SAE and the Pmksa key was |
| // confirmed prior to initiate()'ing this Esssa. |
| (Ptksa::Initialized { .. }, Gtksa::Uninitialized { .. }, Igtksa::Uninitialized) => (), |
| _ => return Err(Error::UnexpectedEsssaInitiation), |
| }; |
| info!("establishing ESSSA..."); |
| |
| // Immediately establish the PMKSA if the key is available. The PMK may be provided |
| // during ESSSA construction, or generated by a subsequent auth handshake such as SAE. |
| let pmk = match self.pmksa.as_ref() { |
| Pmksa::Initialized { pmk: Some(pmk) } => Some(pmk.clone()), |
| _ => None, |
| }; |
| if let Some(pmk) = pmk { |
| let mut new_updates = UpdateSink::default(); |
| let result = self.on_key_confirmed(&mut new_updates, Key::Pmk(pmk.clone())); |
| self.push_updates(update_sink, new_updates); |
| result |
| } else { |
| Ok(()) |
| } |
| } |
| |
| pub fn reset_replay_counter(&mut self) { |
| info!("resetting ESSSA replay counter"); |
| self.key_replay_counter = 0; |
| } |
| |
| pub fn reset_security_associations(&mut self) { |
| info!("resetting ESSSA security associations"); |
| self.pmksa.replace_state(|state| state.reset()); |
| self.ptksa.replace_state(|state| state.reset()); |
| self.gtksa.replace_state(|state| state.reset()); |
| self.igtksa.replace_state(|state| state.reset()); |
| } |
| |
| fn is_established(&self) -> bool { |
| match (self.ptksa.as_ref(), self.gtksa.as_ref()) { |
| (Ptksa::Established { .. }, Gtksa::Established { .. }) => true, |
| _ => false, |
| } |
| } |
| |
| fn on_key_confirmed(&mut self, update_sink: &mut UpdateSink, key: Key) -> Result<(), Error> { |
| let was_esssa_established = self.is_established(); |
| match key { |
| Key::Pmk(pmk) => { |
| self.pmksa.replace_state(|state| match state { |
| Pmksa::Initialized { .. } => { |
| info!("established PMKSA"); |
| update_sink.push(SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)); |
| Pmksa::Established { 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.as_mut() { |
| method.initiate(update_sink, self.key_replay_counter)?; |
| } else { |
| return Err(format_rsn_err!("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"); |
| update_sink.push(SecAssocUpdate::Key(Key::Ptk(ptk.clone()))); |
| 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)"); |
| // Key can be re-established in two cases: |
| // 1. Message gets replayed |
| // 2. Key is being rotated |
| // Checking that ESSSA is already established eliminates the first case. |
| if was_esssa_established { |
| update_sink.push(SecAssocUpdate::Key(Key::Ptk(ptk.clone()))); |
| } |
| 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"); |
| |
| let mut installed_gtks = HashSet::default(); |
| installed_gtks.insert(gtk.clone()); |
| update_sink.push(SecAssocUpdate::Key(Key::Gtk(gtk))); |
| Gtksa::Established { method, installed_gtks } |
| } |
| Gtksa::Established { method, mut installed_gtks } => { |
| info!("re-established new GTKSA; invalidating previous one"); |
| |
| if !installed_gtks.contains(>k) { |
| installed_gtks.insert(gtk.clone()); |
| update_sink.push(SecAssocUpdate::Key(Key::Gtk(gtk))); |
| } |
| Gtksa::Established { method, installed_gtks } |
| } |
| Gtksa::Uninitialized { cfg } => { |
| error!("received GTK in unexpected GTKSA state"); |
| Gtksa::Uninitialized { cfg } |
| } |
| }); |
| } |
| Key::Igtk(igtk) => { |
| self.igtksa.replace_state(|state| match state { |
| Igtksa::Uninitialized => { |
| info!("established IGTKSA"); |
| let mut installed_igtks = HashSet::default(); |
| installed_igtks.insert(igtk.clone()); |
| update_sink.push(SecAssocUpdate::Key(Key::Igtk(igtk))); |
| Igtksa::Established { installed_igtks } |
| } |
| Igtksa::Established { mut installed_igtks } => { |
| info!("re-established new IGTKSA; invalidating previous one"); |
| |
| if !installed_igtks.contains(&igtk) { |
| installed_igtks.insert(igtk.clone()); |
| update_sink.push(SecAssocUpdate::Key(Key::Igtk(igtk))); |
| } |
| Igtksa::Established { installed_igtks } |
| } |
| }); |
| } |
| _ => {} |
| }; |
| Ok(()) |
| } |
| |
| pub fn on_pmk_available( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| pmk: Vec<u8>, |
| ) -> Result<(), Error> { |
| let mut new_updates = UpdateSink::default(); |
| let result = self.on_key_confirmed(&mut new_updates, Key::Pmk(pmk)); |
| self.push_updates(update_sink, new_updates); |
| result |
| } |
| |
| // Do any necessary final processing before passing updates to the higher layer. |
| fn push_updates(&mut self, update_sink: &mut UpdateSink, mut new_updates: UpdateSink) { |
| if let Some(updates_awaiting_confirm) = &mut self.updates_awaiting_confirm { |
| // We're still waiting on a previous eapol confirm, and are not ready |
| // to do anything with these new updates. This can happen if we receive |
| // 4-way message 3 before we've received a confirm for message 2, in which |
| // case we defer all updates. We'll go through the logic below after we |
| // receive the expected confirm. |
| updates_awaiting_confirm.append(&mut new_updates); |
| return; |
| } |
| for update in new_updates { |
| if let SecAssocUpdate::Key(_) = update { |
| // Always install keys immediately to avoid race conditions after the eapol |
| // exchange completes. This is a particular issue for WPA1, where we may miss |
| // the first PTK-encrypted frame of the group key handshake. |
| update_sink.push(update); |
| } else if let Some(updates_awaiting_confirm) = &mut self.updates_awaiting_confirm { |
| // If we've sent an eapol frame, buffer all other non-key |
| // updates until we receive a confirm. |
| updates_awaiting_confirm.push(update); |
| } else { |
| if let SecAssocUpdate::TxEapolKeyFrame { frame, expect_response } = &update { |
| if *expect_response { |
| self.last_key_frame_buf = Some((1, frame.clone())); |
| } else { |
| // We don't expect a response, so we don't need to keep the frame around. |
| self.last_key_frame_buf = None; |
| } |
| // Subsequent non-key updates should be buffered until this frame is confirmed. |
| self.updates_awaiting_confirm.replace(Default::default()); |
| } |
| update_sink.push(update); |
| } |
| } |
| } |
| |
| pub fn on_eapol_conf( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| result: EapolResultCode, |
| ) -> Result<(), Error> { |
| match self.updates_awaiting_confirm.take() { |
| Some(updates) => match result { |
| EapolResultCode::Success => { |
| // We successfully sent a frame. Now send the resulting ESSSA updates. |
| self.push_updates(update_sink, updates); |
| Ok(()) |
| } |
| EapolResultCode::TransmissionFailure => Err(Error::KeyFrameTransmissionFailed), |
| }, |
| None => { |
| error!("Ignored unexpected eapol send confirm"); |
| Ok(()) |
| } |
| } |
| } |
| |
| pub fn on_key_frame_timeout(&mut self, update_sink: &mut UpdateSink) -> Result<(), Error> { |
| // IEEE Std 802.11-2016 6.3.22.2.4: We should always receive a confirm in response to an eapol tx. |
| // If we never received an eapol conf, treat this as a fatal error. |
| if let Some(updates) = &self.updates_awaiting_confirm { |
| return Err(Error::NoKeyFrameTransmissionConfirm(updates.len())); |
| } |
| // Resend the last key frame if appropriate |
| if let Some((attempt, key_frame)) = self.last_key_frame_buf.as_mut() { |
| *attempt += 1; |
| if *attempt > MAX_KEY_FRAME_RETRIES { |
| // Only retry a limited number of times before going idle |
| return Ok(()); |
| } |
| update_sink.push(SecAssocUpdate::TxEapolKeyFrame { |
| frame: key_frame.clone(), |
| expect_response: true, |
| }); |
| // Always expect a confirm for a keyframe retransmission. |
| self.updates_awaiting_confirm = Some(Default::default()); |
| } |
| Ok(()) |
| } |
| |
| pub fn on_establishing_rsna_timeout(&self) -> Error { |
| if let Some(updates) = &self.updates_awaiting_confirm { |
| return Error::NoKeyFrameTransmissionConfirm(updates.len()); |
| } |
| match self.ptksa.as_ref() { |
| Ptksa::Uninitialized { .. } => { |
| return Error::EapolHandshakeIncomplete("PTKSA never initialized".to_string()); |
| } |
| Ptksa::Initialized { method } | Ptksa::Established { method, .. } => { |
| if let Err(error) = method.on_establishing_rsna_timeout() { |
| return error; |
| } |
| } |
| } |
| if !matches!(self.gtksa.as_ref(), Gtksa::Established { .. }) { |
| return Error::EapolHandshakeIncomplete("GTKSA never established".to_string()); |
| } |
| |
| // Unclear how we'd get an establishing RSNA timeout without hitting any of |
| // the previous cases. |
| Error::EapolHandshakeIncomplete("Unexpected timeout while establishing RSNA".to_string()) |
| } |
| |
| pub fn on_eapol_frame<B: ByteSlice>( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| frame: eapol::Frame<B>, |
| ) -> Result<(), Error> { |
| let mut new_updates = UpdateSink::default(); |
| // Only processes EAPOL Key frames. Drop all other frames silently. |
| let updates = match frame { |
| eapol::Frame::Key(key_frame) => { |
| let mut updates = UpdateSink::default(); |
| self.on_eapol_key_frame(&mut updates, key_frame)?; |
| // We've received a new key frame, so don't retransmit our last one. |
| self.last_key_frame_buf.take(); |
| |
| // 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 { |
| let krc = |
| frame.keyframe().key_frame_fields.key_replay_counter.to_native(); |
| |
| if krc <= self.key_replay_counter { |
| error!("tx EAPOL Key frame uses invalid key replay counter: {:?} ({:?})", |
| krc, |
| self.key_replay_counter); |
| } |
| self.key_replay_counter = krc; |
| } |
| } |
| } |
| |
| updates |
| } |
| _ => UpdateSink::default(), |
| }; |
| |
| let was_esssa_established = self.is_established(); |
| for update in updates { |
| match update { |
| // Process Key updates ourselves to correctly track security associations. |
| SecAssocUpdate::Key(key) => { |
| if let Err(e) = self.on_key_confirmed(&mut new_updates, key) { |
| error!("error while processing key: {}", e); |
| }; |
| } |
| // Forward all other updates. |
| _ => new_updates.push(update), |
| } |
| } |
| |
| // Report once ESSSA is established. |
| if !was_esssa_established && self.is_established() { |
| info!("established ESSSA"); |
| new_updates.push(SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished)); |
| } |
| |
| self.push_updates(update_sink, new_updates); |
| |
| Ok(()) |
| } |
| |
| fn on_eapol_key_frame<B: ByteSlice>( |
| &mut self, |
| update_sink: &mut UpdateSink, |
| frame: eapol::KeyFrameRx<B>, |
| ) -> Result<(), Error> { |
| // Verify the frame complies with IEEE Std 802.11-2016, 12.7.2. |
| let verified_frame = match Dot11VerifiedKeyFrame::from_frame( |
| frame, |
| &self.role, |
| &self.negotiated_protection, |
| self.key_replay_counter, |
| ) { |
| // An invalid key replay counter means we should skip the frame, but may happen under |
| // normal circumstances and should not be logged as an error. |
| Err(e @ Error::InvalidKeyReplayCounter(_, _)) => { |
| info!("Ignoring eapol frame: {}", e); |
| return Ok(()); |
| } |
| result => result?, |
| }; |
| |
| // Safe: frame was just verified. |
| let raw_frame = verified_frame.unsafe_get_raw(); |
| |
| // 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(fxbug.dev/68916): We should verify the MIC here and only increase the counter if the MIC |
| // is valid. |
| if raw_frame.key_frame_fields.key_info().key_mic() { |
| if let Role::Supplicant = self.role { |
| self.key_replay_counter = raw_frame.key_frame_fields.key_replay_counter.to_native(); |
| } |
| } |
| |
| // Forward frame to correct security association. |
| // PMKSA must be established before any other security association can be established. Because |
| // the PMKSA is handled outside our ESSSA this is just an early return. |
| match self.pmksa.as_mut() { |
| Pmksa::Initialized { .. } => return Ok(()), |
| Pmksa::Established { .. } => {} |
| }; |
| |
| // Once PMKSA was established PTKSA and GTKSA can process frames. |
| // IEEE Std 802.11-2016, 12.7.2 b.2) |
| if raw_frame.key_frame_fields.key_info().key_type() == eapol::KeyType::PAIRWISE { |
| match self.ptksa.as_mut() { |
| Ptksa::Uninitialized { .. } => Ok(()), |
| Ptksa::Initialized { method } | Ptksa::Established { method, .. } => { |
| method.on_eapol_key_frame(update_sink, self.key_replay_counter, verified_frame) |
| } |
| } |
| } else if raw_frame.key_frame_fields.key_info().key_type() == eapol::KeyType::GROUP_SMK { |
| match self.gtksa.as_mut() { |
| 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: {:?}", |
| raw_frame.key_frame_fields.key_info().key_type() |
| ); |
| Ok(()) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| key::exchange::compute_mic, |
| rsna::{test_util, test_util::expect_eapol_resp, AuthStatus}, |
| Authenticator, Supplicant, |
| }, |
| wlan_common::{ |
| assert_variant, |
| ie::{get_rsn_ie_bytes, rsn::fake_wpa2_s_rsne}, |
| }, |
| }; |
| |
| const ANONCE: [u8; 32] = [0x1A; 32]; |
| const GTK: [u8; 16] = [0x1B; 16]; |
| const GTK_REKEY: [u8; 16] = [0x1F; 16]; |
| const GTK_REKEY_2: [u8; 16] = [0x2F; 16]; |
| |
| #[test] |
| fn test_supplicant_with_wpa3_authenticator() { |
| let mut supplicant = test_util::get_wpa3_supplicant(); |
| let mut authenticator = test_util::get_wpa3_authenticator(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send Supplicant SAE commit |
| let mut s_updates = vec![]; |
| let result = supplicant.on_sae_handshake_ind(&mut s_updates); |
| assert!(result.is_ok(), "Supplicant failed to ind SAE handshake"); |
| let s_sae_frame_vec = test_util::expect_sae_frame_vec(&s_updates[..]); |
| test_util::expect_schedule_sae_timeout(&s_updates[..]); |
| assert_eq!(s_updates.len(), 2, "{:?}", s_updates); |
| |
| // Respond to Supplicant SAE commit |
| let mut a_updates = vec![]; |
| for s_sae_frame in s_sae_frame_vec { |
| let result = authenticator.on_sae_frame_rx(&mut a_updates, s_sae_frame); |
| assert!(result.is_ok(), "Authenticator failed to rx SAE handshake message"); |
| } |
| let a_sae_frame_vec = test_util::expect_sae_frame_vec(&a_updates[..]); |
| test_util::expect_schedule_sae_timeout(&a_updates[..]); |
| assert_eq!(a_updates.len(), 3, "{:?}", a_updates); |
| |
| // Receive Authenticator SAE confirm |
| let mut s_updates = vec![]; |
| for a_sae_frame in a_sae_frame_vec { |
| let result = supplicant.on_sae_frame_rx(&mut s_updates, a_sae_frame); |
| assert!(result.is_ok(), "Supplicant failed to rx SAE handshake message"); |
| } |
| let s_sae_frame_vec = test_util::expect_sae_frame_vec(&s_updates[..]); |
| test_util::expect_schedule_sae_timeout(&s_updates[..]); |
| test_util::expect_reported_pmk(&s_updates[..]); |
| test_util::expect_reported_sae_auth_status(&s_updates[..], AuthStatus::Success); |
| test_util::expect_reported_status(&s_updates[..], SecAssocStatus::PmkSaEstablished); |
| assert_eq!(s_updates.len(), 5, "{:?}", s_updates); |
| |
| // Receive Supplicant SAE confirm |
| let mut a_updates = vec![]; |
| for s_sae_frame in s_sae_frame_vec { |
| let result = authenticator.on_sae_frame_rx(&mut a_updates, s_sae_frame); |
| assert!(result.is_ok(), "Authenticator failed to rx SAE handshake message"); |
| } |
| test_util::expect_reported_pmk(&a_updates[..]); |
| test_util::expect_reported_sae_auth_status(&a_updates[..], AuthStatus::Success); |
| test_util::expect_reported_status(&a_updates[..], SecAssocStatus::PmkSaEstablished); |
| let msg1 = test_util::expect_eapol_resp(&a_updates[..]); |
| authenticator |
| .on_eapol_conf(&mut a_updates, EapolResultCode::Success) |
| .expect("Failed eapol conf"); |
| assert_eq!(a_updates.len(), 4, "{:?}", a_updates); |
| |
| test_eapol_exchange(&mut supplicant, &mut authenticator, Some(msg1), true); |
| } |
| |
| #[test] |
| fn test_supplicant_with_wpa2_authenticator() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| let mut authenticator = test_util::get_wpa2_authenticator(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| test_eapol_exchange(&mut supplicant, &mut authenticator, None, false); |
| } |
| |
| #[test] |
| fn test_replay_first_message() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send first message of handshake. |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert!(result.is_ok()); |
| let first_msg2 = expect_eapol_resp(&updates[..]); |
| let first_fields = first_msg2.keyframe().key_frame_fields; |
| |
| // Replay first message which should restart the entire handshake. |
| // Verify the second message of the handshake was received. |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(3); |
| }); |
| assert!(result.is_ok()); |
| let second_msg2 = expect_eapol_resp(&updates[..]); |
| let second_fields = second_msg2.keyframe().key_frame_fields; |
| |
| // Verify Supplicant responded to the replayed first message and didn't change SNonce. |
| assert_eq!(second_fields.key_replay_counter.to_native(), 3); |
| assert_eq!(first_fields.key_nonce, second_fields.key_nonce); |
| } |
| |
| #[test] |
| fn test_first_message_does_not_change_replay_counter() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| |
| // Send first message of handshake. |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| expect_eapol_resp(&updates[..]); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| |
| // Raise the replay counter of message 1. |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert!(result.is_ok()); |
| expect_eapol_resp(&updates[..]); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| |
| // Lower the replay counter of message 1. |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| expect_eapol_resp(&updates[..]); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_msg1() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| expect_eapol_resp(&updates[..]); |
| } |
| |
| #[test] |
| fn test_nonzero_key_replay_counter_msg1() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert!(result.is_ok()); |
| expect_eapol_resp(&updates[..]); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_lower_msg3_counter() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| |
| // Intuitively, this should not succeed because the replay |
| // counter in message 3 is lower than in message 1. It is a |
| // quirk of IEEE 802.11-2016 12.7.2 that the replay counter in |
| // message 1 is in fact meaningless because replay counters |
| // are only updated when there is a MIC to verify. There is no |
| // MIC to verify in message 1, and so the replay counter |
| // doesn't matter. |
| let (result, updates) = send_fourway_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| test_util::expect_reported_ptk(&updates[..]); |
| } |
| |
| #[test] |
| fn test_key_replay_counter_updated_after_msg3() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert!(result.is_ok()); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| |
| let (result, updates) = send_fourway_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(5); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(5, supplicant.esssa.key_replay_counter); |
| test_util::expect_reported_ptk(&updates[..]); |
| |
| // First message should be dropped if replay counter too low. |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(5, supplicant.esssa.key_replay_counter); |
| assert!(updates.is_empty()); |
| |
| // After reset, first message should not be dropped. |
| supplicant.reset(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| expect_eapol_resp(&updates[..]); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_valid_msg3() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| |
| let (result, updates) = send_fourway_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert!(result.is_ok()); |
| test_util::expect_reported_ptk(&updates[..]); |
| } |
| |
| #[test] |
| fn test_zero_key_replay_counter_replayed_msg3() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| assert_eq!(0, supplicant.esssa.key_replay_counter); |
| |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(0); |
| }); |
| assert!(result.is_ok()); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| |
| let (result, sink) = send_fourway_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(2); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(2, supplicant.esssa.key_replay_counter); |
| test_util::expect_reported_ptk(&sink[..]); |
| |
| // 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, sink) = send_fourway_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(2); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(2, supplicant.esssa.key_replay_counter); |
| assert!(sink.is_empty()); |
| |
| // Send a valid message. |
| let (result, sink) = send_fourway_msg3(&mut supplicant, &ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(3); |
| }); |
| assert!(result.is_ok()); |
| assert_eq!(3, supplicant.esssa.key_replay_counter); |
| assert!(!sink.is_empty()); |
| } |
| |
| // Replays the first message of the 4-Way Handshake with an altered ANonce 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_different_anonces() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send 1st message of 4-Way Handshake for the first time and derive PTK. |
| let (_, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert_eq!(test_util::get_reported_ptk(&updates[..]), None); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let msg2_frame = msg2.keyframe(); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let first_ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let first_nonce = msg2_frame.key_frame_fields.key_nonce; |
| |
| // Send 1st message of 4-Way Handshake a second time and derive PTK. |
| // Use a different ANonce than initially used. |
| let (_, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(2); |
| msg1.key_frame_fields.key_nonce = [99; 32]; |
| }); |
| assert_eq!(test_util::get_reported_ptk(&updates[..]), None); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let msg2_frame = msg2.keyframe(); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let second_ptk = test_util::get_ptk(&[99; 32][..], &snonce[..]); |
| let second_nonce = msg2_frame.key_frame_fields.key_nonce; |
| |
| // Send 3rd message of 4-Way Handshake. |
| // The Supplicant now finished the 4-Way Handshake and should report its PTK. |
| // Use the same ANonce which was used in the replayed 1st message. |
| let (_, updates) = send_fourway_msg3(&mut supplicant, &second_ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(3); |
| msg3.key_frame_fields.key_nonce = [99; 32]; |
| }); |
| |
| let installed_ptk = test_util::expect_reported_ptk(&updates[..]); |
| assert_ne!(first_nonce, second_nonce); |
| assert_ne!(&first_ptk, &second_ptk); |
| assert_eq!(installed_ptk, second_ptk); |
| } |
| |
| // Replays the first message of the 4-Way Handshake without altering its ANonce to verify that |
| // (1) the Supplicant derives the same PTK for the replayed message, and |
| // (2) the Supplicant is reusing the nonce from its previous message, |
| // (3) the Supplicant only reports a PTK if the 4-Way Handshake was completed successfully. |
| // Regression test for: fxbug.dev/29713 |
| #[test] |
| fn test_replayed_msg1_ptk_installation_same_anonces() { |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send 1st message of 4-Way Handshake for the first time and derive PTK. |
| let (_, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(1); |
| }); |
| assert_eq!(test_util::get_reported_ptk(&updates[..]), None); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let msg2_frame = msg2.keyframe(); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let first_ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let first_nonce = msg2_frame.key_frame_fields.key_nonce; |
| |
| // Send 1st message of 4-Way Handshake a second time and derive PTK. |
| let (_, updates) = send_fourway_msg1(&mut supplicant, |msg1| { |
| msg1.key_frame_fields.key_replay_counter.set_from_native(2); |
| }); |
| assert_eq!(test_util::get_reported_ptk(&updates[..]), None); |
| let msg2 = expect_eapol_resp(&updates[..]); |
| let msg2_frame = msg2.keyframe(); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let second_ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let second_nonce = msg2_frame.key_frame_fields.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_fourway_msg3(&mut supplicant, &second_ptk, |msg3| { |
| msg3.key_frame_fields.key_replay_counter.set_from_native(3); |
| }); |
| |
| let installed_ptk = test_util::expect_reported_ptk(&updates[..]); |
| assert_eq!(first_nonce, second_nonce); |
| assert_eq!(&first_ptk, &second_ptk); |
| assert_eq!(installed_ptk, second_ptk); |
| } |
| |
| // Test for WPA2-Personal (PSK CCMP-128) with a Supplicant role. |
| #[test] |
| fn test_supplicant_wpa2_ccmp128_psk() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send first message |
| let (result, updates) = send_fourway_msg1(&mut supplicant, |_| {}); |
| assert!(result.is_ok()); |
| |
| // Verify 2nd message. |
| let msg2_buf = expect_eapol_resp(&updates[..]); |
| let msg2 = msg2_buf.keyframe(); |
| let s_rsne = fake_wpa2_s_rsne(); |
| let s_rsne_data = get_rsn_ie_bytes(&s_rsne); |
| assert_eq!({ msg2.eapol_fields.version }, eapol::ProtocolVersion::IEEE802DOT1X2001); |
| assert_eq!({ msg2.eapol_fields.packet_type }, eapol::PacketType::KEY); |
| let mut buf = vec![]; |
| msg2.write_into(false, &mut buf).expect("error converting message to bytes"); |
| assert_eq!(msg2.eapol_fields.packet_body_len.to_native() as usize, buf.len() - 4); |
| assert_eq!({ msg2.key_frame_fields.descriptor_type }, eapol::KeyDescriptor::IEEE802DOT11); |
| assert_eq!(msg2.key_frame_fields.key_info(), eapol::KeyInformation(0x010A)); |
| assert_eq!(msg2.key_frame_fields.key_len.to_native(), 0); |
| assert_eq!(msg2.key_frame_fields.key_replay_counter.to_native(), 1); |
| assert!(!test_util::is_zero(&msg2.key_frame_fields.key_nonce[..])); |
| assert!(test_util::is_zero(&msg2.key_frame_fields.key_iv[..])); |
| assert_eq!(msg2.key_frame_fields.key_rsc.to_native(), 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(), 20); |
| assert_eq!(&msg2.key_data[..], &s_rsne_data[..]); |
| |
| // Send 3rd message. |
| let snonce = msg2.key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let (result, updates) = send_fourway_msg3(&mut supplicant, &ptk, |_| {}); |
| assert!(result.is_ok()); |
| |
| // Verify 4th message was received and is correct. |
| let msg4_buf = expect_eapol_resp(&updates[..]); |
| let msg4 = msg4_buf.keyframe(); |
| assert_eq!({ msg4.eapol_fields.version }, eapol::ProtocolVersion::IEEE802DOT1X2001); |
| assert_eq!({ msg4.eapol_fields.packet_type }, eapol::PacketType::KEY); |
| assert_eq!(msg4.eapol_fields.packet_body_len.to_native() as usize, &msg4_buf[..].len() - 4); |
| assert_eq!({ msg4.key_frame_fields.descriptor_type }, eapol::KeyDescriptor::IEEE802DOT11); |
| assert_eq!(msg4.key_frame_fields.key_info(), eapol::KeyInformation(0x030A)); |
| assert_eq!(msg4.key_frame_fields.key_len.to_native(), 0); |
| assert_eq!(msg4.key_frame_fields.key_replay_counter.to_native(), 2); |
| assert!(test_util::is_zero(&msg4.key_frame_fields.key_nonce[..])); |
| assert!(test_util::is_zero(&msg4.key_frame_fields.key_iv[..])); |
| assert_eq!(msg4.key_frame_fields.key_rsc.to_native(), 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 = compute_mic(ptk.kck(), &test_util::get_rsne_protection(), &msg4) |
| .expect("error computing MIC"); |
| assert_eq!(&msg4.key_mic[..], &mic[..]); |
| |
| // Verify PTK was reported. |
| let reported_ptk = test_util::expect_reported_ptk(&updates[..]); |
| assert_eq!(ptk.ptk, reported_ptk.ptk); |
| |
| // Verify GTK was reported. |
| let reported_gtk = test_util::expect_reported_gtk(&updates[..]); |
| assert_eq!(>K[..], &reported_gtk.gtk[..]); |
| |
| // Verify ESS was reported to be established. |
| let reported_status = |
| test_util::expect_reported_status(&updates[..], SecAssocStatus::EssSaEstablished); |
| assert_eq!(reported_status, SecAssocStatus::EssSaEstablished); |
| |
| // Cause re-keying of GTK via Group-Key Handshake. |
| |
| let (result, updates) = send_group_key_msg1(&mut supplicant, &ptk, GTK_REKEY, 3, 3); |
| assert!(result.is_ok()); |
| |
| // Verify 2th message was received and is correct. |
| let msg2_buf = expect_eapol_resp(&updates[..]); |
| let msg2 = msg2_buf.keyframe(); |
| assert_eq!({ msg2.eapol_fields.version }, eapol::ProtocolVersion::IEEE802DOT1X2001); |
| assert_eq!({ msg2.eapol_fields.packet_type }, eapol::PacketType::KEY); |
| assert_eq!(msg2.eapol_fields.packet_body_len.to_native() as usize, &msg2_buf[..].len() - 4); |
| assert_eq!({ msg2.key_frame_fields.descriptor_type }, eapol::KeyDescriptor::IEEE802DOT11); |
| assert_eq!(msg2.key_frame_fields.key_info(), eapol::KeyInformation(0x0302)); |
| assert_eq!(msg2.key_frame_fields.key_len.to_native(), 0); |
| assert_eq!(msg2.key_frame_fields.key_replay_counter.to_native(), 3); |
| assert!(test_util::is_zero(&msg2.key_frame_fields.key_nonce[..])); |
| assert!(test_util::is_zero(&msg2.key_frame_fields.key_iv[..])); |
| assert_eq!(msg2.key_frame_fields.key_rsc.to_native(), 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 = compute_mic(ptk.kck(), &test_util::get_rsne_protection(), &msg2) |
| .expect("error computing MIC"); |
| assert_eq!(&msg2.key_mic[..], &mic[..]); |
| |
| // Verify PTK was NOT re-installed. |
| assert_eq!(test_util::get_reported_ptk(&updates[..]), None); |
| |
| // Verify GTK was installed. |
| let reported_gtk = test_util::expect_reported_gtk(&updates[..]); |
| assert_eq!(>K_REKEY[..], &reported_gtk.gtk[..]); |
| } |
| |
| // Test to verify that GTKs derived in the 4-Way Handshake are not being re-installed |
| // through Group Key Handshakes. |
| #[test] |
| fn test_supplicant_no_gtk_reinstallation_from_4way() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Complete 4-Way Handshake. |
| let updates = send_fourway_msg1(&mut supplicant, |_| {}).1; |
| let msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let _ = send_fourway_msg3(&mut supplicant, &ptk, |_| {}); |
| |
| // Cause re-keying of GTK via Group-Key Handshake. |
| // Rekey same GTK which has been already installed via the 4-Way Handshake. |
| // This GTK should not be re-installed. |
| let (result, updates) = send_group_key_msg1(&mut supplicant, &ptk, GTK, 2, 3); |
| assert!(result.is_ok()); |
| |
| // Verify 2th message was received and is correct. |
| let msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let keyframe = msg2.keyframe(); |
| assert_eq!(keyframe.eapol_fields.version, eapol::ProtocolVersion::IEEE802DOT1X2001); |
| assert_eq!(keyframe.eapol_fields.packet_type, eapol::PacketType::KEY); |
| assert_eq!(keyframe.eapol_fields.packet_body_len.to_native() as usize, msg2.len() - 4); |
| assert_eq!(keyframe.key_frame_fields.descriptor_type, eapol::KeyDescriptor::IEEE802DOT11); |
| assert_eq!(keyframe.key_frame_fields.key_info().0, 0x0302); |
| assert_eq!(keyframe.key_frame_fields.key_len.to_native(), 0); |
| assert_eq!(keyframe.key_frame_fields.key_replay_counter.to_native(), 3); |
| assert!(test_util::is_zero(&keyframe.key_frame_fields.key_nonce[..])); |
| assert!(test_util::is_zero(&keyframe.key_frame_fields.key_iv[..])); |
| assert_eq!(keyframe.key_frame_fields.key_rsc.to_native(), 0); |
| assert!(!test_util::is_zero(&keyframe.key_mic[..])); |
| assert_eq!(keyframe.key_mic.len(), test_util::mic_len()); |
| assert_eq!(keyframe.key_data.len(), 0); |
| assert!(test_util::is_zero(&keyframe.key_data[..])); |
| // Verify the message's MIC. |
| let mic = compute_mic(ptk.kck(), &test_util::get_rsne_protection(), &keyframe) |
| .expect("error computing MIC"); |
| assert_eq!(&keyframe.key_mic[..], &mic[..]); |
| |
| // Verify neither PTK nor GTK were re-installed. |
| assert_eq!(test_util::get_reported_ptk(&updates[..]), None); |
| assert_eq!(test_util::get_reported_gtk(&updates[..]), None); |
| } |
| |
| // Test to verify that already rotated GTKs are not being re-installed. |
| #[test] |
| fn test_supplicant_no_gtk_reinstallation() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Complete 4-Way Handshake. |
| let updates = send_fourway_msg1(&mut supplicant, |_| {}).1; |
| let msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let _ = send_fourway_msg3(&mut supplicant, &ptk, |_| {}); |
| |
| // Cause re-keying of GTK via Group-Key Handshake. |
| // Rekey same GTK which has been already installed via the 4-Way Handshake. |
| // This GTK should not be re-installed. |
| |
| // Rotate GTK. |
| let (result, updates) = send_group_key_msg1(&mut supplicant, &ptk, GTK_REKEY, 3, 3); |
| assert!(result.is_ok()); |
| let reported_gtk = test_util::expect_reported_gtk(&updates[..]); |
| assert_eq!(&reported_gtk.gtk[..], >K_REKEY[..]); |
| |
| let (result, updates) = send_group_key_msg1(&mut supplicant, &ptk, GTK_REKEY_2, 1, 4); |
| assert!(result.is_ok(), "{:?}", result); |
| let reported_gtk = test_util::expect_reported_gtk(&updates[..]); |
| assert_eq!(&reported_gtk.gtk[..], >K_REKEY_2[..]); |
| |
| // Rotate GTK to already installed key. Verify GTK was not re-installed. |
| let (result, updates) = send_group_key_msg1(&mut supplicant, &ptk, GTK_REKEY, 3, 5); |
| assert!(result.is_ok()); |
| assert_eq!(test_util::get_reported_gtk(&updates[..]), None); |
| } |
| |
| #[test] |
| fn test_key_frame_timeout() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send the first frame. |
| let updates = send_fourway_msg1(&mut supplicant, |_| {}).1; |
| let msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert!(update_sink.is_empty()); |
| |
| // Timeout several times. |
| for _ in 1..MAX_KEY_FRAME_RETRIES { |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_key_frame_timeout(&mut update_sink) |
| .expect("Failed to send key frame timeout"); |
| let msg2_retry = test_util::expect_eapol_resp(&update_sink[..]); |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert_eq!(msg2, msg2_retry); |
| } |
| |
| // Go idle on the last retry. |
| let mut update_sink = vec![]; |
| assert_variant!(supplicant.on_eapol_key_frame_timeout(&mut update_sink), Ok(())); |
| assert!(update_sink.is_empty()); |
| } |
| |
| #[test] |
| fn test_key_frame_timeout_retries_reset() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| for _ in 0..3 { |
| // Send the first frame. |
| let updates = send_fourway_msg1(&mut supplicant, |_| {}).1; |
| let msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert!(update_sink.is_empty()); |
| |
| // Timeout several times. |
| for _ in 1..MAX_KEY_FRAME_RETRIES { |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_key_frame_timeout(&mut update_sink) |
| .expect("Failed to send key frame timeout"); |
| let msg2_retry = test_util::expect_eapol_resp(&update_sink[..]); |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert_eq!(msg2, msg2_retry); |
| } |
| |
| // Go idle on the last retry. |
| let mut update_sink = vec![]; |
| assert_variant!(supplicant.on_eapol_key_frame_timeout(&mut update_sink), Ok(())); |
| assert!(update_sink.is_empty()); |
| } |
| } |
| |
| #[test] |
| fn test_overall_timeout_before_msg1() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| assert_variant!(supplicant.on_establishing_rsna_timeout(), Error::EapolHandshakeNotStarted); |
| } |
| |
| #[test] |
| fn test_overall_timeout_after_msg2() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send the first frame. |
| let updates = send_fourway_msg1(&mut supplicant, |_| {}).1; |
| let _msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert!(update_sink.is_empty()); |
| |
| assert_variant!(supplicant.on_establishing_rsna_timeout(), Error::LikelyWrongCredential); |
| } |
| |
| #[test] |
| fn test_overall_timeout_before_conf() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send the first frame but don't send a conf in response. |
| let msg = test_util::get_wpa2_4whs_msg1(&ANONCE[..], |_| {}); |
| let mut update_sink = UpdateSink::default(); |
| supplicant |
| .on_eapol_frame(&mut update_sink, eapol::Frame::Key(msg.keyframe())) |
| .expect("Failed to send eapol frame"); |
| let _msg2 = test_util::expect_eapol_resp(&update_sink[..]); |
| |
| assert_variant!( |
| supplicant.on_establishing_rsna_timeout(), |
| Error::NoKeyFrameTransmissionConfirm(0) |
| ); |
| } |
| |
| #[test] |
| fn test_key_frame_timeout_retry_without_conf() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send the first frame. |
| let updates = send_fourway_msg1(&mut supplicant, |_| {}).1; |
| let msg2 = test_util::expect_eapol_resp(&updates[..]); |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert!(update_sink.is_empty()); |
| |
| // Respond to one timeout, but don't confirm the retry. |
| let mut update_sink = vec![]; |
| supplicant |
| .on_eapol_key_frame_timeout(&mut update_sink) |
| .expect("Failed to send key frame timeout"); |
| let msg2_retry = test_util::expect_eapol_resp(&update_sink[..]); |
| assert_eq!(msg2, msg2_retry); |
| |
| // Failure on the next timeout. |
| let mut update_sink = vec![]; |
| assert_variant!( |
| supplicant.on_eapol_key_frame_timeout(&mut update_sink), |
| Err(Error::NoKeyFrameTransmissionConfirm(0)) |
| ); |
| } |
| |
| #[test] |
| fn test_msg2_out_of_order_conf() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send the first frame but don't send a conf in response. |
| let msg = test_util::get_wpa2_4whs_msg1(&ANONCE[..], |_| {}); |
| let mut update_sink = UpdateSink::default(); |
| supplicant |
| .on_eapol_frame(&mut update_sink, eapol::Frame::Key(msg.keyframe())) |
| .expect("Failed to send eapol frame"); |
| let msg2 = test_util::expect_eapol_resp(&update_sink[..]); |
| |
| // No key frame confirm means that we will buffer updates until the confirm is received. |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let (result, updates) = send_fourway_msg3(&mut supplicant, &ptk, |_| {}); |
| result.expect("Failed to send msg3"); |
| assert!(updates.is_empty()); |
| |
| // When confirm is received we receive updates through msg4 |
| let mut update_sink = UpdateSink::default(); |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| assert_eq!(update_sink.len(), 3); |
| test_util::expect_eapol_resp(&update_sink[..]); |
| test_util::expect_reported_ptk(&update_sink[..]); |
| test_util::expect_reported_gtk(&update_sink[..]); |
| |
| // On another confirm, we receive the remaining updates. |
| supplicant |
| .on_eapol_conf(&mut update_sink, EapolResultCode::Success) |
| .expect("Failed to send eapol conf"); |
| test_util::expect_reported_status(&update_sink[..], SecAssocStatus::EssSaEstablished); |
| } |
| |
| #[test] |
| fn test_msg2_no_conf() { |
| // Create ESS Security Association |
| let mut supplicant = test_util::get_wpa2_supplicant(); |
| supplicant.start().expect("Failed starting Supplicant"); |
| |
| // Send the first frame but don't send a conf in response. |
| let msg = test_util::get_wpa2_4whs_msg1(&ANONCE[..], |_| {}); |
| let mut update_sink = UpdateSink::default(); |
| supplicant |
| .on_eapol_frame(&mut update_sink, eapol::Frame::Key(msg.keyframe())) |
| .expect("Failed to send eapol frame"); |
| let msg2 = test_util::expect_eapol_resp(&update_sink[..]); |
| |
| // No key frame confirm means that we will buffer updates until the confirm is received. |
| let snonce = msg2.keyframe().key_frame_fields.key_nonce; |
| let ptk = test_util::get_ptk(&ANONCE[..], &snonce[..]); |
| let (result, updates) = send_fourway_msg3(&mut supplicant, &ptk, |_| {}); |
| result.expect("Failed to send msg3"); |
| assert!(updates.is_empty()); |
| |
| // We never receive a confirm, and report this on timeout. |
| // There are 4 pending updates that we drop after this timeout: |
| // * EAPOL message 4 |
| // * PTK |
| // * GTK |
| // * ESSSA Established |
| let mut update_sink = vec![]; |
| assert_variant!( |
| supplicant.on_eapol_key_frame_timeout(&mut update_sink), |
| Err(Error::NoKeyFrameTransmissionConfirm(4)) |
| ); |
| } |
| |
| // TODO(hahnr): Add additional tests: |
| // Invalid messages from Authenticator |
| // Timeouts |
| // Nonce reuse |
| // (in)-compatible protocol and RSNE versions |
| |
| fn test_eapol_exchange( |
| supplicant: &mut Supplicant, |
| authenticator: &mut Authenticator, |
| msg1: Option<eapol::KeyFrameBuf>, |
| wpa3: bool, |
| ) { |
| // Initiate Authenticator. |
| let mut a_updates = vec![]; |
| let result = authenticator.initiate(&mut a_updates); |
| assert!(result.is_ok(), "Authenticator failed initiating: {}", result.unwrap_err()); |
| |
| if wpa3 { |
| assert!( |
| msg1.is_some(), |
| "WPA3 EAPOL exchange starting without message constructed immediately after SAE" |
| ); |
| } |
| // If msg1 is provided, we expect no updates from the Authenticator. Otherwise, we |
| // expect the Authenticator to establish the PmkSa and produce msg1. |
| let msg1 = match msg1 { |
| Some(msg1) => { |
| assert_eq!(a_updates.len(), 0, "{:?}", a_updates); |
| msg1 |
| } |
| None => { |
| assert_eq!(a_updates.len(), 2); |
| test_util::expect_reported_status(&a_updates, SecAssocStatus::PmkSaEstablished); |
| let resp = test_util::expect_eapol_resp(&a_updates[..]); |
| authenticator |
| .on_eapol_conf(&mut a_updates, EapolResultCode::Success) |
| .expect("Failed eapol conf"); |
| resp |
| } |
| }; |
| |
| // 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.keyframe())); |
| assert!(result.is_ok(), "Supplicant failed processing msg #1: {}", result.unwrap_err()); |
| let msg2 = test_util::expect_eapol_resp(&s_updates[..]); |
| supplicant |
| .on_eapol_conf(&mut s_updates, EapolResultCode::Success) |
| .expect("Failed eapol conf"); |
| assert_eq!(s_updates.len(), 1, "{:?}", s_updates); |
| |
| // 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.keyframe())); |
| assert!(result.is_ok(), "Authenticator failed processing msg #2: {}", result.unwrap_err()); |
| let msg3 = test_util::expect_eapol_resp(&a_updates[..]); |
| authenticator |
| .on_eapol_conf(&mut a_updates, EapolResultCode::Success) |
| .expect("Failed eapol conf"); |
| assert_eq!(a_updates.len(), 1, "{:?}", a_updates); |
| |
| // 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.keyframe())); |
| assert!(result.is_ok(), "Supplicant failed processing msg #3: {}", result.unwrap_err()); |
| |
| let msg4 = test_util::expect_eapol_resp(&s_updates[..]); |
| let s_ptk = test_util::expect_reported_ptk(&s_updates[..]); |
| let s_gtk = test_util::expect_reported_gtk(&s_updates[..]); |
| let s_igtk = if wpa3 { |
| assert_eq!(s_updates.len(), 4, "{:?}", s_updates); |
| Some(test_util::expect_reported_igtk(&s_updates[..])) |
| } else { |
| assert_eq!(s_updates.len(), 3, "{:?}", s_updates); |
| None |
| }; |
| |
| // We shouldn't see the esssa established until a confirm is received. |
| supplicant |
| .on_eapol_conf(&mut s_updates, EapolResultCode::Success) |
| .expect("Failed eapol conf"); |
| test_util::expect_reported_status(&s_updates, SecAssocStatus::EssSaEstablished); |
| |
| // Send msg #4 to Authenticator. |
| let mut a_updates = vec![]; |
| let result = |
| authenticator.on_eapol_frame(&mut a_updates, eapol::Frame::Key(msg4.keyframe())); |
| assert!(result.is_ok(), "Authenticator failed processing msg #4: {}", result.unwrap_err()); |
| let a_ptk = test_util::expect_reported_ptk(&a_updates[..]); |
| let a_gtk = test_util::expect_reported_gtk(&a_updates[..]); |
| |
| let a_igtk = if wpa3 { |
| assert_eq!(a_updates.len(), 4, "{:?}", a_updates); |
| Some(test_util::expect_reported_igtk(&a_updates[..])) |
| } else { |
| assert_eq!(a_updates.len(), 3, "{:?}", a_updates); |
| None |
| }; |
| |
| test_util::expect_reported_status(&a_updates, SecAssocStatus::EssSaEstablished); |
| |
| // Verify derived keys match and status reports ESS-SA as established. |
| assert_eq!(a_ptk, s_ptk); |
| assert_eq!(a_gtk, s_gtk); |
| assert_eq!(a_igtk, s_igtk); |
| } |
| |
| fn send_eapol_conf( |
| supplicant: &mut Supplicant, |
| update_sink: &mut UpdateSink, |
| ) -> Result<(), Error> { |
| let mut sent_frame = false; |
| for update in &update_sink[..] { |
| if let SecAssocUpdate::TxEapolKeyFrame { .. } = update { |
| sent_frame = true; |
| } |
| } |
| if sent_frame { |
| supplicant.on_eapol_conf(update_sink, EapolResultCode::Success) |
| } else { |
| Ok(()) |
| } |
| } |
| |
| fn send_fourway_msg1<F>( |
| supplicant: &mut Supplicant, |
| msg_modifier: F, |
| ) -> (Result<(), Error>, UpdateSink) |
| where |
| F: Fn(&mut eapol::KeyFrameTx), |
| { |
| let msg = test_util::get_wpa2_4whs_msg1(&ANONCE[..], msg_modifier); |
| let mut update_sink = UpdateSink::default(); |
| let result = supplicant.on_eapol_frame(&mut update_sink, eapol::Frame::Key(msg.keyframe())); |
| let result = result.and_then(|_| send_eapol_conf(supplicant, &mut update_sink)); |
| (result, update_sink) |
| } |
| |
| fn send_fourway_msg3<F>( |
| supplicant: &mut Supplicant, |
| ptk: &Ptk, |
| msg_modifier: F, |
| ) -> (Result<(), Error>, UpdateSink) |
| where |
| F: Fn(&mut eapol::KeyFrameTx), |
| { |
| let msg = test_util::get_wpa2_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.keyframe())); |
| let result = result.and_then(|_| send_eapol_conf(supplicant, &mut update_sink)); |
| (result, update_sink) |
| } |
| |
| fn send_group_key_msg1( |
| supplicant: &mut Supplicant, |
| ptk: &Ptk, |
| gtk: [u8; 16], |
| key_id: u8, |
| key_replay_counter: u64, |
| ) -> (Result<(), Error>, UpdateSink) { |
| let msg = test_util::get_group_key_hs_msg1(ptk, >k[..], key_id, key_replay_counter); |
| let mut update_sink = UpdateSink::default(); |
| let result = supplicant.on_eapol_frame(&mut update_sink, eapol::Frame::Key(msg.keyframe())); |
| let result = result.and_then(|_| send_eapol_conf(supplicant, &mut update_sink)); |
| (result, update_sink) |
| } |
| } |