pakery_spake2plus/
transcript.rs1use alloc::vec::Vec;
4use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
5
6use pakery_core::crypto::{Hash, Kdf, Mac};
7use pakery_core::SharedSecret;
8
9use crate::ciphersuite::Spake2PlusCiphersuite;
10use crate::error::Spake2PlusError;
11
12#[derive(Zeroize, ZeroizeOnDrop)]
14pub struct Spake2PlusOutput {
15 #[zeroize(skip)]
17 pub session_key: SharedSecret,
18}
19
20impl Spake2PlusOutput {
21 #[must_use]
27 pub fn into_session_key(mut self) -> SharedSecret {
28 core::mem::replace(&mut self.session_key, SharedSecret::new(Vec::new()))
29 }
30}
31
32pub(crate) struct KeySchedule {
36 pub confirm_p: Vec<u8>,
37 pub confirm_v: Vec<u8>,
38 pub session_key: SharedSecret,
39}
40
41impl Zeroize for KeySchedule {
42 fn zeroize(&mut self) {
43 self.confirm_p.zeroize();
44 self.confirm_v.zeroize();
45 self.session_key.zeroize();
48 }
49}
50
51impl Drop for KeySchedule {
52 fn drop(&mut self) {
53 self.zeroize();
54 }
55}
56
57pub(crate) fn derive_key_schedule<C: Spake2PlusCiphersuite>(
66 tt: &[u8],
67 share_p: &[u8],
68 share_v: &[u8],
69) -> Result<KeySchedule, Spake2PlusError> {
70 const { assert!(<C::Hash as pakery_core::crypto::Hash>::OUTPUT_SIZE >= C::NH) };
72 let k_main = Zeroizing::new(C::Hash::digest(tt));
73
74 let prk = C::Kdf::extract(&[], &k_main[..C::NH]);
76
77 let kc = C::Kdf::expand(&prk, b"ConfirmationKeys", 2 * C::NH)
79 .map_err(|_| Spake2PlusError::InternalError("KDF expand failed for ConfirmationKeys"))?;
80 let k_confirm_p = &kc[..C::NH];
81 let k_confirm_v = &kc[C::NH..2 * C::NH];
82
83 let mut k_shared = C::Kdf::expand(&prk, b"SharedKey", C::NH)
85 .map_err(|_| Spake2PlusError::InternalError("KDF expand failed for SharedKey"))?;
86
87 let confirm_v = C::Mac::mac(k_confirm_v, share_p)
90 .map_err(|_| Spake2PlusError::InternalError("MAC computation failed"))?;
91 let confirm_p = C::Mac::mac(k_confirm_p, share_v)
92 .map_err(|_| Spake2PlusError::InternalError("MAC computation failed"))?;
93
94 Ok(KeySchedule {
95 confirm_p,
96 confirm_v,
97 session_key: SharedSecret::new(core::mem::take(&mut *k_shared)),
98 })
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use alloc::vec;
105
106 #[test]
111 fn output_zeroize_leaves_self_zeroizing_session_key() {
112 let mut output = Spake2PlusOutput {
113 session_key: SharedSecret::new(vec![0xAA; 32]),
114 };
115 output.zeroize();
116 assert_eq!(output.session_key.as_bytes(), &[0xAA; 32]);
118 }
119
120 #[test]
123 fn key_schedule_zeroize_clears_all_secret_fields() {
124 let mut ks = KeySchedule {
125 confirm_p: vec![0xAA; 64],
126 confirm_v: vec![0xBB; 64],
127 session_key: SharedSecret::new(vec![0xCC; 32]),
128 };
129 ks.zeroize();
130 assert!(ks.confirm_p.is_empty());
131 assert!(ks.confirm_v.is_empty());
132 assert!(ks.session_key.as_bytes().is_empty());
133 }
134}