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