securedrop_protocol_minimal/primitives/
x25519.rs1use alloc::string::String;
2
3use crate::primitives::provider::{self, curve25519::ecdh};
4use anyhow::Error;
5use rand_core::{CryptoRng, RngCore};
6use serde::de::Error as _;
7
8pub const DH_PUBLIC_KEY_LEN: usize = crate::primitives::provider::curve25519::PK_LEN;
9pub(crate) const DH_PRIVATE_KEY_LEN: usize = crate::primitives::provider::curve25519::SK_LEN;
10pub(crate) const DH_SHARED_SECRET_LEN: usize =
11 crate::primitives::provider::curve25519::LEN_DH_SHARE;
12
13#[derive(Debug, Clone, Copy)]
15pub struct DHPublicKey([u8; DH_PUBLIC_KEY_LEN]);
16
17impl DHPublicKey {
18 pub fn into_bytes(self) -> [u8; DH_PUBLIC_KEY_LEN] {
19 self.0
20 }
21
22 pub fn from_bytes(bytes: [u8; DH_PUBLIC_KEY_LEN]) -> Self {
23 Self(bytes)
24 }
25}
26
27#[cfg_attr(hax, hax_lib::exclude)]
28impl serde::Serialize for DHPublicKey {
29 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
30 ser.serialize_str(&hex::encode(self.0))
31 }
32}
33
34#[cfg_attr(hax, hax_lib::exclude)]
35impl<'de> serde::Deserialize<'de> for DHPublicKey {
36 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
37 let s = String::deserialize(de)?;
38 let mut bytes = [0u8; DH_PUBLIC_KEY_LEN];
39 hex::decode_to_slice(s.trim(), &mut bytes).map_err(D::Error::custom)?;
40 Ok(Self::from_bytes(bytes))
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct DHPrivateKey([u8; DH_PRIVATE_KEY_LEN]);
47
48impl DHPrivateKey {
49 pub fn as_bytes(&self) -> &[u8; DH_PRIVATE_KEY_LEN] {
50 &self.0
51 }
52
53 pub fn into_bytes(self) -> [u8; DH_PRIVATE_KEY_LEN] {
54 self.0
55 }
56
57 pub fn from_bytes(bytes: [u8; DH_PRIVATE_KEY_LEN]) -> Self {
58 Self(bytes)
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct DHSharedSecret([u8; 32]);
65
66impl DHSharedSecret {
67 pub fn into_bytes(self) -> [u8; 32] {
68 self.0
69 }
70
71 pub fn from_bytes(bytes: [u8; 32]) -> Self {
72 Self(bytes)
73 }
74}
75
76pub fn deterministic_dh_keygen(randomness: [u8; 32]) -> Result<(DHPrivateKey, DHPublicKey), Error> {
79 let mut public_key = [0u8; DH_PUBLIC_KEY_LEN];
80 let mut secret_key = [0u8; DH_PRIVATE_KEY_LEN];
81
82 provider::curve25519::x25519_keygen(&mut public_key, &mut secret_key, &randomness)
83 .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
84
85 Ok((DHPrivateKey(secret_key), DHPublicKey(public_key)))
86}
87
88pub fn generate_dh_keypair<R: RngCore + CryptoRng>(
90 rng: &mut R,
91) -> Result<(DHPrivateKey, DHPublicKey), Error> {
92 let mut randomness = [0u8; 32];
93 provider::rng::fill_bytes(rng, &mut randomness);
94
95 let mut public_key = [0u8; DH_PUBLIC_KEY_LEN];
96 let mut secret_key = [0u8; DH_PRIVATE_KEY_LEN];
97
98 provider::curve25519::x25519_keygen(&mut public_key, &mut secret_key, &randomness)
101 .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
102
103 typed(secret_key, public_key)
104}
105
106#[cfg_attr(hax, hax_lib::ensures(|result| result.is_ok()))]
109fn typed(
110 sk: [u8; DH_PRIVATE_KEY_LEN],
111 pk: [u8; DH_PUBLIC_KEY_LEN],
112) -> Result<(DHPrivateKey, DHPublicKey), Error> {
113 Ok((DHPrivateKey(sk), DHPublicKey(pk)))
114}
115
116pub fn generate_random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Result<[u8; 32], Error> {
118 let mut randomness = [0u8; 32];
119 provider::rng::fill_bytes(rng, &mut randomness);
120
121 let mut secret_key = [0u8; 32];
122 let mut _public_key = [0u8; 32]; provider::curve25519::x25519_keygen(&mut _public_key, &mut secret_key, &randomness)
127 .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
128
129 Ok(secret_key)
130}
131
132pub fn dh_public_key_from_scalar(scalar: [u8; 32]) -> DHPublicKey {
137 let mut public_key_bytes = [0u8; 32];
138 provider::curve25519::secret_to_public(&mut public_key_bytes, &scalar);
139 DHPublicKey::from_bytes(public_key_bytes)
140}
141
142pub fn dh_shared_secret(
144 public_key: &DHPublicKey,
145 private_scalar: [u8; 32],
146) -> Result<DHSharedSecret, Error> {
147 let mut shared_secret_bytes = [0u8; 32];
148 ecdh(&mut shared_secret_bytes, &public_key.0, &private_scalar)
149 .map_err(|_| anyhow::anyhow!("X25519 DH failed"))?;
150 Ok(DHSharedSecret(shared_secret_bytes))
151}
152
153#[cfg(test)]
154mod tests {
155 use crate::primitives::provider::curve25519::LEN_DH_SHARE;
156
157 use super::*;
158 use proptest::prelude::*;
159 use rand_chacha::ChaCha20Rng;
160 use rand_core::SeedableRng;
161
162 fn get_rng() -> ChaCha20Rng {
164 let mut seed = [0u8; 32];
165 getrandom::fill(&mut seed).expect("OS random source failed");
166 ChaCha20Rng::from_seed(seed)
167 }
168
169 #[test]
170 fn test_deterministic_dh_keygen() {
171 proptest!(|(randomness in proptest::array::uniform32(any::<u8>()))| {
172 let (private_key, public_key) = deterministic_dh_keygen(randomness).unwrap();
173 });
174 }
175
176 #[test]
177 fn test_dh_shared_secret() {
178 let mut rng = get_rng();
179
180 let (sk1, pk1) = generate_dh_keypair(&mut rng).expect("need dh keygen");
181
182 let (sk2, pk2) = generate_dh_keypair(&mut rng).expect("need dh keygen");
183
184 let ss1 = dh_shared_secret(&pk1, sk2.into_bytes()).expect("need shared secret 1");
185 let ss2 = dh_shared_secret(&pk2, sk1.into_bytes()).expect("need shared secret 2");
186
187 assert_eq!(ss1.clone().into_bytes(), ss2.into_bytes());
188 assert_ne!(ss1.into_bytes(), [0u8; LEN_DH_SHARE])
189 }
190}