blob: 5b437324b6d3d4aeb62c315ee19c15434b01ce13 [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.
use {
super::{
boringssl::{Bignum, BignumCtx},
frame::{write_commit, write_confirm},
internal::FiniteCyclicGroup,
internal::SaeParameters,
AntiCloggingTokenMsg, CommitMsg, ConfirmMsg, Key, RejectReason, SaeHandshake, SaeUpdate,
SaeUpdateSink, Timeout,
},
crate::crypto_utils::kdf_sha256,
anyhow::{bail, format_err, Error},
log::{error, warn},
wlan_statemachine::*,
};
// TODO(fxbug.dev/42140): Handle received timeouts.
// TODO(fxbug.dev/42562): Handle BadGrp/DiffGrp.
// TODO(fxbug.dev/42563): Handle frame status.
/// We store an FcgConstructor rather than a FiniteCyclicGroup so that our handshake
/// can impl `Send`. FCGs are not generally `Send`, so we construct them on the fly.
type FcgConstructor<E> =
Box<dyn Fn() -> Result<Box<dyn FiniteCyclicGroup<Element = E>>, Error> + Send + 'static>;
struct SaeConfiguration<E> {
fcg: FcgConstructor<E>,
params: SaeParameters,
pwe: Vec<u8>,
}
struct Commit<E> {
scalar: Bignum,
element: E,
}
struct SerializedCommit {
scalar: Vec<u8>,
element: Vec<u8>,
}
#[derive(Debug, PartialEq)]
struct Kck(Vec<u8>);
impl<E> Commit<E> {
/// IEEE 802.11-2016 12.4.7.4
/// Returns the serialized scalar and element with appropriate padding as needed.
fn serialize(&self, config: &SaeConfiguration<E>) -> Result<SerializedCommit, Error> {
let fcg = (config.fcg)()?;
let scalar_size = fcg.scalar_size()?;
let scalar = self.scalar.to_left_padded_vec(scalar_size);
let element = fcg.element_to_octets(&self.element)?;
Ok(SerializedCommit { scalar, element })
}
}
impl SerializedCommit {
fn deserialize<E>(&self, config: &SaeConfiguration<E>) -> Result<Commit<E>, Error> {
let fcg = (config.fcg)()?;
let scalar = Bignum::new_from_slice(&self.scalar[..])?;
let element = match fcg.element_from_octets(&self.element)? {
Some(element) => element,
None => bail!("Attempted to deserialize invalid FCG element"),
};
Ok(Commit { scalar, element })
}
}
struct SaeNew<E> {
config: SaeConfiguration<E>,
}
struct SaeCommitted<E> {
config: SaeConfiguration<E>,
rand: Vec<u8>,
commit: SerializedCommit,
sync: u16,
anti_clogging_token: Vec<u8>,
}
struct SaeConfirmed<E> {
config: SaeConfiguration<E>,
commit: SerializedCommit,
peer_commit: SerializedCommit,
kck: Kck,
key: Key,
sc: u16, // send confirm
rc: u16, // receive confirm
sync: u16,
}
// Everything is finished in this state. We keep around the old SaeConfirmed struct in case we need
// to replay our confirm frame.
struct SaeAccepted<E>(SaeConfirmed<E>);
struct SaeFailed;
statemachine!(
enum SaeHandshakeState<E>,
() => SaeNew<E>,
SaeNew<E> => [SaeCommitted<E>, SaeConfirmed<E>, SaeFailed],
// SaeCommitted does not self-loop because a retry does not update any state.
SaeCommitted<E> => [SaeConfirmed<E>, SaeFailed],
// SaeConfirmed can self-loop because retries increment send_confirm.
SaeConfirmed<E> => [SaeAccepted<E>, SaeFailed, SaeConfirmed<E>],
SaeAccepted<E> => SaeFailed,
);
/// This enum is used in any place where an operation can either succeed, or result in silently
/// dropping the received frame.
enum FrameResult<T> {
/// The frame was processed and the given output produced as a result.
Proceed(T),
/// The frame was incorrect and should be dropped silently.
Drop,
}
impl<T> std::fmt::Debug for FrameResult<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Proceed(_) => write!(f, "FrameResult::Proceed"),
Self::Drop => write!(f, "FrameResult::Drop"),
}
}
}
/// IEEE 802.11-2016 12.4.5.4
/// Returns the calculated pairwise key and peer commit, or None if the given peer element is
/// invalid.
fn process_commit<E>(
config: &SaeConfiguration<E>,
rand: &Bignum,
commit: &Commit<E>,
peer_scalar: &[u8],
peer_element: &[u8],
) -> Result<FrameResult<(Commit<E>, Kck, Key)>, RejectReason> {
let fcg = (config.fcg)()?;
// Parse the peer element.
let peer_commit = match fcg.element_from_octets(peer_element)? {
Some(element) => Commit { scalar: Bignum::new_from_slice(peer_scalar)?, element },
None => return Ok(FrameResult::Drop),
};
let pwe = fcg.element_from_octets(&config.pwe)?.ok_or(format_err!("Could not unwrap PWE"))?;
let element_k = fcg.scalar_op(
rand,
&fcg.elem_op(&fcg.scalar_op(&peer_commit.scalar, &pwe)?, &peer_commit.element)?,
)?;
let k = match fcg.map_to_secret_value(&element_k)? {
Some(k) => k,
None => return Ok(FrameResult::Drop), // This is an auth failure.
};
let ctx = BignumCtx::new()?;
let keyseed = (config.params.h)(&[0u8; 32][..], &k.to_vec()[..]);
let sha_ctx = peer_commit.scalar.mod_add(&commit.scalar, &fcg.order()?, &ctx)?.to_vec();
let kck_and_pmk = kdf_sha256(&keyseed[..], "SAE KCK and PMK", &sha_ctx[..], 512);
let kck = kck_and_pmk[0..32].to_vec();
let pmk = kck_and_pmk[32..64].to_vec();
let pmkid = sha_ctx[0..16].to_vec();
Ok(FrameResult::Proceed((peer_commit, Kck(kck), Key { pmk, pmkid })))
}
/// IEEE 802.11-2016 12.4.5.{5,6}
/// Computes the confirm value for sending or validating a confirm message. This can only fail from
/// an internal error.
fn compute_confirm<E>(
config: &SaeConfiguration<E>,
kck: &Kck,
send_confirm: u16,
commit1: &SerializedCommit,
commit2: &SerializedCommit,
) -> Result<Vec<u8>, RejectReason> {
Ok((config.params.cn)(
&kck.0[..],
send_confirm,
vec![&commit1.scalar[..], &commit1.element[..], &commit2.scalar[..], &commit2.element[..]],
))
}
/// Helper function to reject the authentication after too many retries.
fn check_sync(sync: &u16) -> Result<(), RejectReason> {
// IEEE says we should only fail if sync exceeds our limit, but failing on equality as well gives
// MAX_RETRIES_PER_EXCHANGE slightly more obvious behavior.
if *sync >= super::MAX_RETRIES_PER_EXCHANGE {
Err(RejectReason::TooManyRetries)
} else {
Ok(())
}
}
/// IEEE 802.11-2016 12.4.5.3
impl<E> SaeNew<E> {
fn commit(&self) -> Result<(Vec<u8>, SerializedCommit), Error> {
let fcg = (self.config.fcg)()?;
let order = fcg.order()?;
let ctx = BignumCtx::new()?;
let (rand, mask, scalar) = loop {
// 2 < rand < order
let rand = Bignum::rand(&order.sub(Bignum::new_from_u64(3)?)?)?
.add(Bignum::new_from_u64(3)?)?;
// 1 < mask < rand
let mask = Bignum::rand(&rand.sub(Bignum::new_from_u64(2)?)?)?
.add(Bignum::new_from_u64(2)?)?;
let commit_scalar = rand.mod_add(&mask, &order, &ctx)?;
if !commit_scalar.is_zero() && !commit_scalar.is_one() {
break (rand, mask, commit_scalar);
}
};
let pwe = fcg
.element_from_octets(&self.config.pwe)?
.ok_or(format_err!("Could not unwrap PWE"))?;
let element = fcg.inverse_op(fcg.scalar_op(&mask, &pwe)?)?;
Ok((rand.to_vec(), Commit { scalar, element }.serialize(&self.config)?))
}
fn send_first_commit(
&self,
sink: &mut SaeUpdateSink,
) -> Result<(Vec<u8>, SerializedCommit), RejectReason> {
let (rand, commit) = self.commit()?;
let group_id = (self.config.fcg)()?.group_id();
sink.push(SaeUpdate::SendFrame(write_commit(
group_id,
&commit.scalar[..],
&commit.element[..],
&[],
)));
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
Ok((rand, commit))
}
fn handle_commit(
&self,
sink: &mut SaeUpdateSink,
commit_msg: &CommitMsg,
) -> Result<(Vec<u8>, SerializedCommit, SerializedCommit, Kck, Key), RejectReason> {
let (serialized_rand, serialized_commit) = self.commit()?;
let commit = serialized_commit.deserialize(&self.config)?;
let rand = Bignum::new_from_slice(&serialized_rand[..])?;
let (peer_commit, kck, key) = match process_commit(
&self.config,
&rand,
&commit,
&commit_msg.scalar[..],
&commit_msg.element[..],
)? {
FrameResult::Proceed(res) => res,
// If we drop the first frame, reject the authentication immediately.
FrameResult::Drop => return Err(RejectReason::AuthFailed),
};
let peer_commit = peer_commit.serialize(&self.config)?;
let confirm = compute_confirm(&self.config, &kck, 1, &serialized_commit, &peer_commit)?;
// We do not send our own commit message unless we process the peer's successfully.
let group_id = (self.config.fcg)()?.group_id();
sink.push(SaeUpdate::SendFrame(write_commit(
group_id,
&serialized_commit.scalar[..],
&serialized_commit.element[..],
&[],
)));
sink.push(SaeUpdate::SendFrame(write_confirm(1, &confirm[..])));
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
Ok((serialized_rand, serialized_commit, peer_commit, kck, key))
}
}
/// IEEE 802.11-2016 12.4.8.6.4
impl<E> SaeCommitted<E> {
fn handle_commit(
&self,
sink: &mut SaeUpdateSink,
commit_msg: &CommitMsg,
) -> Result<FrameResult<(SerializedCommit, Kck, Key)>, RejectReason> {
if &commit_msg.scalar[..] == &self.commit.scalar[..]
&& &commit_msg.element[..] == &self.commit.element[..]
{
// This is a reflection attack.
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
return Ok(FrameResult::Drop);
}
let (peer_commit, kck, key) = match process_commit(
&self.config,
&Bignum::new_from_slice(&self.rand[..])?,
&self.commit.deserialize(&self.config)?,
&commit_msg.scalar[..],
&commit_msg.element[..],
)? {
FrameResult::Proceed(res) => res,
// IEEE doesn't specify that we do anything in this case. It might make sense to reset
// the retransmission timer, but we stick with the spec.
FrameResult::Drop => return Ok(FrameResult::Drop),
};
let peer_commit = peer_commit.serialize(&self.config)?;
let confirm = compute_confirm(&self.config, &kck, 1, &self.commit, &peer_commit)?;
sink.push(SaeUpdate::SendFrame(write_confirm(1, &confirm[..])));
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
Ok(FrameResult::Proceed((peer_commit, kck, key)))
}
fn resend_last_frame(&mut self, sink: &mut SaeUpdateSink) -> Result<(), RejectReason> {
check_sync(&self.sync)?;
self.sync += 1;
// We resend our last commit.
let group_id = (self.config.fcg)()?.group_id();
sink.push(SaeUpdate::SendFrame(write_commit(
group_id,
&self.commit.scalar[..],
&self.commit.element[..],
&self.anti_clogging_token[..],
)));
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
Ok(())
}
fn handle_confirm(
&mut self,
sink: &mut SaeUpdateSink,
_confirm_msg: &ConfirmMsg,
) -> Result<(), RejectReason> {
self.resend_last_frame(sink)
}
fn handle_anti_clogging_token(
&mut self,
sink: &mut SaeUpdateSink,
token: &AntiCloggingTokenMsg,
) -> Result<(), RejectReason> {
self.anti_clogging_token = token.token.to_vec();
self.resend_last_frame(sink)
}
fn handle_timeout(
&mut self,
sink: &mut SaeUpdateSink,
timeout: Timeout,
) -> Result<(), RejectReason> {
match timeout {
Timeout::Retransmission => self.resend_last_frame(sink),
Timeout::KeyExpiration => {
Err(format_err!("Unexpected key expiration timout before PMKSA established.")
.into())
}
}
}
}
/// IEEE 802.11-2016 12.4.8.6.5
impl<E> SaeConfirmed<E> {
fn handle_commit(
&mut self,
sink: &mut SaeUpdateSink,
_commit_msg: &CommitMsg,
) -> Result<(), RejectReason> {
// The peer did not receive our previous commit or confirm.
check_sync(&self.sync)?;
// IEEE Std 802.11 does *not* specify that we verify the peer sent the same commit. We just
// assume this to be the case.
self.sync += 1;
self.sc += 1;
let confirm =
compute_confirm(&self.config, &self.kck, self.sc, &self.commit, &self.peer_commit)?;
let group_id = (self.config.fcg)()?.group_id();
sink.push(SaeUpdate::SendFrame(write_commit(
group_id,
&self.commit.scalar[..],
&self.commit.element[..],
&[],
)));
sink.push(SaeUpdate::SendFrame(write_confirm(self.sc, &confirm[..])));
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
Ok(())
}
fn handle_confirm(
&mut self,
sink: &mut SaeUpdateSink,
confirm_msg: &ConfirmMsg,
) -> Result<FrameResult<()>, RejectReason> {
let verifier = compute_confirm(
&self.config,
&self.kck,
confirm_msg.send_confirm,
&self.peer_commit,
&self.commit,
)?;
if confirm_msg.confirm == &verifier[..] {
sink.push(SaeUpdate::CancelTimeout(Timeout::Retransmission));
sink.push(SaeUpdate::ResetTimeout(Timeout::KeyExpiration));
self.rc = confirm_msg.send_confirm;
// We use u16::max_value() where IEEE specifies 2^16 - 1.
self.sc = u16::max_value();
sink.push(SaeUpdate::Success(self.key.clone()));
Ok(FrameResult::Proceed(()))
} else {
Ok(FrameResult::Drop)
}
}
fn handle_timeout(
&mut self,
sink: &mut SaeUpdateSink,
timeout: Timeout,
) -> Result<(), RejectReason> {
match timeout {
Timeout::Retransmission => {
// Resend our confirm message.
check_sync(&self.sync)?;
self.sync += 1;
self.sc += 1;
let confirm = compute_confirm(
&self.config,
&self.kck,
self.sc,
&self.commit,
&self.peer_commit,
)?;
sink.push(SaeUpdate::SendFrame(write_confirm(self.sc, &confirm[..])));
sink.push(SaeUpdate::ResetTimeout(Timeout::Retransmission));
Ok(())
}
Timeout::KeyExpiration => {
Err(format_err!("Unexpected key expiration timout before PMKSA established.")
.into())
}
}
}
}
/// IEEE 802.11-2016 12.4.8.6.6
impl<E> SaeAccepted<E> {
// This function does not return a FrameResult because there is no state transition in the
// successful case.
fn handle_confirm(
&mut self,
sink: &mut SaeUpdateSink,
confirm_msg: &ConfirmMsg,
) -> Result<(), RejectReason> {
check_sync(&mut self.0.sync)?;
// We use u16::max_value() where IEEE specifies 2^16 - 1.
if confirm_msg.send_confirm <= self.0.rc || confirm_msg.send_confirm == u16::max_value() {
return Ok(());
}
// If we fail to verify, the message is dropped silently.
if let Ok(verifier) = compute_confirm(
&self.0.config,
&self.0.kck,
confirm_msg.send_confirm,
&self.0.peer_commit,
&self.0.commit,
) {
if verifier == &confirm_msg.confirm[..] {
self.0.rc = confirm_msg.send_confirm;
self.0.sync += 1;
let confirm = compute_confirm(
&self.0.config,
&self.0.kck,
self.0.sc,
&self.0.commit,
&self.0.peer_commit,
)?;
sink.push(SaeUpdate::SendFrame(write_confirm(self.0.sc, &confirm[..])));
}
}
Ok(())
}
fn handle_timeout(
&mut self,
sink: &mut SaeUpdateSink,
timeout: Timeout,
) -> Result<(), RejectReason> {
match timeout {
Timeout::Retransmission => {
// This is weird, but probably shouldn't kill our PMKSA.
error!("Unexpected retransmission timeout after completed SAE handshake.");
Ok(())
}
Timeout::KeyExpiration => Err(RejectReason::KeyExpiration),
}
}
}
impl<E> SaeHandshakeState<E> {
fn initiate_sae(self, sink: &mut SaeUpdateSink) -> Self {
match self {
SaeHandshakeState::SaeNew(state) => match state.send_first_commit(sink) {
Ok((rand, commit)) => {
let (transition, state) = state.release_data();
transition
.to(SaeCommitted {
config: state.config,
rand,
commit,
sync: 0,
anti_clogging_token: vec![],
})
.into()
}
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
},
_ => {
error!("Unexpected call to initiate_sae");
self
}
}
}
fn handle_commit(self, sink: &mut SaeUpdateSink, commit_msg: &CommitMsg) -> Self {
match self {
SaeHandshakeState::SaeNew(state) => {
match state.handle_commit(sink, commit_msg) {
Ok((rand, commit, peer_commit, kck, key)) => {
let (transition, state) = state.release_data();
transition
.to(SaeConfirmed {
config: state.config,
commit,
peer_commit,
kck,
key,
sc: 1,
rc: 0,
sync: 0,
})
.into()
}
// We always reject the authentication if the first commit is invalid.
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
SaeHandshakeState::SaeCommitted(state) => match state.handle_commit(sink, commit_msg) {
Ok(FrameResult::Proceed((peer_commit, kck, key))) => {
let (transition, committed) = state.release_data();
let confirmed = SaeConfirmed {
config: committed.config,
commit: committed.commit,
peer_commit,
kck,
key,
sc: 1,
rc: 0,
sync: committed.sync,
};
transition.to(confirmed).into()
}
Ok(FrameResult::Drop) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
},
SaeHandshakeState::SaeConfirmed(mut state) => {
match state.handle_commit(sink, commit_msg) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
_ => {
warn!("Unexpected SAE commit received");
self
}
}
}
fn handle_confirm(self, sink: &mut SaeUpdateSink, confirm_msg: &ConfirmMsg) -> Self {
match self {
SaeHandshakeState::SaeCommitted(mut state) => {
match state.handle_confirm(sink, confirm_msg) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
SaeHandshakeState::SaeConfirmed(mut state) => {
match state.handle_confirm(sink, confirm_msg) {
Ok(FrameResult::Proceed(())) => {
let (transition, mut state) = state.release_data();
transition.to(SaeAccepted(state)).into()
}
Ok(FrameResult::Drop) => state.into(),
Err(e) => {
sink.push(SaeUpdate::Reject(e.into()));
state.transition_to(SaeFailed).into()
}
}
}
SaeHandshakeState::SaeAccepted(mut state) => {
match state.handle_confirm(sink, confirm_msg) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
_ => {
warn!("Unexpected SAE confirm received");
self
}
}
}
fn handle_anti_clogging_token(
self,
sink: &mut SaeUpdateSink,
token: &AntiCloggingTokenMsg,
) -> Self {
match self {
SaeHandshakeState::SaeCommitted(mut state) => {
match state.handle_anti_clogging_token(sink, token) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
_ => {
error!("Unexpected anti clogging token received");
self
}
}
}
fn handle_timeout(self, sink: &mut SaeUpdateSink, timeout: Timeout) -> Self {
match self {
SaeHandshakeState::SaeCommitted(mut state) => {
match state.handle_timeout(sink, timeout) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
SaeHandshakeState::SaeConfirmed(mut state) => {
match state.handle_timeout(sink, timeout) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
SaeHandshakeState::SaeAccepted(mut state) => {
match state.handle_timeout(sink, timeout) {
Ok(()) => state.into(),
Err(reject) => {
sink.push(SaeUpdate::Reject(reject));
state.transition_to(SaeFailed).into()
}
}
}
_ => {
error!("Unexpected SAE timeout triggered");
self
}
}
}
}
pub struct SaeHandshakeImpl<E>(StateMachine<SaeHandshakeState<E>>);
impl<E> SaeHandshakeImpl<E> {
pub fn new(fcg_constructor: FcgConstructor<E>, params: SaeParameters) -> Result<Self, Error> {
let fcg = fcg_constructor()?;
let pwe = fcg.element_to_octets(&fcg.generate_pwe(&params)?)?;
Ok(Self(StateMachine::new(SaeHandshakeState::from(State::new(SaeNew {
config: SaeConfiguration { fcg: fcg_constructor, params, pwe },
})))))
}
}
impl<E> SaeHandshake for SaeHandshakeImpl<E> {
fn initiate_sae(&mut self, sink: &mut SaeUpdateSink) {
self.0.replace_state(|state| state.initiate_sae(sink));
}
fn handle_commit(&mut self, sink: &mut SaeUpdateSink, commit_msg: &CommitMsg) {
self.0.replace_state(|state| state.handle_commit(sink, commit_msg));
}
fn handle_confirm(&mut self, sink: &mut SaeUpdateSink, confirm_msg: &ConfirmMsg) {
self.0.replace_state(|state| state.handle_confirm(sink, confirm_msg));
}
fn handle_anti_clogging_token(
&mut self,
sink: &mut SaeUpdateSink,
token: &AntiCloggingTokenMsg,
) {
self.0.replace_state(|state| state.handle_anti_clogging_token(sink, token));
}
fn handle_timeout(&mut self, sink: &mut SaeUpdateSink, timeout: Timeout) {
self.0.replace_state(|state| state.handle_timeout(sink, timeout));
}
}
// Most testing is done in sae/mod.rs, so we only test internal functions here.
#[cfg(test)]
mod test {
use {
super::*,
crate::{
self as sae,
boringssl::{Bignum, EcGroupId},
ecc,
},
hex::FromHex,
wlan_common::{assert_variant, mac::MacAddr},
};
// IEEE 802.11-18/1104r0: "New Test Vectors for SAE" provides all of these values.
const TEST_GROUP: EcGroupId = EcGroupId::P256;
const TEST_PWD: &'static str = "mekmitasdigoatpsk4internet";
const TEST_STA_A: MacAddr = [0x82, 0x7b, 0x91, 0x9d, 0xd4, 0xb9];
const TEST_RAND_A: &'static str =
"a906f61e4d3a5d4eb2965ff34cf917dd044445c878c17ca5d5b93786da9f83cf";
const TEST_SCALAR_A: &'static str =
"eb3bab1964e4a0ab05925ddf3339519138bc65d6cdc0f813dd6fd4344eb4bfe4";
const TEST_ELEMENT_A: &'static str = "4b5c21597658f4e3eddfb4b99f25b4d6540f32ff1fd5c530c60a794448610bc6de3d92bdbbd47d935980ca6cf8988ab6630be6764c885ceb9793970f695217ee";
const TEST_CONFIRM_A: &'static str =
"12d9d5c78c500526d36c41dbc56aedf2914cedddd7cad4a58c48f83dbde9fc77";
const TEST_STA_B: MacAddr = [0x1e, 0xec, 0x49, 0xea, 0x64, 0x88];
const TEST_RAND_B: &'static str =
"a47d07bbd3d1b618b325dfde02413a450a90fd1ee1ac35f4d3856cc9cb77128c";
const TEST_SCALAR_B: &'static str =
"5564f045b2ea1e566cf1dd741f70d9be35d2df5b9a5502946ee03cf8dae27e1e";
const TEST_ELEMENT_B: &'static str = "05b8430eb7a99e24877ce69baf3dc580e309633d6b385f83ee1c3ec3591f1a5393c06e805ddceb2fde50930dd7cfebb987c6ff9666af164eb5184d8e6662ed6a";
const TEST_CONFIRM_B: &'static str =
"02871cf906898b8060ec184143be77b8c08a8019b13eb6d0aef0d8383dfac2fd";
const KEY_PMK: &'static str =
"7aead86fba4c3221fc437f5f14d70d854ea5d5aac1690116793081eda4d557c5";
const KEY_KCK: &'static str =
"599d6f1e27548be8499dceed2feccf94818ce1c79f1b4eb3d6a53228a09bf3ed";
const KEY_PMKID: &'static str = "40a09b6017cebf0072843b5352aa2b4f";
fn make_ecc_config() -> SaeConfiguration<<ecc::Group as FiniteCyclicGroup>::Element> {
let params = SaeParameters {
h: sae::h,
cn: sae::cn,
password: Vec::from(TEST_PWD),
sta_a_mac: TEST_STA_A,
sta_b_mac: TEST_STA_B,
};
let fcg_constructor = Box::new(|| {
ecc::Group::new(TEST_GROUP).map(|group| {
Box::new(group)
as Box<
dyn FiniteCyclicGroup<Element = <ecc::Group as FiniteCyclicGroup>::Element>,
>
})
});
let fcg = (fcg_constructor)().unwrap();
let pwe = fcg.element_to_octets(&fcg.generate_pwe(&params).unwrap()).unwrap();
SaeConfiguration { fcg: fcg_constructor, params, pwe }
}
fn make_commit<E>(config: &SaeConfiguration<E>, scalar: &str, element: &str) -> Commit<E> {
let scalar = Bignum::new_from_slice(&Vec::from_hex(scalar).unwrap()[..]).unwrap();
let element = (config.fcg)()
.unwrap()
.element_from_octets(&Vec::from_hex(element).unwrap()[..])
.unwrap()
.unwrap();
Commit { scalar, element }
}
fn expected_kck() -> Kck {
Kck(Vec::from_hex(KEY_KCK).unwrap())
}
fn expected_key() -> Key {
Key { pmk: Vec::from_hex(KEY_PMK).unwrap(), pmkid: Vec::from_hex(KEY_PMKID).unwrap() }
}
#[test]
fn process_commit_success_sta_a() {
let config = make_ecc_config();
let commit_a = make_commit(&config, TEST_SCALAR_A, TEST_ELEMENT_A);
let rand_a = Bignum::new_from_slice(&Vec::from_hex(TEST_RAND_A).unwrap()[..]).unwrap();
let scalar_b = Vec::from_hex(TEST_SCALAR_B).unwrap();
let element_b = Vec::from_hex(TEST_ELEMENT_B).unwrap();
let result =
process_commit(&config, &rand_a, &commit_a, &scalar_b[..], &element_b[..]).unwrap();
let (_peer_commit_a, kck, key) = assert_variant!(result, FrameResult::Proceed(res) => res);
assert_eq!(kck, expected_kck());
assert_eq!(key, expected_key());
}
#[test]
fn process_commit_success_sta_b() {
let config = make_ecc_config();
let commit_b = make_commit(&config, TEST_SCALAR_B, TEST_ELEMENT_B);
let rand_b = Bignum::new_from_slice(&Vec::from_hex(TEST_RAND_B).unwrap()[..]).unwrap();
let scalar_a = Vec::from_hex(TEST_SCALAR_A).unwrap();
let element_a = Vec::from_hex(TEST_ELEMENT_A).unwrap();
let result =
process_commit(&config, &rand_b, &commit_b, &scalar_a[..], &element_a[..]).unwrap();
let (_peer_commit_b, kck, key) = assert_variant!(result, FrameResult::Proceed(res) => res);
assert_eq!(kck, expected_kck());
assert_eq!(key, expected_key());
}
#[test]
fn process_commit_fails_bad_peer_element() {
let config = make_ecc_config();
let commit_a = make_commit(&config, TEST_SCALAR_A, TEST_ELEMENT_A);
let rand_a = Bignum::new_from_slice(&Vec::from_hex(TEST_RAND_A).unwrap()[..]).unwrap();
let scalar_b = Vec::from_hex(TEST_SCALAR_B).unwrap();
let mut element_b = Vec::from_hex(TEST_ELEMENT_B).unwrap();
element_b[0] += 1;
let result =
process_commit(&config, &rand_a, &commit_a, &scalar_b[..], &element_b[..]).unwrap();
assert_variant!(result, FrameResult::Drop);
}
#[test]
fn test_compute_confirm() {
let config = make_ecc_config();
let commit_a =
make_commit(&config, TEST_SCALAR_A, TEST_ELEMENT_A).serialize(&config).unwrap();
let commit_b =
make_commit(&config, TEST_SCALAR_B, TEST_ELEMENT_B).serialize(&config).unwrap();
let kck = expected_kck();
let confirm_a = compute_confirm(&config, &kck, 1, &commit_a, &commit_b).unwrap();
let expected_confirm_a = Vec::from_hex(TEST_CONFIRM_A).unwrap();
assert_eq!(confirm_a, expected_confirm_a);
let confirm_b = compute_confirm(&config, &kck, 1, &commit_b, &commit_a).unwrap();
let expected_confirm_b = Vec::from_hex(TEST_CONFIRM_B).unwrap();
assert_eq!(confirm_b, expected_confirm_b);
}
}