pakery_spake2/
transcript.rs1use alloc::vec::Vec;
4use subtle::ConstantTimeEq;
5use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
6
7use pakery_core::crypto::{Hash, Kdf, Mac};
8use pakery_core::SharedSecret;
9
10use crate::ciphersuite::Spake2Ciphersuite;
11use crate::error::Spake2Error;
12
13#[derive(Zeroize, ZeroizeOnDrop)]
17pub struct Spake2Output {
18 #[zeroize(skip)]
20 pub session_key: SharedSecret,
21 pub confirmation_mac: Vec<u8>,
23 expected_peer_mac: Vec<u8>,
25}
26
27impl Spake2Output {
28 pub fn verify_peer_confirmation(&self, peer_mac: &[u8]) -> Result<(), Spake2Error> {
30 if self.expected_peer_mac.ct_eq(peer_mac).into() {
31 Ok(())
32 } else {
33 Err(Spake2Error::ConfirmationFailed)
34 }
35 }
36}
37
38pub fn derive_key_schedule<C: Spake2Ciphersuite>(
46 tt: &[u8],
47 aad: &[u8],
48 is_party_a: bool,
49) -> Result<Spake2Output, Spake2Error> {
50 const { assert!(<C::Hash as pakery_core::crypto::Hash>::OUTPUT_SIZE >= C::NH) };
52 let hash_tt = Zeroizing::new(C::Hash::digest(tt));
53 let half = C::NH / 2;
54 let ke = &hash_tt[..half];
55 let ka = &hash_tt[half..C::NH];
56
57 let prk = C::Kdf::extract(&[], ka);
59
60 let mut info = Vec::from(b"ConfirmationKeys" as &[u8]);
62 info.extend_from_slice(aad);
63 let kc = C::Kdf::expand(&prk, &info, C::NH)
64 .map_err(|_| Spake2Error::InternalError("KDF expand failed"))?;
65 let kc_a = &kc[..half];
66 let kc_b = &kc[half..C::NH];
67
68 let mac_a =
70 C::Mac::mac(kc_a, tt).map_err(|_| Spake2Error::InternalError("MAC computation failed"))?;
71 let mac_b =
72 C::Mac::mac(kc_b, tt).map_err(|_| Spake2Error::InternalError("MAC computation failed"))?;
73
74 let session_key = SharedSecret::new(ke.to_vec());
75
76 if is_party_a {
77 Ok(Spake2Output {
78 session_key,
79 confirmation_mac: mac_a,
80 expected_peer_mac: mac_b,
81 })
82 } else {
83 Ok(Spake2Output {
84 session_key,
85 confirmation_mac: mac_b,
86 expected_peer_mac: mac_a,
87 })
88 }
89}