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/// Symmetric encryption for message IDs using ChaCha20-Poly1305
19///
20/// This is used in step 7 for encrypting message IDs with a shared secret
21///
22#[cfg_attr(hax, hax_lib::requires(
23    message_id.len()
24        <= usize::MAX
25            - provider::chacha20poly1305::NONCE_LEN
26            - provider::chacha20poly1305::TAG_LEN
27))]
28pub fn encrypt_message_id<R: RngCore + CryptoRng>(
29    key: &[u8],
30    message_id: &[u8],
31    rng: &mut R,
32) -> Result<Vec<u8>, Error> {
33    use provider::chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
34
35    if key.len() != KEY_LEN {
36        return Err(anyhow::anyhow!("Invalid key length"));
37    }
38
39    // Generate a random nonce with supplied rng
40    let mut nonce = [0u8; NONCE_LEN];
41    provider::rng::fill_bytes(rng, &mut nonce);
42
43    // Prepare output buffer: nonce + ciphertext + tag
44    let mut output = alloc::vec::Vec::new();
45    output.extend_from_slice(&nonce);
46
47    let mut ciphertext = alloc::vec![0u8; message_id.len() + TAG_LEN];
48    let result = key.try_into();
49    let key_array = result.map_err(|_| anyhow::anyhow!("Key length mismatch"))?;
50
51    // Encrypt the message ID
52    match provider::chacha20poly1305::encrypt(&key_array, message_id, &mut ciphertext, &[], &nonce)
53    {
54        Ok(_) => {}
55        Err(e) => {
56            return Err(anyhow::anyhow!(
57                "ChaCha20-Poly1305 encryption failed: {:?}",
58                e
59            ));
60        }
61    }
62    output.extend_from_slice(&ciphertext);
63    Ok(output)
64}
65
66/// Symmetric decryption for message IDs using ChaCha20-Poly1305
67///
68/// This is used in step 7 for decrypting message IDs with a shared secret
69pub fn decrypt_message_id(key: &[u8], encrypted_data: &[u8]) -> Result<Vec<u8>, Error> {
70    use provider::chacha20poly1305::{KEY_LEN, NONCE_LEN, TAG_LEN};
71
72    if key.len() != KEY_LEN {
73        return Err(anyhow::anyhow!("Invalid key length"));
74    }
75
76    if encrypted_data.len() < NONCE_LEN + TAG_LEN {
77        return Err(anyhow::anyhow!("Encrypted data too short"));
78    }
79
80    // Extract nonce and ciphertext
81    let nonce_r = encrypted_data[..NONCE_LEN].try_into();
82    let nonce: [u8; NONCE_LEN] = nonce_r.map_err(|_| anyhow::anyhow!("Nonce extraction failed"))?;
83    let ciphertext = &encrypted_data[NONCE_LEN..];
84
85    // Prepare output buffer
86    let mut plaintext = alloc::vec![0u8; ciphertext.len() - TAG_LEN];
87    let key_arr_res = key.try_into();
88    let key_array: [u8; KEY_LEN] =
89        key_arr_res.map_err(|_| anyhow::anyhow!("Key length mismatch"))?;
90
91    // Decrypt the message ID
92    provider::chacha20poly1305::decrypt(
93        &key_array,
94        &mut plaintext,
95        ciphertext,
96        &[], // empty AAD
97        &nonce,
98    )
99    .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 decryption failed: {:?}", e))?;
100
101    Ok(plaintext)
102}