| // 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 Error; |
| use akm::Akm; |
| use bytes::Bytes; |
| use bytes::BytesMut; |
| use crypto_utils::nonce::NonceReader; |
| use eapol; |
| use failure; |
| use integrity; |
| use key::exchange::Key; |
| use key::exchange::handshake::fourway::{self, FourwayHandshakeFrame}; |
| use key::gtk::Gtk; |
| use key::ptk::Ptk; |
| use key_data; |
| use rsna::{SecAssocResult, SecAssocUpdate, VerifiedKeyFrame}; |
| use rsne::Rsne; |
| |
| #[derive(Debug, Default, PartialEq)] |
| struct PtkInitState {} |
| #[derive(Debug, PartialEq)] |
| struct GtkInitState {} |
| |
| impl PtkInitState { |
| // IEEE Std 802.1X-2010, 12.7.6.2 |
| fn on_message_1(&self, shared: &mut SharedState, msg1: FourwayHandshakeFrame) |
| -> Result<(eapol::KeyFrame, Ptk), failure::Error> |
| { |
| let anonce = &msg1.get().key_nonce; |
| let snonce = shared.nonce_rdr.next()?; |
| let rsne = &shared.cfg.s_rsne; |
| let akm = &rsne.akm_suites[0]; |
| let cipher = &rsne.pairwise_cipher_suites[0]; |
| |
| let ptk = Ptk::new( |
| &shared.pmk[..], |
| &shared.cfg.a_addr, |
| &shared.cfg.s_addr, |
| &anonce[..], |
| &snonce[..], |
| akm, |
| cipher, |
| )?; |
| shared.anonce.copy_from_slice(&anonce[..]); |
| shared.kek = ptk.kek().to_vec(); |
| shared.kck = ptk.kck().to_vec(); |
| |
| let msg2 = self.create_message_2(shared, msg1.get(), &snonce[..])?; |
| |
| Ok((msg2, ptk)) |
| } |
| |
| // IEEE Std 802.1X-2010, 12.7.6.3 |
| fn create_message_2( |
| &self, |
| shared: &SharedState, |
| msg1: &eapol::KeyFrame, |
| snonce: &[u8], |
| ) -> Result<eapol::KeyFrame, failure::Error> { |
| let mut key_info = eapol::KeyInformation(0); |
| key_info.set_key_descriptor_version(msg1.key_info.key_descriptor_version()); |
| key_info.set_key_type(msg1.key_info.key_type()); |
| key_info.set_key_mic(true); |
| |
| let mut key_data = vec![]; |
| shared.cfg.s_rsne.as_bytes(&mut key_data); |
| |
| let mut msg2 = eapol::KeyFrame { |
| version: msg1.version, |
| packet_type: eapol::PacketType::Key as u8, |
| packet_body_len: 0, // Updated afterwards |
| descriptor_type: eapol::KeyDescriptor::Ieee802dot11 as u8, |
| key_info: key_info, |
| key_len: 0, |
| key_replay_counter: msg1.key_replay_counter, |
| key_mic: Bytes::from(vec![0u8; msg1.key_mic.len()]), |
| key_rsc: 0, |
| key_iv: [0u8; 16], |
| key_nonce: eapol::to_array(snonce), |
| key_data_len: key_data.len() as u16, |
| key_data: Bytes::from(key_data), |
| }; |
| msg2.update_packet_body_len(); |
| |
| // Verified before that Supplicant's RSNE holds one AKM Suite. |
| let akm = &shared.cfg.s_rsne.akm_suites[0]; |
| let integrity_alg = akm.integrity_algorithm().ok_or(Error::UnsupportedAkmSuite)?; |
| let mic_len = akm.mic_bytes().ok_or(Error::UnsupportedAkmSuite)?; |
| update_mic(&shared.kck[..], mic_len, integrity_alg, &mut msg2)?; |
| |
| Ok(msg2) |
| } |
| } |
| |
| impl GtkInitState { |
| // IEEE Std 802.1X-2010, 12.7.6.4 |
| fn on_message_3(&self, shared: &mut SharedState, msg3: FourwayHandshakeFrame) |
| -> Result<(eapol::KeyFrame, Gtk), failure::Error> |
| { |
| let mut gtk: Option<key_data::kde::Gtk> = None; |
| let mut rsne: Option<Rsne> = None; |
| let mut _second_rsne: Option<Rsne> = None; |
| let elements = key_data::extract_elements(&msg3.key_data_plaintext()[..])?; |
| for ele in elements { |
| match (ele, rsne.as_ref()) { |
| (key_data::Element::Gtk(_, e), _) => gtk = Some(e), |
| (key_data::Element::Rsne(e), None) => rsne = Some(e), |
| (key_data::Element::Rsne(e), Some(_)) => _second_rsne = Some(e), |
| _ => (), |
| } |
| } |
| |
| // Proceed if key data held a GTK and RSNE and RSNE is the Authenticator's announced one. |
| match (gtk, rsne) { |
| (Some(gtk), Some(rsne)) => { |
| ensure!(&rsne == &shared.cfg.a_rsne, Error::InvalidKeyDataRsne); |
| let msg4 = self.create_message_4(shared, msg3.get())?; |
| Ok((msg4, Gtk::from_gtk(gtk.gtk, gtk.info.key_id()))) |
| } |
| _ => bail!(Error::InvalidKeyDataContent), |
| } |
| } |
| |
| // IEEE Std 802.1X-2010, 12.7.6.5 |
| fn create_message_4(&self, shared: &SharedState, msg3: &eapol::KeyFrame) |
| -> Result<eapol::KeyFrame, failure::Error> |
| { |
| let mut key_info = eapol::KeyInformation(0); |
| key_info.set_key_descriptor_version(msg3.key_info.key_descriptor_version()); |
| key_info.set_key_type(msg3.key_info.key_type()); |
| key_info.set_key_mic(true); |
| key_info.set_secure(true); |
| |
| let mut msg4 = eapol::KeyFrame { |
| version: msg3.version, |
| packet_type: eapol::PacketType::Key as u8, |
| packet_body_len: 0, // Updated afterwards |
| descriptor_type: eapol::KeyDescriptor::Ieee802dot11 as u8, |
| key_info: key_info, |
| key_len: 0, |
| key_replay_counter: msg3.key_replay_counter, |
| key_mic: Bytes::from(vec![0u8; msg3.key_mic.len()]), |
| key_rsc: 0, |
| key_iv: [0u8; 16], |
| key_nonce: [0u8; 32], |
| key_data_len: 0, |
| key_data: Bytes::from(vec![]), |
| }; |
| msg4.update_packet_body_len(); |
| |
| // Verified before that Supplicant's RSNE holds one AKM Suite. |
| let akm = &shared.cfg.s_rsne.akm_suites[0]; |
| let integrity_alg = akm.integrity_algorithm().ok_or(Error::UnsupportedAkmSuite)?; |
| let mic_len = akm.mic_bytes().ok_or(Error::UnsupportedAkmSuite)?; |
| update_mic(&shared.kck[..], mic_len, integrity_alg, &mut msg4)?; |
| |
| Ok(msg4) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| enum State { |
| PtkInit(PtkInitState), |
| GtkInit(GtkInitState), |
| Completed, |
| } |
| |
| impl State { |
| pub fn on_eapol_key_frame(self, shared: &mut SharedState, frame: FourwayHandshakeFrame) |
| -> Result<(State, Option<Vec<SecAssocUpdate>>), failure::Error> |
| { |
| match fourway::message_number(frame.get()) { |
| // Only process first and third message of the Handshake. |
| fourway::MessageNumber::Message1 => self.on_message_1(shared, frame), |
| fourway::MessageNumber::Message3 => self.on_message_3(shared, frame), |
| // Drop any other message with an error. |
| unexpected_msg => bail!(Error::Unexpected4WayHandshakeMessage(unexpected_msg)), |
| } |
| } |
| |
| fn on_message_1(self, shared: &mut SharedState, msg1: FourwayHandshakeFrame) |
| -> Result<(State, Option<Vec<SecAssocUpdate>>), failure::Error> |
| { |
| // Always reset Handshake when first message was received. |
| match self { |
| // If the Handshake already completed, simply drop the message. |
| State::Completed => return Ok((self, None)), |
| // If the Handshake is already advanced further, restart the entire Handshake. |
| State::GtkInit(_) => return Ok((State::PtkInit(PtkInitState {}), None)), |
| // Else, if the message was expected, proceed. |
| _ => (), |
| }; |
| |
| // Only the PTK-Init state processes the first message. |
| match self { |
| State::PtkInit(ptk_init) => { |
| let (msg2, ptk) = ptk_init.on_message_1(shared, msg1)?; |
| // If the first message was processed successfully the PTK is known and the GTK |
| // can be exchanged with the Authenticator. Move the state machine forward. |
| Ok(( |
| State::GtkInit(GtkInitState {}), |
| Some(vec![ |
| SecAssocUpdate::TxEapolKeyFrame(msg2), |
| SecAssocUpdate::Key(Key::Ptk(ptk)), |
| ]), |
| )) |
| } |
| // This should never happen. |
| _ => panic!("tried to process first message of 4-Way Handshake in illegal state"), |
| } |
| } |
| |
| fn on_message_3(self, shared: &mut SharedState, msg3: FourwayHandshakeFrame) |
| -> Result<(State, Option<Vec<SecAssocUpdate>>), failure::Error> |
| { |
| // Third message of Handshake is only processed once to prevent replay attacks such as |
| // KRACK. A replayed third message will be dropped and has no effect on the Supplicant. |
| // TODO(hahnr): Decide whether this client side fix should be kept, which will reduce |
| // reliability, or if instead the Supplicant should trust the Authenticator to be patched. |
| match self { |
| State::GtkInit(gtk_init) => { |
| let (msg4, gtk) = gtk_init.on_message_3(shared, msg3)?; |
| // The third message was successfully processed and the handshake completed. |
| Ok(( |
| State::Completed, |
| Some(vec![ |
| SecAssocUpdate::TxEapolKeyFrame(msg4), |
| SecAssocUpdate::Key(Key::Gtk(gtk)), |
| ]) |
| )) |
| } |
| // At this point keys are either already installed and this message is a replay of a |
| // previous one, or, the message was received before the first message. In any case, |
| // drop the frame. |
| _ => Ok((self, None)), |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct SharedState { |
| anonce: [u8; 32], |
| pmk: Vec<u8>, |
| kek: Vec<u8>, |
| kck: Vec<u8>, |
| cfg: fourway::Config, |
| nonce_rdr: NonceReader, |
| } |
| |
| impl PartialEq for SharedState { |
| fn eq(&self, other: &SharedState) -> bool { |
| // Exclude nonce generator from comparison. This code will soon change once the nonce |
| // generator moved further up the stack. |
| self.anonce == other.anonce && |
| self.pmk == other.pmk && |
| self.kek == other.kek && |
| self.kck == other.kck && |
| self.cfg == other.cfg |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Supplicant { |
| shared: SharedState, |
| state: Option<State>, |
| } |
| |
| impl Supplicant { |
| pub fn new(cfg: fourway::Config, pmk: Vec<u8>) -> Result<Self, failure::Error> { |
| let nonce_rdr = NonceReader::new(&cfg.s_addr[..])?; |
| Ok(Supplicant { |
| state: Some(State::PtkInit(PtkInitState {})), |
| shared: SharedState { |
| anonce: [0u8; 32], |
| pmk: pmk, |
| kek: vec![], |
| kck: vec![], |
| cfg, |
| nonce_rdr, |
| }, |
| }) |
| } |
| |
| pub fn anonce(&self) -> &[u8] { |
| &self.shared.anonce[..] |
| } |
| |
| pub fn on_eapol_key_frame(&mut self, frame: FourwayHandshakeFrame) -> SecAssocResult { |
| self.state.take().map_or_else(|| Ok(vec![]), |state| { |
| let (state, updates) = state.on_eapol_key_frame(&mut self.shared, frame)?; |
| self.state = Some(state); |
| Ok(updates.unwrap_or_default()) |
| }) |
| } |
| |
| pub fn destroy(self) -> fourway::Config { |
| self.shared.cfg |
| } |
| } |
| |
| fn update_mic( |
| kck: &[u8], |
| mic_len: u16, |
| alg: Box<integrity::Algorithm>, |
| frame: &mut eapol::KeyFrame, |
| ) -> Result<(), failure::Error> { |
| let mut buf = Vec::with_capacity(frame.len()); |
| frame.as_bytes(true, &mut buf); |
| let written = buf.len(); |
| buf.truncate(written); |
| let mic = alg.compute(kck, &buf[..])?; |
| frame.key_mic = Bytes::from(&mic[..mic_len as usize]); |
| Ok(()) |
| } |