huddle_protocol/crypto/
code_join.rs1use hkdf::Hkdf;
12use sha2::Sha256;
13use x25519_dalek::{PublicKey, StaticSecret};
14
15use crate::crypto::passphrase::KEY_LEN;
16
17const CODE_JOIN_INFO: &[u8] = b"huddle-code-join-v1";
20
21pub fn derive_wrap_key(our_secret: &StaticSecret, their_pub: &PublicKey) -> [u8; KEY_LEN] {
25 let shared = our_secret.diffie_hellman(their_pub);
26 let hk = Hkdf::<Sha256>::new(None, shared.as_bytes());
27 let mut wrap_key = [0u8; KEY_LEN];
28 hk.expand(CODE_JOIN_INFO, &mut wrap_key)
29 .expect("32 bytes is within HKDF-SHA256's output limit");
30 wrap_key
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use rand::rngs::OsRng;
37
38 #[test]
39 fn both_sides_derive_the_same_wrap_key() {
40 let owner = StaticSecret::random_from_rng(OsRng);
41 let joiner = StaticSecret::random_from_rng(OsRng);
42 let owner_pub = PublicKey::from(&owner);
43 let joiner_pub = PublicKey::from(&joiner);
44 let k_owner = derive_wrap_key(&owner, &joiner_pub);
46 let k_joiner = derive_wrap_key(&joiner, &owner_pub);
47 assert_eq!(k_owner, k_joiner, "ECDH is commutative -> same wrap key");
48 }
49
50 #[test]
51 fn different_peers_derive_different_keys() {
52 let owner = StaticSecret::random_from_rng(OsRng);
53 let a = PublicKey::from(&StaticSecret::random_from_rng(OsRng));
54 let b = PublicKey::from(&StaticSecret::random_from_rng(OsRng));
55 assert_ne!(derive_wrap_key(&owner, &a), derive_wrap_key(&owner, &b));
56 }
57}