1use zeroize::Zeroize;
47
48use crate::entropy::{self, EntropySnapshot};
49use crate::error::{KkError, Result};
50use crate::kk_mix;
51
52const EKA_SESSION_INFO: &[u8] = b"KK-EKA-session";
54
55#[derive(Clone)]
59pub struct EkaMsg1 {
60 pub commit: [u8; 32],
61}
62
63impl EkaMsg1 {
64 pub const BYTES: usize = 32;
65
66 pub fn to_bytes(&self) -> Vec<u8> {
67 self.commit.to_vec()
68 }
69
70 pub fn from_bytes(data: &[u8]) -> Result<Self> {
71 if data.len() < Self::BYTES {
72 return Err(KkError::InvalidPacket("EkaMsg1 too short".into()));
73 }
74 let mut commit = [0u8; 32];
75 commit.copy_from_slice(&data[..32]);
76 Ok(Self { commit })
77 }
78}
79
80#[derive(Clone)]
82pub struct EkaMsg2 {
83 pub entropy_b_bytes: [u8; 48],
84 pub auth_b: [u8; 32],
85}
86
87impl EkaMsg2 {
88 pub const BYTES: usize = 80;
89
90 pub fn to_bytes(&self) -> Vec<u8> {
91 let mut out = Vec::with_capacity(Self::BYTES);
92 out.extend_from_slice(&self.entropy_b_bytes);
93 out.extend_from_slice(&self.auth_b);
94 out
95 }
96
97 pub fn from_bytes(data: &[u8]) -> Result<Self> {
98 if data.len() < Self::BYTES {
99 return Err(KkError::InvalidPacket("EkaMsg2 too short".into()));
100 }
101 let mut entropy_b_bytes = [0u8; 48];
102 entropy_b_bytes.copy_from_slice(&data[..48]);
103 let mut auth_b = [0u8; 32];
104 auth_b.copy_from_slice(&data[48..80]);
105 Ok(Self {
106 entropy_b_bytes,
107 auth_b,
108 })
109 }
110}
111
112#[derive(Clone)]
114pub struct EkaMsg3 {
115 pub entropy_a_bytes: [u8; 48],
116 pub auth_a: [u8; 32],
117}
118
119impl EkaMsg3 {
120 pub const BYTES: usize = 80;
121
122 pub fn to_bytes(&self) -> Vec<u8> {
123 let mut out = Vec::with_capacity(Self::BYTES);
124 out.extend_from_slice(&self.entropy_a_bytes);
125 out.extend_from_slice(&self.auth_a);
126 out
127 }
128
129 pub fn from_bytes(data: &[u8]) -> Result<Self> {
130 if data.len() < Self::BYTES {
131 return Err(KkError::InvalidPacket("EkaMsg3 too short".into()));
132 }
133 let mut entropy_a_bytes = [0u8; 48];
134 entropy_a_bytes.copy_from_slice(&data[..48]);
135 let mut auth_a = [0u8; 32];
136 auth_a.copy_from_slice(&data[48..80]);
137 Ok(Self {
138 entropy_a_bytes,
139 auth_a,
140 })
141 }
142}
143
144pub struct EkaInitiator {
152 psk: Vec<u8>,
153 #[allow(dead_code)] entropy_a: EntropySnapshot,
155 entropy_a_serialized: Vec<u8>,
156 commit_a: [u8; 32],
157}
158
159impl Drop for EkaInitiator {
160 fn drop(&mut self) {
161 self.psk.zeroize();
162 self.entropy_a_serialized.zeroize();
163 self.commit_a.zeroize();
164 }
166}
167
168impl EkaInitiator {
169 pub fn new(psk: &[u8]) -> Result<(Self, EkaMsg1)> {
174 let entropy_a = entropy::gather()?;
175 Self::new_with_entropy(psk, entropy_a)
176 }
177
178 #[doc(hidden)]
182 pub fn new_with_entropy(psk: &[u8], entropy_a: EntropySnapshot) -> Result<(Self, EkaMsg1)> {
183 let entropy_a_serialized = entropy_a.to_bytes();
184 let commit_a = kk_mix::kk_hash(&entropy_a_serialized);
185
186 let msg1 = EkaMsg1 { commit: commit_a };
187 let state = Self {
188 psk: psk.to_vec(),
189 entropy_a,
190 entropy_a_serialized,
191 commit_a,
192 };
193 Ok((state, msg1))
194 }
195
196 pub fn process_msg2(self, msg2: &EkaMsg2) -> Result<(EkaMsg3, [u8; 32])> {
202 let mut auth_b_message = Vec::with_capacity(48 + 32);
204 auth_b_message.extend_from_slice(&msg2.entropy_b_bytes);
205 auth_b_message.extend_from_slice(&self.commit_a);
206
207 if !kk_mix::kk_mac_verify(&self.psk, &auth_b_message, &msg2.auth_b) {
208 return Err(KkError::CommitmentMismatch);
209 }
210
211 let mut auth_a_message = Vec::with_capacity(48 + 48);
213 auth_a_message.extend_from_slice(&self.entropy_a_serialized);
214 auth_a_message.extend_from_slice(&msg2.entropy_b_bytes);
215 let auth_a = kk_mix::kk_mac(&self.psk, &auth_a_message);
216
217 let mut entropy_a_bytes = [0u8; 48];
218 entropy_a_bytes.copy_from_slice(&self.entropy_a_serialized);
219
220 let msg3 = EkaMsg3 {
221 entropy_a_bytes,
222 auth_a,
223 };
224
225 let mut salt = Vec::with_capacity(48 + 48);
227 salt.extend_from_slice(&self.entropy_a_serialized);
228 salt.extend_from_slice(&msg2.entropy_b_bytes);
229 let session_key_vec = kk_mix::kk_kdf(&self.psk, &salt, EKA_SESSION_INFO, 32);
230 let mut session_key = [0u8; 32];
231 session_key.copy_from_slice(&session_key_vec);
232
233 Ok((msg3, session_key))
234 }
235}
236
237pub struct EkaResponder {
245 psk: Vec<u8>,
246 entropy_b_serialized: Vec<u8>,
247 commit_a: [u8; 32],
248}
249
250impl Drop for EkaResponder {
251 fn drop(&mut self) {
252 self.psk.zeroize();
253 self.entropy_b_serialized.zeroize();
254 self.commit_a.zeroize();
255 }
256}
257
258impl EkaResponder {
259 pub fn new(psk: &[u8], msg1: &EkaMsg1) -> Result<(Self, EkaMsg2)> {
264 let entropy_b = entropy::gather()?;
265 Self::new_with_entropy(psk, msg1, entropy_b)
266 }
267
268 #[doc(hidden)]
272 pub fn new_with_entropy(
273 psk: &[u8],
274 msg1: &EkaMsg1,
275 entropy_b: EntropySnapshot,
276 ) -> Result<(Self, EkaMsg2)> {
277 let entropy_b_serialized = entropy_b.to_bytes();
278
279 let mut auth_b_message = Vec::with_capacity(48 + 32);
281 auth_b_message.extend_from_slice(&entropy_b_serialized);
282 auth_b_message.extend_from_slice(&msg1.commit);
283
284 let auth_b = kk_mix::kk_mac(psk, &auth_b_message);
285
286 let mut entropy_b_bytes = [0u8; 48];
287 entropy_b_bytes.copy_from_slice(&entropy_b_serialized);
288
289 let msg2 = EkaMsg2 {
290 entropy_b_bytes,
291 auth_b,
292 };
293
294 let state = Self {
295 psk: psk.to_vec(),
296 entropy_b_serialized,
297 commit_a: msg1.commit,
298 };
299 Ok((state, msg2))
300 }
301
302 pub fn process_msg3(self, msg3: &EkaMsg3) -> Result<[u8; 32]> {
308 let recomputed_commit = kk_mix::kk_hash(&msg3.entropy_a_bytes);
310 if recomputed_commit != self.commit_a {
311 return Err(KkError::CommitmentMismatch);
312 }
313
314 let mut auth_a_message = Vec::with_capacity(48 + 48);
316 auth_a_message.extend_from_slice(&msg3.entropy_a_bytes);
317 auth_a_message.extend_from_slice(&self.entropy_b_serialized);
318 if !kk_mix::kk_mac_verify(&self.psk, &auth_a_message, &msg3.auth_a) {
319 return Err(KkError::CommitmentMismatch);
320 }
321
322 let mut salt = Vec::with_capacity(48 + 48);
324 salt.extend_from_slice(&msg3.entropy_a_bytes);
325 salt.extend_from_slice(&self.entropy_b_serialized);
326 let session_key_vec = kk_mix::kk_kdf(&self.psk, &salt, EKA_SESSION_INFO, 32);
327 let mut session_key = [0u8; 32];
328 session_key.copy_from_slice(&session_key_vec);
329
330 Ok(session_key)
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn eka_happy_path_live_entropy() {
340 let psk = b"test-psk-for-eka";
341
342 let (alice, msg1) = EkaInitiator::new(psk).unwrap();
344
345 let (bob, msg2) = EkaResponder::new(psk, &msg1).unwrap();
347
348 let (msg3, alice_key) = alice.process_msg2(&msg2).unwrap();
350
351 let bob_key = bob.process_msg3(&msg3).unwrap();
353
354 assert_eq!(alice_key, bob_key);
356 assert_ne!(alice_key, [0u8; 32]);
358 }
359
360 #[test]
361 fn eka_wire_format_sizes() {
362 assert_eq!(EkaMsg1::BYTES, 32);
363 assert_eq!(EkaMsg2::BYTES, 80);
364 assert_eq!(EkaMsg3::BYTES, 80);
365
366 let psk = b"size-test";
367 let (_, msg1) = EkaInitiator::new(psk).unwrap();
368 assert_eq!(msg1.to_bytes().len(), 32);
369
370 let (_, msg2) = EkaResponder::new(psk, &msg1).unwrap();
371 assert_eq!(msg2.to_bytes().len(), 80);
372 }
373
374 #[test]
375 fn eka_msg_roundtrip() {
376 let psk = b"roundtrip-test";
377 let (alice, msg1) = EkaInitiator::new(psk).unwrap();
378
379 let msg1_bytes = msg1.to_bytes();
381 let msg1_restored = EkaMsg1::from_bytes(&msg1_bytes).unwrap();
382 assert_eq!(msg1.commit, msg1_restored.commit);
383
384 let (bob, msg2) = EkaResponder::new(psk, &msg1).unwrap();
386 let msg2_bytes = msg2.to_bytes();
387 let msg2_restored = EkaMsg2::from_bytes(&msg2_bytes).unwrap();
388 assert_eq!(msg2.entropy_b_bytes, msg2_restored.entropy_b_bytes);
389 assert_eq!(msg2.auth_b, msg2_restored.auth_b);
390
391 let (msg3, _) = alice.process_msg2(&msg2).unwrap();
393 let msg3_bytes = msg3.to_bytes();
394 let msg3_restored = EkaMsg3::from_bytes(&msg3_bytes).unwrap();
395 assert_eq!(msg3.entropy_a_bytes, msg3_restored.entropy_a_bytes);
396 assert_eq!(msg3.auth_a, msg3_restored.auth_a);
397
398 let key = bob.process_msg3(&msg3_restored).unwrap();
400 assert_ne!(key, [0u8; 32]);
401 }
402
403 #[test]
404 fn eka_different_sessions_different_keys() {
405 let psk = b"same-psk";
406
407 let (alice1, msg1_a) = EkaInitiator::new(psk).unwrap();
408 let (bob1, msg2_a) = EkaResponder::new(psk, &msg1_a).unwrap();
409 let (msg3_a, key_a) = alice1.process_msg2(&msg2_a).unwrap();
410 let _ = bob1.process_msg3(&msg3_a).unwrap();
411
412 let (alice2, msg1_b) = EkaInitiator::new(psk).unwrap();
413 let (bob2, msg2_b) = EkaResponder::new(psk, &msg1_b).unwrap();
414 let (msg3_b, key_b) = alice2.process_msg2(&msg2_b).unwrap();
415 let _ = bob2.process_msg3(&msg3_b).unwrap();
416
417 assert_ne!(
418 key_a, key_b,
419 "different sessions must derive different keys"
420 );
421 }
422}