Skip to main content

securedrop_protocol_minimal/
primitives.rs

1use alloc::vec::Vec;
2use anyhow::Error;
3use rand_core::{CryptoRng, RngCore};
4
5pub(crate) mod dh_akem;
6pub(crate) mod mlkem;
7pub mod pad;
8pub mod x25519;
9pub(crate) mod xwing;
10
11/// Fixed number of message ID entries to return in privacy-preserving fetch
12///
13/// This prevents traffic analysis by always returning the same number of entries,
14/// regardless of how many actual messages exist.
15pub const MESSAGE_ID_FETCH_SIZE: usize = 10;
16
17// TODO: aesgcm256 for consistency with other methods
18/// Symmetric encryption for message IDs using ChaCha20-Poly1305
19///
20/// This is used in step 7 for encrypting message IDs with a shared secret
21pub fn encrypt_message_id<R: RngCore + CryptoRng>(
22    key: &[u8],
23    message_id: &[u8],
24    rng: &mut R,
25) -> Result<Vec<u8>, Error> {
26    use libcrux_chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
27
28    if key.len() != KEY_LEN {
29        return Err(anyhow::anyhow!("Invalid key length"));
30    }
31
32    // Generate a random nonce with supplied rng
33    let mut nonce = [0u8; NONCE_LEN];
34    rng.fill_bytes(&mut nonce);
35
36    // Prepare output buffer: nonce + ciphertext + tag
37    let mut output = alloc::vec::Vec::new();
38    output.extend_from_slice(&nonce);
39
40    let mut ciphertext = alloc::vec![0u8; message_id.len() + TAG_LEN];
41    let key_array: [u8; KEY_LEN] = key
42        .try_into()
43        .map_err(|_| anyhow::anyhow!("Key length mismatch"))?;
44
45    // Encrypt the message ID
46    libcrux_chacha20poly1305::encrypt(
47        &key_array,
48        message_id,
49        &mut ciphertext,
50        &[], // empty AAD
51        &nonce,
52    )
53    .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 encryption failed: {:?}", e))?;
54
55    output.extend_from_slice(&ciphertext);
56    Ok(output)
57}
58
59/// Symmetric decryption for message IDs using ChaCha20-Poly1305
60///
61/// This is used in step 7 for decrypting message IDs with a shared secret
62pub fn decrypt_message_id(key: &[u8], encrypted_data: &[u8]) -> Result<Vec<u8>, Error> {
63    use libcrux_chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
64
65    if key.len() != KEY_LEN {
66        return Err(anyhow::anyhow!("Invalid key length"));
67    }
68
69    if encrypted_data.len() < NONCE_LEN + TAG_LEN {
70        return Err(anyhow::anyhow!("Encrypted data too short"));
71    }
72
73    // Extract nonce and ciphertext
74    let nonce: [u8; NONCE_LEN] = encrypted_data[..NONCE_LEN]
75        .try_into()
76        .map_err(|_| anyhow::anyhow!("Nonce extraction failed"))?;
77    let ciphertext = &encrypted_data[NONCE_LEN..];
78
79    // Prepare output buffer
80    let mut plaintext = alloc::vec![0u8; ciphertext.len() - TAG_LEN];
81    let key_array: [u8; KEY_LEN] = key
82        .try_into()
83        .map_err(|_| anyhow::anyhow!("Key length mismatch"))?;
84
85    // Decrypt the message ID
86    libcrux_chacha20poly1305::decrypt(
87        &key_array,
88        &mut plaintext,
89        ciphertext,
90        &[], // empty AAD
91        &nonce,
92    )
93    .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 decryption failed: {:?}", e))?;
94
95    Ok(plaintext)
96}