Skip to main content

craft_codec/
crypto.rs

1use crate::{Encryption, Result};
2
3// Keep codec state inline so conversion requires no extra allocation.
4#[allow(clippy::large_enum_variant)]
5pub(crate) enum EncryptionState {
6    None,
7    #[cfg(feature = "aes-gcm")]
8    Aes256Gcm(Aes256Gcm),
9}
10
11impl EncryptionState {
12    pub(crate) fn new(parameters: &Encryption) -> Result<Self> {
13        match parameters {
14            Encryption::None => Ok(Self::None),
15            #[cfg(feature = "aes-gcm")]
16            Encryption::Aes256Gcm { key } => Ok(Self::Aes256Gcm(Aes256Gcm::new(key))),
17            #[cfg(not(feature = "aes-gcm"))]
18            Encryption::Aes256Gcm { .. } => Err(crate::Error::EncryptionUnavailable),
19        }
20    }
21
22    /// Additional buffer capacity required to seal a payload.
23    pub(crate) fn overhead(&self) -> usize {
24        match self {
25            Self::None => 0,
26            #[cfg(feature = "aes-gcm")]
27            Self::Aes256Gcm(_) => crate::TAG_LEN,
28        }
29    }
30
31    pub(crate) fn seal(&self, _index: u64, _buffer: &mut Vec<u8>) -> Result<()> {
32        match self {
33            Self::None => Ok(()),
34            #[cfg(feature = "aes-gcm")]
35            Self::Aes256Gcm(codec) => codec.seal(_index, _buffer),
36        }
37    }
38
39    pub(crate) fn open(&self, _index: u64, _buffer: &mut Vec<u8>) -> Result<()> {
40        match self {
41            Self::None => Ok(()),
42            #[cfg(feature = "aes-gcm")]
43            Self::Aes256Gcm(codec) => codec.open(_index, _buffer),
44        }
45    }
46}
47
48/// AES-256-GCM with nonce `0u32 || index.to_be_bytes()`, empty AAD and a suffix tag.
49///
50/// Use one independent key per immutable object. Never encrypt distinct payloads
51/// under the same key/index, even across encoder instances. Metadata must be
52/// trusted. Key generation belongs to the caller; metadata persists the key.
53#[cfg(feature = "aes-gcm")]
54pub(crate) struct Aes256Gcm(aes_gcm::Aes256Gcm);
55
56#[cfg(feature = "aes-gcm")]
57impl Aes256Gcm {
58    /// Initialize the expanded key once. The caller retains responsibility for
59    /// its original key bytes. The underlying cipher enables its zeroize feature.
60    pub(crate) fn new(key: &[u8; 32]) -> Self {
61        use aes_gcm::KeyInit;
62        Self(aes_gcm::Aes256Gcm::new(key.into()))
63    }
64}
65
66#[cfg(feature = "aes-gcm")]
67fn nonce(index: u64) -> aes_gcm::Nonce<aes_gcm::aead::consts::U12> {
68    let [a, b, c, d, e, f, g, h] = index.to_be_bytes();
69    [0, 0, 0, 0, a, b, c, d, e, f, g, h].into()
70}
71
72#[cfg(feature = "aes-gcm")]
73impl Aes256Gcm {
74    fn seal(&self, index: u64, buffer: &mut Vec<u8>) -> Result<()> {
75        use aes_gcm::aead::AeadInOut;
76        buffer
77            .try_reserve(crate::TAG_LEN)
78            .map_err(|_| crate::Error::Allocation)?;
79        let tag = self
80            .0
81            .encrypt_inout_detached(&nonce(index), b"", buffer.as_mut_slice().into())
82            .map_err(|_| crate::Error::EncryptionFailed)?;
83        buffer.extend_from_slice(&tag);
84        Ok(())
85    }
86
87    fn open(&self, index: u64, buffer: &mut Vec<u8>) -> Result<()> {
88        use aes_gcm::aead::AeadInOut;
89        let payload_len = buffer
90            .len()
91            .checked_sub(crate::TAG_LEN)
92            .ok_or(crate::Error::InvalidStoredLength)?;
93        let tag: [u8; crate::TAG_LEN] = buffer
94            .get(payload_len..)
95            .ok_or(crate::Error::InvalidStoredLength)?
96            .try_into()
97            .map_err(|_| crate::Error::InvalidStoredLength)?;
98        let payload = buffer
99            .get_mut(..payload_len)
100            .ok_or(crate::Error::InvalidStoredLength)?;
101        self.0
102            .decrypt_inout_detached(&nonce(index), b"", payload.into(), &tag.into())
103            .map_err(|_| crate::Error::AuthenticationFailed)?;
104        buffer.truncate(payload_len);
105        Ok(())
106    }
107}