1use crate::{randomness, CryptoError};
4use chacha20poly1305::{
5 aead::{array::typenum::Unsigned, consts::U24, Aead, AeadCore, AeadInOut, Payload},
6 KeyInit, XChaCha20Poly1305,
7};
8use zeroize::Zeroizing;
9
10pub const FILE_CHUNK_TAG_LEN: usize = <XChaCha20Poly1305 as AeadCore>::TagSize::USIZE;
12
13pub(crate) const NONCE_LEN: usize = <XChaCha20Poly1305 as AeadCore>::NonceSize::USIZE;
15
16pub const SEALED_OBJECT_CHUNK_OVERHEAD: usize = NONCE_LEN + FILE_CHUNK_TAG_LEN;
18
19pub(crate) fn storage_path(path: &str) -> &[u8] {
20 assert!(!path.is_empty(), "storage paths must be nonempty");
21 path.as_bytes()
22}
23pub(crate) fn context(parts: &[&[u8]]) -> Vec<u8> {
24 let mut aad = Vec::new();
25 for part in parts {
26 aad.extend_from_slice(&(part.len() as u64).to_be_bytes());
27 aad.extend_from_slice(part);
28 }
29 aad
30}
31
32pub(crate) fn seal(key: &[u8; 32], nonce: &[u8; 24], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
33 encrypt(&XChaCha20Poly1305::new(key.into()), nonce, aad, plaintext)
34}
35
36fn encrypt(
37 cipher: &impl Aead<NonceSize = U24>,
38 nonce: &[u8; 24],
39 aad: &[u8],
40 plaintext: &[u8],
41) -> Vec<u8> {
42 cipher
43 .encrypt(nonce.into(), Payload { msg: plaintext, aad })
44 .expect("XChaCha20-Poly1305 plaintext must fit its block counter and lengths must fit u64")
45}
46
47pub(crate) fn open(
48 key: &[u8; 32],
49 nonce: &[u8; 24],
50 aad: &[u8],
51 sealed: &[u8],
52) -> Result<Vec<u8>, CryptoError> {
53 if sealed.len() < FILE_CHUNK_TAG_LEN {
54 return Err(CryptoError::Malformed);
55 }
56 XChaCha20Poly1305::new(key.into())
57 .decrypt(nonce.into(), Payload { msg: sealed, aad })
58 .map_err(|chacha20poly1305::aead::Error| CryptoError::Authentication)
60}
61
62pub(crate) fn seal_random(
63 key: &[u8; 32],
64 aad: &[u8],
65 plaintext: &[u8],
66) -> Result<Vec<u8>, CryptoError> {
67 let mut nonce = [0; NONCE_LEN];
68 randomness::fill(&mut nonce)?;
69 let mut sealed = nonce.to_vec();
70 sealed.extend(seal(key, &nonce, aad, plaintext));
71 Ok(sealed)
72}
73
74pub(crate) fn open_random(
75 key: &[u8; 32],
76 aad: &[u8],
77 sealed: &[u8],
78) -> Result<Vec<u8>, CryptoError> {
79 let (nonce, ciphertext) =
80 sealed.split_first_chunk::<NONCE_LEN>().ok_or(CryptoError::Malformed)?;
81 open(key, nonce, aad, ciphertext)
82}
83
84pub(crate) fn open_exact<const N: usize>(
87 key: &[u8; 32],
88 nonce: &[u8; NONCE_LEN],
89 aad: &[u8],
90 ciphertext: &[u8; N],
91 tag: &[u8; FILE_CHUNK_TAG_LEN],
92) -> Result<Zeroizing<[u8; N]>, CryptoError> {
93 let mut plaintext = Zeroizing::new(*ciphertext);
94 XChaCha20Poly1305::new(key.into())
95 .decrypt_inout_detached(nonce.into(), aad, plaintext.as_mut_slice().into(), tag.into())
96 .map_err(|chacha20poly1305::aead::Error| CryptoError::Authentication)?;
98 Ok(plaintext)
99}
100
101#[cfg(test)]
102#[path = "cipher_tests.rs"]
103mod tests;