securedrop_protocol_minimal/
metadata.rs1use alloc::vec::Vec;
19use anyhow::Error;
20use hpke_rs::{
21 Hpke, Mode, hpke_types::AeadAlgorithm::Aes256Gcm, hpke_types::KdfAlgorithm::HkdfSha256,
22 hpke_types::KemAlgorithm::XWingDraft06, libcrux::HpkeLibcrux,
23};
24use rand_core::{CryptoRng, RngCore};
25
26use crate::constants::LEN_XWING_SHAREDSECRET_ENCAPS;
27use crate::primitives::xwing::{XWingPrivateKey, XWingPublicKey, generate_xwing_keypair};
28
29#[derive(Debug, Clone)]
31pub struct MetadataPublicKey(pub(crate) XWingPublicKey);
32
33pub struct MetadataPrivateKey(pub(crate) XWingPrivateKey);
35
36pub struct MetadataKeyPair {
38 sk: MetadataPrivateKey,
39 pk: MetadataPublicKey,
40}
41
42impl MetadataKeyPair {
43 pub fn public_key(&self) -> &MetadataPublicKey {
45 &self.pk
46 }
47
48 pub fn private_key(&self) -> &MetadataPrivateKey {
50 &self.sk
51 }
52}
53
54#[derive(Debug, Clone)]
57pub struct MetadataCiphertext {
58 pub(crate) c: [u8; LEN_XWING_SHAREDSECRET_ENCAPS],
60 pub(crate) cp: Vec<u8>,
62}
63
64impl MetadataCiphertext {
65 pub fn len(&self) -> usize {
67 self.c.len() + self.cp.len()
68 }
69}
70
71pub fn keygen<R: RngCore + CryptoRng>(rng: &mut R) -> Result<MetadataKeyPair, anyhow::Error> {
77 let (sk_s, pk_s) = generate_xwing_keypair(rng)?;
78 Ok(MetadataKeyPair {
79 sk: MetadataPrivateKey(sk_s),
80 pk: MetadataPublicKey(pk_s),
81 })
82}
83
84pub(crate) fn deterministic_keygen(randomness: [u8; 32]) -> Result<MetadataKeyPair, anyhow::Error> {
93 use crate::primitives::xwing::deterministic_keygen as xwing_derand;
94 let (sk_s, pk_s) = xwing_derand(randomness)?;
95 Ok(MetadataKeyPair {
96 sk: MetadataPrivateKey(sk_s),
97 pk: MetadataPublicKey(pk_s),
98 })
99}
100
101impl MetadataPublicKey {
102 pub fn as_bytes(&self) -> &[u8] {
104 self.0.as_bytes()
105 }
106}
107
108impl MetadataPrivateKey {
109 #[cfg(test)]
111 pub(crate) fn as_bytes(&self) -> &[u8] {
112 self.0.as_bytes()
113 }
114}
115
116pub fn encrypt(pk_r: &MetadataPublicKey, m: &[u8]) -> MetadataCiphertext {
120 let mut hpke = Hpke::<HpkeLibcrux>::new(Mode::Base, XWingDraft06, HkdfSha256, Aes256Gcm);
121 let pk_r_hpke = pk_r.0.clone().into();
122
123 let (c_vec, cp) = hpke
125 .seal(&pk_r_hpke, b"", b"", m, None, None, None)
126 .expect("SD-PKE encryption failed");
127
128 let c: [u8; LEN_XWING_SHAREDSECRET_ENCAPS] = c_vec
130 .try_into()
131 .expect("X-Wing encapsulation output has unexpected length");
132
133 MetadataCiphertext { c, cp }
134}
135
136pub fn decrypt(sk_r: &MetadataPrivateKey, ct: &MetadataCiphertext) -> Result<Vec<u8>, Error> {
142 let hpke = Hpke::<HpkeLibcrux>::new(Mode::Base, XWingDraft06, HkdfSha256, Aes256Gcm);
143 let sk_r_hpke = sk_r.0.clone().into();
144
145 hpke.open(&ct.c, &sk_r_hpke, b"", b"", &ct.cp, None, None, None)
146 .map_err(|e| anyhow::anyhow!("SD-PKE decryption failed: {:?}", e))
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use proptest::prelude::*;
153 use rand_chacha::ChaCha20Rng;
154 use rand_core::SeedableRng;
155
156 fn get_rng() -> ChaCha20Rng {
157 let mut seed = [0u8; 32];
158 getrandom::fill(&mut seed).expect("OS random source failed");
159 ChaCha20Rng::from_seed(seed)
160 }
161
162 proptest! {
163 #[test]
164 fn test_metadata_encrypt_decrypt_roundtrip(m in proptest::collection::vec(any::<u8>(), 0..200)) {
165 let mut rng = get_rng();
166 let kp = keygen(&mut rng).expect("KGen failed");
167
168 let ct = encrypt(kp.public_key(), &m);
169 let decrypted = decrypt(kp.private_key(), &ct).expect("Decryption failed");
170
171 prop_assert_eq!(m, decrypted);
172 }
173 }
174
175 #[test]
176 fn test_metadata_decrypt_wrong_key_fails() {
177 let mut rng = get_rng();
178 let kp = keygen(&mut rng).expect("KGen failed");
179 let wrong_kp = keygen(&mut rng).expect("KGen failed");
180
181 let ct = encrypt(kp.public_key(), b"some sender apke key bytes");
182 assert!(decrypt(wrong_kp.private_key(), &ct).is_err());
183 }
184}