blob: 5369e31a688d0978e9386fc2b7e8161dff49c0f8 [file]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This crate doesn't comply with all 2018 idioms
#![allow(elided_lifetimes_in_paths)]
#![allow(unused)]
mod boringssl;
mod crypto_utils;
mod ecc;
mod frame;
mod state;
pub use crypto_utils::kdf_sha256;
pub use frame::{AntiCloggingTokenMsg, CommitMsg, ConfirmMsg};
use {
anyhow::{bail, Error},
boringssl::{Bignum, EcGroupId},
fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
log::warn,
mundane::{hash::Digest, hmac},
wlan_common::ie::rsn::akm::{self, Akm, AKM_PSK, AKM_SAE},
wlan_common::mac::MacAddr,
};
/// IEEE Std 802.11-2016, 12.4.4.1
/// Elliptic curve group 19 is the default supported group -- all SAE peers must support it, and in
/// practice it is generally used.
pub const DEFAULT_GROUP_ID: u16 = 19;
/// Maximum number of incorrect frames sent before SAE fails.
const MAX_RETRIES_PER_EXCHANGE: u16 = 30;
/// A shared key computed by an SAE handshake.
#[derive(Clone, PartialEq, Debug)]
pub struct Key {
pub pmk: Vec<u8>,
pub pmkid: Vec<u8>,
}
/// Types of timeout that are used by SAE handshakes. Duration and scheduling of these timeouts
/// is left to the user of this library.
#[derive(Debug, Clone, PartialEq)]
pub enum Timeout {
/// Timeout before the most recent message(s) should be resent.
Retransmission,
/// Timeout before the PMK produced by a successful handshake is considered invalid.
KeyExpiration,
}
#[derive(Debug)]
pub enum RejectReason {
/// We experienced a failure that was unrelated to data received from the peer. This likely
/// means we are not in a good state.
InternalError(Error),
/// Data received from the peer failed validation, and we cannot generate a PMK.
AuthFailed,
/// The peer has failed to respond or sent incorrect responses too many times.
TooManyRetries,
/// The SAE PMKSA has expired, reauthenticate.
KeyExpiration,
}
impl From<Error> for RejectReason {
fn from(e: Error) -> Self {
Self::InternalError(e)
}
}
#[derive(Debug)]
pub struct AuthFrameRx<'a> {
pub seq: u16,
pub status_code: fidl_ieee80211::StatusCode,
pub body: &'a [u8],
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AuthFrameTx {
pub seq: u16,
pub status_code: fidl_ieee80211::StatusCode,
pub body: Vec<u8>,
}
/// An update generated to progress an SAE handshake. These updates should generally be converted
/// into a frame and sent to the SAE peer.
#[derive(Debug)]
pub enum SaeUpdate {
/// Send an auth frame to the peer.
SendFrame(AuthFrameTx),
/// Indicates the handshake is complete. The handshake should *not* be deleted at this point.
Success(Key),
/// Indicates that the handshake has failed and must be aborted or restarted.
Reject(RejectReason),
/// Request the user of the library to set or reset a timeout. If this timeout expires, it
/// should be passed to SaeHandshake::handle_timeout.
ResetTimeout(Timeout),
/// Request the user of the library to cancel a timeout that was previously set.
CancelTimeout(Timeout),
}
pub type SaeUpdateSink = Vec<SaeUpdate>;
/// IEEE 802.11-2016 12.4: Simultaneous Authentication of Equals (SAE)
///
/// An SAE handshake with a peer is a symmetric handshake that may be used in place of open
/// authentication as the AKM. A full handshake consists of both peers sending a Commit and Confirm
/// frame, at which point they have both derived a shared key that is unique to those peers and that
/// session.
///
/// Structs implementing this trait are responsible for handling both a successful SAE handshake,
/// various failure modes, and edge cases such as retries and timeouts.
///
/// None of the functions in this trait return errors. Instead, non-fatal errors are logged, and
/// fatal errors push an SaeUpdate::Reject to the update sink. Once an SaeUpdate::Reject is pushed,
/// all further operations are no-ops.
pub trait SaeHandshake: Send {
/// Initiate SAE by sending the first commit message. If the peer STA sends the first commit
/// message, handle_commit should be called first and initiate_sae should never be called.
fn initiate_sae(&mut self, sink: &mut SaeUpdateSink);
fn handle_commit(&mut self, sink: &mut SaeUpdateSink, commit_msg: &CommitMsg);
fn handle_confirm(&mut self, sink: &mut SaeUpdateSink, confirm_msg: &ConfirmMsg);
fn handle_anti_clogging_token(
&mut self,
sink: &mut SaeUpdateSink,
token: &AntiCloggingTokenMsg,
);
fn handle_timeout(&mut self, sink: &mut SaeUpdateSink, timeout: Timeout);
fn handle_frame(&mut self, sink: &mut SaeUpdateSink, frame: &AuthFrameRx) {
match frame::parse(frame) {
Ok(parse) => match parse {
frame::ParseSuccess::Commit(commit) => self.handle_commit(sink, &commit),
frame::ParseSuccess::Confirm(confirm) => self.handle_confirm(sink, &confirm),
frame::ParseSuccess::AntiCloggingToken(_act) => {
warn!("Anti-clogging tokens not yet supported");
}
},
Err(e) => warn!("Failed to parse SAE auth frame: {}", e),
}
}
}
/// Creates a new SAE handshake for the given group ID and authentication parameters.
pub fn new_sae_handshake(
group_id: u16,
akm: Akm,
password: Vec<u8>,
mac: MacAddr,
peer_mac: MacAddr,
) -> Result<Box<dyn SaeHandshake>, Error> {
let (h, cn) = match akm.suite_type {
akm::SAE | akm::FT_SAE => (h, cn),
_ => bail!("Cannot construct SAE handshake with AKM {:?}", akm),
};
let params = internal::SaeParameters { h, cn, password, sta_a_mac: mac, sta_b_mac: peer_mac };
match group_id {
DEFAULT_GROUP_ID => {
let group_constructor = Box::new(|| {
ecc::Group::new(EcGroupId::P256).map(|group| {
Box::new(group)
as Box<
dyn internal::FiniteCyclicGroup<
Element = <ecc::Group as internal::FiniteCyclicGroup>::Element,
>,
>
})
});
Ok(Box::new(state::SaeHandshakeImpl::new(group_constructor, params)?))
}
_ => bail!("Unsupported SAE group id: {}", group_id),
}
}
/// Creates a new SAE handshake in response to a first message from a peer, using the FCG indiated
/// by the peer if possible. In a successful handshake, this will immediately push a Commit and
/// Confirm to the given update sink.
pub fn join_sae_handshake(
sink: &mut SaeUpdateSink,
first_frame: &AuthFrameRx,
akm: Akm,
password: Vec<u8>,
mac: MacAddr,
peer_mac: MacAddr,
) -> Result<Box<dyn SaeHandshake>, Error> {
let parsed_frame = frame::parse(first_frame)?;
match parsed_frame {
frame::ParseSuccess::Commit(commit) => {
let mut handshake = new_sae_handshake(commit.group_id, akm, password, mac, peer_mac)?;
handshake.handle_commit(sink, &commit);
Ok(handshake)
}
_ => bail!("Recieved incorrect first frame of SAE handshake"),
}
}
// Internal mod for structs with mod-public visibility.
mod internal {
use super::*;
/// IEEE 802.11-2016 12.4.4
/// SAE may use many different finite cyclic groups (FCGs) to compute the various values used
/// during the handshake. This trait allows our SAE implementation to seamlessly handle
/// different classes of FCG. IEEE 802.11-2016 defines support for both elliptic curve groups
/// and finite field cryptography groups.
///
/// All functions provided by this trait will only return an Error when something internal has
/// gone wrong.
pub trait FiniteCyclicGroup {
/// Different classes of FCG have different Element types, but scalars can always be
/// represented by a Bignum.
type Element;
fn group_id(&self) -> u16;
/// IEEE 802.11-2016 12.4.3
/// Generates a new password element, a secret value shared by the two peers in SAE.
fn generate_pwe(&self, params: &SaeParameters) -> Result<Self::Element, Error>;
/// IEEE 12.4.4.1
/// These three operators are used to manipulate FCG elements for the purposes of the
/// Diffie-Hellman key exchange used by SAE.
fn scalar_op(
&self,
scalar: &Bignum,
element: &Self::Element,
) -> Result<Self::Element, Error>;
fn elem_op(
&self,
element1: &Self::Element,
element2: &Self::Element,
) -> Result<Self::Element, Error>;
fn inverse_op(&self, element: Self::Element) -> Result<Self::Element, Error>;
/// Returns the prime order of the FCG.
fn order(&self) -> Result<Bignum, Error>;
/// IEEE 802.11-2016 12.4.5.4
/// Maps the given secret element to the shared secret value. Returns None if this is the
/// identity element for this FCG, indicating that we have in invalid secret element.
fn map_to_secret_value(&self, element: &Self::Element) -> Result<Option<Bignum>, Error>;
/// IEEE 802.11-2016 12.4.2: The FCG Element must convert into an octet string such
/// that it may be included in the confirmation hash when completing SAE.
fn element_to_octets(&self, element: &Self::Element) -> Result<Vec<u8>, Error>;
/// Convert octets into an element. Returns None if the given octet string does not
/// contain a valid element for this group.
fn element_from_octets(&self, octets: &[u8]) -> Result<Option<Self::Element>, Error>;
/// Return the expected size of scalar and element values when serialized into a frame.
fn scalar_size(&self) -> Result<usize, Error> {
self.order().map(|order| order.len())
}
fn element_size(&self) -> Result<usize, Error>;
}
#[derive(Clone)]
pub struct SaeParameters {
// IEEE 802.11-2016 12.4.2: SAE theoretically supports arbitrary H and CN functions,
// although the standard only uses HMAC-SHA-256.
pub h: fn(salt: &[u8], ikm: &[u8]) -> Vec<u8>,
#[allow(unused)]
pub cn: fn(key: &[u8], counter: u16, data: Vec<&[u8]>) -> Vec<u8>,
// IEEE 802.11-2016 12.4.3
pub password: Vec<u8>,
// IEEE 802.11-2016 12.4.4.2.2: The two MacAddrs are needed for generating a password seed.
pub sta_a_mac: MacAddr,
pub sta_b_mac: MacAddr,
}
impl SaeParameters {
pub fn pwd_seed(&self, counter: u8) -> Vec<u8> {
let (big_mac, little_mac) = match self.sta_a_mac.cmp(&self.sta_b_mac) {
std::cmp::Ordering::Less => (self.sta_b_mac, self.sta_a_mac),
_ => (self.sta_a_mac, self.sta_b_mac),
};
let mut salt = vec![];
salt.extend_from_slice(&big_mac[..]);
salt.extend_from_slice(&little_mac[..]);
let mut ikm = self.password.clone();
ikm.push(counter);
(self.h)(&salt[..], &ikm[..])
}
}
}
fn h(salt: &[u8], ikm: &[u8]) -> Vec<u8> {
let mut hasher = hmac::HmacSha256::new(salt);
hasher.update(ikm);
hasher.finish().bytes().to_vec()
}
fn cn(key: &[u8], counter: u16, data: Vec<&[u8]>) -> Vec<u8> {
let mut hasher = hmac::HmacSha256::new(key);
hasher.update(&counter.to_le_bytes()[..]);
for data_part in data {
hasher.update(data_part);
}
hasher.finish().bytes().to_vec()
}
#[cfg(test)]
mod tests {
use {
super::{internal::*, *},
wlan_common::assert_variant,
};
// IEEE 802.11-2016 Annex J.10 SAE test vector
const TEST_PWD: &'static str = "thisisreallysecret";
const TEST_STA_A: MacAddr = [0x7b, 0x88, 0x56, 0x20, 0x2d, 0x8d];
const TEST_STA_B: MacAddr = [0xe2, 0x47, 0x1c, 0x0a, 0x5a, 0xcb];
const TEST_PWD_SEED: [u8; 32] = [
0x69, 0xf6, 0x90, 0x99, 0x83, 0x67, 0x53, 0x92, 0xd0, 0xa3, 0xa8, 0x82, 0x47, 0xff, 0xef,
0x20, 0x41, 0x3e, 0xe9, 0x72, 0x15, 0x87, 0x29, 0x42, 0x44, 0x15, 0xe1, 0x39, 0x46, 0xec,
0xc2, 0x06,
];
#[test]
fn pwd_seed() {
let params = SaeParameters {
h,
cn,
password: Vec::from(TEST_PWD),
sta_a_mac: TEST_STA_A,
sta_b_mac: TEST_STA_B,
};
let seed = params.pwd_seed(1);
assert_eq!(&seed[..], &TEST_PWD_SEED[..]);
}
#[test]
fn symmetric_pwd_seed() {
let params = SaeParameters {
h,
cn,
password: Vec::from(TEST_PWD),
// The password seed should not change depending on the order of mac addresses.
sta_a_mac: TEST_STA_B,
sta_b_mac: TEST_STA_A,
};
let seed = params.pwd_seed(1);
assert_eq!(&seed[..], &TEST_PWD_SEED[..]);
}
#[test]
fn bad_akm() {
let akm = AKM_PSK;
let res = new_sae_handshake(19, akm, Vec::from(TEST_PWD), TEST_STA_A, TEST_STA_B);
assert!(res.is_err());
assert!(format!("{}", res.err().unwrap())
.contains("Cannot construct SAE handshake with AKM 00-0F-AC:2"));
}
#[test]
fn bad_fcg() {
let akm = AKM_SAE;
let res = new_sae_handshake(200, akm, Vec::from(TEST_PWD), TEST_STA_A, TEST_STA_B);
assert!(res.is_err());
assert!(format!("{}", res.err().unwrap()).contains("Unsupported SAE group id: 200"));
}
struct TestHandshake {
sta1: Box<dyn SaeHandshake>,
sta2: Box<dyn SaeHandshake>,
}
// Helper structs for differentiating Commit/Confirm messages once they've been converted into
// generic auth frames.
#[derive(Clone, Eq, PartialEq, Debug)]
struct CommitTx(AuthFrameTx);
#[derive(Clone, Eq, PartialEq, Debug)]
struct ConfirmTx(AuthFrameTx);
struct CommitRx<'a>(AuthFrameRx<'a>);
struct ConfirmRx<'a>(AuthFrameRx<'a>);
fn to_rx(frame: &AuthFrameTx) -> AuthFrameRx {
AuthFrameRx { seq: frame.seq, status_code: frame.status_code, body: &frame.body[..] }
}
impl CommitTx {
fn to_rx(&self) -> CommitRx {
CommitRx(to_rx(&self.0))
}
}
impl ConfirmTx {
fn to_rx(&self) -> ConfirmRx {
ConfirmRx(to_rx(&self.0))
}
}
impl<'a> CommitRx<'a> {
fn msg(&'a self) -> CommitMsg<'a> {
assert_variant!(frame::parse(&self.0),
Ok(frame::ParseSuccess::Commit(commit)) => commit)
}
}
impl<'a> ConfirmRx<'a> {
fn msg(&'a self) -> ConfirmMsg<'a> {
assert_variant!(frame::parse(&self.0),
Ok(frame::ParseSuccess::Confirm(confirm)) => confirm)
}
}
fn expect_commit(sink: &mut Vec<SaeUpdate>) -> CommitTx {
let mut commit = assert_variant!(sink.remove(0), SaeUpdate::SendFrame(frame) => frame);
assert_variant!(frame::parse(&to_rx(&commit)), Ok(frame::ParseSuccess::Commit(msg)));
CommitTx(commit)
}
fn expect_confirm(sink: &mut Vec<SaeUpdate>) -> ConfirmTx {
let mut confirm = assert_variant!(sink.remove(0), SaeUpdate::SendFrame(frame) => frame);
assert_variant!(frame::parse(&to_rx(&confirm)), Ok(frame::ParseSuccess::Confirm(msg)));
ConfirmTx(confirm)
}
fn expect_reset_timeout(sink: &mut Vec<SaeUpdate>, timeout: Timeout) {
assert_variant!(sink.remove(0), SaeUpdate::ResetTimeout(timeout));
}
fn expect_cancel_timeout(sink: &mut Vec<SaeUpdate>, timeout: Timeout) {
assert_variant!(sink.remove(0), SaeUpdate::CancelTimeout(timeout));
}
// Test helper to advance through successful steps of an SAE handshake.
impl TestHandshake {
fn new() -> Self {
let akm = AKM_SAE;
let mut sta1 =
new_sae_handshake(19, akm.clone(), Vec::from(TEST_PWD), TEST_STA_A, TEST_STA_B)
.unwrap();
let mut sta2 =
new_sae_handshake(19, akm, Vec::from(TEST_PWD), TEST_STA_B, TEST_STA_A).unwrap();
Self { sta1, sta2 }
}
fn sta1_init(&mut self) -> CommitTx {
let mut sink = vec![];
self.sta1.initiate_sae(&mut sink);
assert_eq!(sink.len(), 2);
let commit = expect_commit(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
commit
}
fn sta2_handle_commit(&mut self, commit1: CommitRx) -> (CommitTx, ConfirmTx) {
let mut sink = vec![];
self.sta2.handle_commit(&mut sink, &commit1.msg());
assert_eq!(sink.len(), 3);
let commit2 = expect_commit(&mut sink);
let confirm2 = expect_confirm(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
(commit2, confirm2)
}
fn sta1_handle_commit(&mut self, commit2: CommitRx) -> ConfirmTx {
let mut sink = vec![];
self.sta1.handle_commit(&mut sink, &commit2.msg());
assert_eq!(sink.len(), 2);
let confirm1 = expect_confirm(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
confirm1
}
fn sta1_handle_confirm(&mut self, confirm2: ConfirmRx) -> Key {
Self::__internal_handle_confirm(&mut self.sta1, confirm2.msg())
}
fn sta2_handle_confirm(&mut self, confirm1: ConfirmRx) -> Key {
Self::__internal_handle_confirm(&mut self.sta2, confirm1.msg())
}
fn __internal_handle_confirm(sta: &mut Box<dyn SaeHandshake>, confirm: ConfirmMsg) -> Key {
let mut sink = vec![];
sta.handle_confirm(&mut sink, &confirm);
assert_eq!(sink.len(), 3);
expect_cancel_timeout(&mut sink, Timeout::Retransmission);
expect_reset_timeout(&mut sink, Timeout::KeyExpiration);
assert_variant!(sink.remove(0), SaeUpdate::Success(key) => key)
}
}
#[test]
fn sae_handshake_success() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
let key2 = handshake.sta2_handle_confirm(confirm1.to_rx());
assert_eq!(key1, key2);
}
#[test]
fn password_mismatch() {
let akm = AKM_SAE;
let mut sta1 =
new_sae_handshake(19, akm.clone(), Vec::from(TEST_PWD), TEST_STA_A, TEST_STA_B)
.unwrap();
let mut sta2 =
new_sae_handshake(19, akm, Vec::from("other_pwd"), TEST_STA_B, TEST_STA_A).unwrap();
let mut handshake = TestHandshake { sta1, sta2 };
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let mut sink1 = vec![];
handshake.sta1.handle_confirm(&mut sink1, &confirm2.to_rx().msg());
let mut sink2 = vec![];
handshake.sta2.handle_confirm(&mut sink2, &confirm1.to_rx().msg());
// The confirm is dropped both ways.
assert_eq!(sink1.len(), 0);
assert_eq!(sink2.len(), 0);
}
#[test]
fn retry_commit_on_unexpected_confirm() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
let mut sink = vec![];
handshake.sta1.handle_confirm(&mut sink, &confirm2.to_rx().msg());
assert_eq!(sink.len(), 2);
let commit1_retry = expect_commit(&mut sink);
assert_variant!(sink.remove(0), SaeUpdate::ResetTimeout(Timeout::Retransmission));
// We retransmit the same commit in response to a faulty confirm.
assert_eq!(commit1, commit1_retry);
}
#[test]
fn retry_commit_on_anti_clogging_token() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
// Simulate an anti-clogging token sent to sta1.
let mut sink = vec![];
let token = "anticloggingtokentext";
let token_msg = AntiCloggingTokenMsg { group_id: 19, token: token.as_bytes() };
handshake.sta1.handle_anti_clogging_token(&mut sink, &token_msg);
let commit1_retry = expect_commit(&mut sink);
assert_eq!(commit1_retry.clone().to_rx().msg().token, Some(token.as_bytes()));
// Finish the handshake.
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1_retry.to_rx());
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
let key2 = handshake.sta2_handle_confirm(confirm1.to_rx());
assert_eq!(key1, key2);
}
#[test]
fn ignore_wrong_confirm() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let mut sink = vec![];
let mut confirm2_wrong = ConfirmTx(frame::write_confirm(1, &[1; 32][..]));
handshake.sta1.handle_confirm(&mut sink, &confirm2_wrong.to_rx().msg());
assert_eq!(sink.len(), 0); // Ignored.
// STA1 should still be able to handle a subsequent correct confirm.
handshake.sta1_handle_confirm(confirm2.to_rx());
}
#[test]
fn handle_resent_commit() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
let (commit2_retry, confirm2_retry) = handshake.sta2_handle_commit(commit1.to_rx());
// The resent commit message should be unchanged, but the resent confirm should increment
// sc and produce a different value.
assert_eq!(commit2, commit2_retry);
assert_eq!(confirm2.to_rx().msg().send_confirm, 1);
assert_eq!(confirm2_retry.to_rx().msg().send_confirm, 2);
assert!(confirm2.to_rx().msg().confirm != confirm2_retry.to_rx().msg().confirm);
// Now complete the handshake.
let confirm1 = handshake.sta1_handle_commit(commit2_retry.to_rx());
let key1 = handshake.sta1_handle_confirm(confirm2_retry.to_rx());
let key2 = handshake.sta2_handle_confirm(confirm1.to_rx());
assert_eq!(key1, key2);
}
#[test]
fn completed_handshake_handles_resent_confirm() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
let (commit2_retry, confirm2_retry) = handshake.sta2_handle_commit(commit1.to_rx());
// Send STA1 the second confirm message first.
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let key1 = handshake.sta1_handle_confirm(confirm2.clone().to_rx());
// STA1 should respond to the second confirm with its own confirm.
let mut sink = vec![];
handshake.sta1.handle_confirm(&mut sink, &confirm2_retry.to_rx().msg());
assert_eq!(sink.len(), 1);
let confirm1_retry = expect_confirm(&mut sink);
assert!(confirm1.to_rx().msg().confirm != confirm1_retry.to_rx().msg().confirm);
assert_eq!(confirm1_retry.to_rx().msg().send_confirm, u16::max_value());
// STA2 should complete the handshake with the resent confirm.
let key2 = handshake.sta2_handle_confirm(confirm1_retry.to_rx());
assert_eq!(key1, key2);
// STA1 should silently drop either of our confirm frames now.
handshake.sta1.handle_confirm(&mut sink, &confirm2_retry.to_rx().msg());
assert!(sink.is_empty());
handshake.sta1.handle_confirm(&mut sink, &confirm2.to_rx().msg());
assert!(sink.is_empty());
// STA1 should also silently drop an incorrect confirm, even if send_confirm is incremented.
let confirm2_wrong = ConfirmMsg { send_confirm: 10, confirm: &[0xab; 32][..] };
handshake.sta1.handle_confirm(&mut sink, &confirm2_wrong);
assert!(sink.is_empty());
}
#[test]
fn completed_handshake_ignores_commit() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
handshake.sta1_handle_commit(commit2.to_rx());
handshake.sta1_handle_confirm(confirm2.clone().to_rx());
// STA1 has completed it's side of the handshake.
let mut sink = vec![];
handshake.sta1.handle_confirm(&mut sink, &confirm2.to_rx().msg());
assert!(sink.is_empty());
}
#[test]
fn bad_first_commit_rejects_auth() {
let mut handshake = TestHandshake::new();
let commit1_wrong = CommitMsg {
group_id: 19,
scalar: &[0xab; 32][..],
element: &[0xcd; 64][..],
token: None,
};
let mut sink = vec![];
handshake.sta1.handle_commit(&mut sink, &commit1_wrong);
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::AuthFailed));
}
#[test]
fn bad_second_commit_ignored() {
let mut handshake = TestHandshake::new();
let mut commit1 = handshake.sta1_init();
let (_commit1, _confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
let commit2_wrong = CommitMsg {
group_id: 19,
scalar: &[0xab; 32][..],
element: &[0xcd; 64][..],
token: None,
};
let mut sink = vec![];
handshake.sta1.handle_commit(&mut sink, &commit2_wrong);
assert_eq!(sink.len(), 0);
}
#[test]
fn reflected_commit_discarded() {
let mut handshake = TestHandshake::new();
let mut commit1 = handshake.sta1_init();
let mut sink = vec![];
handshake.sta1.handle_commit(&mut sink, &commit1.to_rx().msg());
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::ResetTimeout(Timeout::Retransmission));
}
#[test]
fn maximum_commit_retries() {
let mut handshake = TestHandshake::new();
let mut commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
// STA2 should allow MAX_RETRIES_PER_EXCHANGE retry operations before giving up.
for i in 0..MAX_RETRIES_PER_EXCHANGE {
let (commit2_retry, confirm2_retry) =
handshake.sta2_handle_commit(commit1.clone().to_rx());
assert_eq!(commit2, commit2_retry);
assert_eq!(confirm2_retry.to_rx().msg().send_confirm, i + 2);
}
// The last straw!
let mut sink = vec![];
handshake.sta2.handle_commit(&mut sink, &commit1.to_rx().msg());
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
}
#[test]
fn completed_exchange_fails_after_retries() {
let mut handshake = TestHandshake::new();
let mut commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
// STA2 should allow MAX_RETRIES_PER_EXCHANGE retry operations before giving up. We subtract 1
// here for the reason explained in the note below.
for i in 0..(MAX_RETRIES_PER_EXCHANGE - 1) {
let (commit2_retry, confirm2_retry) =
handshake.sta2_handle_commit(commit1.clone().to_rx());
assert_eq!(commit2, commit2_retry);
assert_eq!(confirm2_retry.to_rx().msg().send_confirm, i + 2);
}
let mut sink = vec![];
// Generate 3 different confirm messages for our testing...
let confirm1_sc1 = handshake.sta1_handle_commit(commit2.clone().to_rx());
handshake.sta1.handle_commit(&mut sink, &commit2.to_rx().msg());
assert_eq!(sink.len(), 3);
sink.remove(0);
let confirm1_sc2 = expect_confirm(&mut sink);
sink.clear();
let confirm1_invalid = ConfirmMsg { send_confirm: 3, confirm: &[0xab; 32][..] };
// STA2 completes the handshake. However, one more indication that STA1 is misbehaving will
// immediately kill the authentication.
handshake.sta2_handle_confirm(confirm1_sc1.clone().to_rx());
// NOTE: We run all of the operations here two times. This is because of a quirk in the SAE
// state machine: while only certain operations *increment* sync, all invalid operations
// will *check* sync. We can test whether sync is being incremented by running twice to see
// if this pushes us over the MAX_RETRIES_PER_EXCHANGE threshold.
// STA2 ignores commits.
handshake.sta2.handle_commit(&mut sink, &commit1.to_rx().msg());
handshake.sta2.handle_commit(&mut sink, &commit1.to_rx().msg());
assert_eq!(sink.len(), 0);
// STA2 ignores invalid confirm.
handshake.sta2.handle_confirm(&mut sink, &confirm1_invalid);
handshake.sta2.handle_confirm(&mut sink, &confirm1_invalid);
assert_eq!(sink.len(), 0);
// STA2 ignores old confirm.
handshake.sta2.handle_confirm(&mut sink, &confirm1_sc1.to_rx().msg());
handshake.sta2.handle_confirm(&mut sink, &confirm1_sc1.to_rx().msg());
assert_eq!(sink.len(), 0);
// But another valid confirm increments sync!
handshake.sta2.handle_confirm(&mut sink, &confirm1_sc2.to_rx().msg());
assert_eq!(sink.len(), 1);
expect_confirm(&mut sink);
handshake.sta2.handle_confirm(&mut sink, &confirm1_sc2.to_rx().msg());
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
}
#[test]
fn resend_commit_after_retransmission_timeout() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let mut sink = vec![];
handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
let commit1_retry = expect_commit(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
assert_eq!(commit1, commit1_retry);
}
#[test]
fn resend_confirm_after_retransmission_timeout() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
let mut sink = vec![];
handshake.sta2.handle_timeout(&mut sink, Timeout::Retransmission);
// On timeout we should only send commit and confirm.
let confirm2_retry = expect_confirm(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
assert_eq!(
confirm2.to_rx().msg().send_confirm + 1,
confirm2_retry.to_rx().msg().send_confirm
);
}
#[test]
fn abort_commit_after_too_many_timeouts() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let mut sink = vec![];
for i in 0..MAX_RETRIES_PER_EXCHANGE {
handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
let commit1_retry = expect_commit(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
assert_eq!(commit1, commit1_retry);
}
// This camel can't hold another straw!
handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
}
#[test]
fn abort_confirm_after_too_many_timeouts() {
let mut handshake = TestHandshake::new();
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.clone().to_rx());
let mut sink = vec![];
for i in 0..MAX_RETRIES_PER_EXCHANGE {
handshake.sta2.handle_timeout(&mut sink, Timeout::Retransmission);
// On timeout we should only send commit and confirm.
let confirm2_retry = expect_confirm(&mut sink);
expect_reset_timeout(&mut sink, Timeout::Retransmission);
assert_eq!(
confirm2.to_rx().msg().send_confirm + i + 1,
confirm2_retry.to_rx().msg().send_confirm
);
}
handshake.sta2.handle_timeout(&mut sink, Timeout::Retransmission);
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::TooManyRetries));
}
#[test]
fn ignore_unexpected_retransmit_timeout() {
let mut handshake = TestHandshake::new();
let mut sink = vec![];
// Timeout::Retransmission is ignored while in New state.
handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
assert!(sink.is_empty());
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
// Timeout::Retransmission is ignored while in Accepted state.
handshake.sta1.handle_timeout(&mut sink, Timeout::Retransmission);
assert!(sink.is_empty());
}
#[test]
fn fail_on_early_key_expiration() {
let mut handshake = TestHandshake::new();
handshake.sta1_init();
// Early key expiration indicates that something has gone very wrong, so we abort.
let mut sink = vec![];
handshake.sta1.handle_timeout(&mut sink, Timeout::KeyExpiration);
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::InternalError(_)));
}
#[test]
fn key_expiration_timeout() {
let mut handshake = TestHandshake::new();
// Timeout::KeyExpiration is only expected once our handshake has completed.
let commit1 = handshake.sta1_init();
let (commit2, confirm2) = handshake.sta2_handle_commit(commit1.to_rx());
let confirm1 = handshake.sta1_handle_commit(commit2.to_rx());
let key1 = handshake.sta1_handle_confirm(confirm2.to_rx());
let mut sink = vec![];
handshake.sta1.handle_timeout(&mut sink, Timeout::KeyExpiration);
assert_eq!(sink.len(), 1);
assert_variant!(sink.remove(0), SaeUpdate::Reject(RejectReason::KeyExpiration));
}
}