use alloc::vec::Vec;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use pakery_core::crypto::{Hash, Kdf, Mac};
use pakery_core::SharedSecret;
use crate::ciphersuite::Spake2Ciphersuite;
use crate::error::Spake2Error;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct Spake2Output {
#[zeroize(skip)]
pub session_key: SharedSecret,
pub confirmation_mac: Vec<u8>,
expected_peer_mac: Vec<u8>,
}
impl Spake2Output {
pub fn verify_peer_confirmation(&self, peer_mac: &[u8]) -> Result<(), Spake2Error> {
if self.expected_peer_mac.ct_eq(peer_mac).into() {
Ok(())
} else {
Err(Spake2Error::ConfirmationFailed)
}
}
#[must_use]
pub fn into_session_key(mut self) -> SharedSecret {
core::mem::replace(&mut self.session_key, SharedSecret::new(Vec::new()))
}
#[must_use]
pub fn into_confirmation_mac(mut self) -> Vec<u8> {
core::mem::take(&mut self.confirmation_mac)
}
}
pub fn derive_key_schedule<C: Spake2Ciphersuite>(
tt: &[u8],
aad: &[u8],
is_party_a: bool,
) -> Result<Spake2Output, Spake2Error> {
const { assert!(<C::Hash as pakery_core::crypto::Hash>::OUTPUT_SIZE >= C::NH) };
let hash_tt = Zeroizing::new(C::Hash::digest(tt));
let half = C::NH / 2;
let ke = &hash_tt[..half];
let ka = &hash_tt[half..C::NH];
let prk = C::Kdf::extract(&[], ka);
let mut info = Vec::from(b"ConfirmationKeys" as &[u8]);
info.extend_from_slice(aad);
let kc = C::Kdf::expand(&prk, &info, C::NH)
.map_err(|_| Spake2Error::InternalError("KDF expand failed"))?;
let kc_a = &kc[..half];
let kc_b = &kc[half..C::NH];
let mac_a =
C::Mac::mac(kc_a, tt).map_err(|_| Spake2Error::InternalError("MAC computation failed"))?;
let mac_b =
C::Mac::mac(kc_b, tt).map_err(|_| Spake2Error::InternalError("MAC computation failed"))?;
let session_key = SharedSecret::new(ke.to_vec());
if is_party_a {
Ok(Spake2Output {
session_key,
confirmation_mac: mac_a,
expected_peer_mac: mac_b,
})
} else {
Ok(Spake2Output {
session_key,
confirmation_mac: mac_b,
expected_peer_mac: mac_a,
})
}
}