Skip to main content

hpke/
aead.rs

1//! AEAD algorithms for HPKE ciphersuites (`draft-ietf-hpke-hpke-05` Section 5.2
2//! and Section 7.3).
3//!
4//! The AEADs registered by the specification are owned by the `crypto` crate and
5//! re-exported here, so users of this crate never need to depend on `crypto`
6//! directly:
7//!
8//! - [`Aes256Gcm`] (AEAD id `0x0002`)
9//! - [`ChaCha20Poly1305`] (AEAD id `0x0003`)
10//! - [`ExportOnly`] (AEAD id `0xffff`), the local pseudo-AEAD
11//!
12//! HPKE-specific metadata (the AEAD identifier, message length limits, and the
13//! keyed constructor) is added through the [`Aead`] extension trait.
14
15use crypto::Hash;
16pub use crypto::{aes::Aes256Gcm, chacha::ChaCha20Poly1305};
17
18use super::HpkeError;
19
20/// Maximum plaintext length for AES-GCM (`P_MAX`), 2^36 - 31 bytes ([RFC 5116]
21/// Section 5.2.1.1).
22const AES_GCM_MAX_PLAINTEXT_SIZE: u64 = (1u64 << 36) - 31;
23/// Maximum ciphertext length for AES-GCM (`C_MAX`), 2^36 - 15 bytes ([RFC 5116]
24/// Section 5.2.1.1).
25const AES_GCM_MAX_CIPHERTEXT_SIZE: u64 = (1u64 << 36) - 15;
26/// Maximum plaintext length for ChaCha20-Poly1305 (`P_MAX`), 2^38 - 64 bytes
27/// ([RFC 8439] Section 2.8).
28const CHACHA20_POLY1305_MAX_PLAINTEXT_SIZE: u64 = (1u64 << 38) - 64;
29/// Maximum ciphertext length for ChaCha20-Poly1305 (`C_MAX`), 2^38 - 48 bytes
30/// ([RFC 8439] Section 2.8).
31const CHACHA20_POLY1305_MAX_CIPHERTEXT_SIZE: u64 = (1u64 << 38) - 48;
32
33/// HPKE ciphersuite binding for an AEAD algorithm.
34///
35/// An AEAD is the symmetric cipher used to seal and open messages. This trait
36/// only adds what HPKE itself needs on top of [`crypto::Aead`] (from which the
37/// nonce size, tag size, and the underlying cipher operations are inherited):
38///
39/// - [`Aead::HPKE_AEAD_ID`], the IANA "HPKE AEAD Identifiers" value used to build
40///   the ciphersuite `suite_id`,
41/// - [`Aead::MAX_PLAINTEXT_SIZE`] and [`Aead::MAX_CIPHERTEXT_SIZE`], the message
42///   length limits `P_MAX` and `C_MAX` defined by [RFC 5116],
43/// - [`Aead::new`], the construction of a ready-to-use cipher from the
44///   raw key schedule output.
45///
46/// # Example
47///
48/// ```
49/// use hpke::aead::{Aead, Aes256Gcm, ExportOnly};
50///
51/// let cipher = <Aes256Gcm as Aead>::new(&[0x42u8; 32]).unwrap();
52/// assert_eq!(Aes256Gcm::HPKE_AEAD_ID, 0x0002);
53/// assert_eq!(ExportOnly::HPKE_AEAD_ID, 0xffff);
54/// # let _ = cipher;
55/// ```
56///
57/// # Custom AEADs
58///
59/// Implement [`crypto::Aead`] for the cipher's operations, then implement this
60/// trait to give it an HPKE identifier, key size, and message length limits. Any
61/// nonce and tag size is supported as long as `KEY_SIZE` is at most 64 bytes and
62/// `NONCE_SIZE` is between 8 and 64 bytes; larger values fail to compile when
63/// the cipher is used with [`new_sender`](crate::new_sender) or
64/// [`new_recipient`](crate::new_recipient).
65pub trait Aead: Sized + crypto::Aead {
66    /// The HPKE AEAD identifier (`draft-ietf-hpke-hpke-05` Section 7.3, IANA
67    /// "HPKE AEAD Identifiers"), used to build the ciphersuite `suite_id`.
68    const HPKE_AEAD_ID: u16;
69
70    /// Maximum length, in bytes, of a plaintext passed to [`Aead::seal`], i.e.
71    /// `P_MAX` ([RFC 5116]).
72    ///
73    /// HPKE refuses to encrypt longer plaintexts with
74    /// [`HpkeError::MessageLimitReached`]: exceeding `P_MAX` would void the
75    /// confidentiality and integrity guarantees of the AEAD
76    /// (`draft-ietf-hpke-hpke-05` Section 5.2). There is no default; every AEAD
77    /// must state its limit.
78    const MAX_PLAINTEXT_SIZE: u64;
79
80    /// Maximum length, in bytes, of a ciphertext passed to
81    /// [`Aead::open`], i.e. `C_MAX` ([RFC 5116]).
82    ///
83    /// HPKE refuses to open longer ciphertexts with
84    /// [`HpkeError::MessageLimitReached`] (`draft-ietf-hpke-hpke-05`
85    /// Section 5.2). There is no default; every AEAD must state its limit.
86    const MAX_CIPHERTEXT_SIZE: u64;
87
88    /// Instantiates the cipher from `KEY_SIZE` raw key bytes, as produced by
89    /// the HPKE key schedule.
90    ///
91    /// # Errors
92    ///
93    /// Returns [`HpkeError::InvalidKey`] when `key` does not have exactly
94    /// [`crypto::Aead::KEY_SIZE`] bytes.
95    fn new(key: &[u8]) -> Result<Self, HpkeError>;
96
97    /// Encrypts `in_out` in place and returns the detached authentication tag,
98    /// forwarding to [`crypto::Aead::encrypt_in_place`].
99    ///
100    /// # Errors
101    ///
102    /// Returns [`HpkeError::NotSupported`] when the cipher is the
103    /// [`ExportOnly`] pseudo-AEAD.
104    fn seal(&self, in_out: &mut [u8], nonce: &[u8], associated_data: &[u8]) -> Result<Hash, HpkeError> {
105        return Ok(crypto::Aead::encrypt_in_place(self, in_out, nonce, associated_data));
106    }
107
108    /// Decrypts `in_out` in place using the detached authentication `tag`,
109    /// forwarding to [`crypto::Aead::decrypt_in_place`].
110    ///
111    /// # Errors
112    ///
113    /// Returns [`HpkeError::OpenError`] when authentication fails, or
114    /// [`HpkeError::NotSupported`] when the cipher is the [`ExportOnly`]
115    /// pseudo-AEAD.
116    fn open(&self, in_out: &mut [u8], nonce: &[u8], associated_data: &[u8], tag: &[u8]) -> Result<(), HpkeError> {
117        return crypto::Aead::decrypt_in_place(self, in_out, nonce, associated_data, tag)
118            .map_err(|_| HpkeError::OpenError);
119    }
120}
121
122/// AES-256-GCM (AEAD id `0x0002`).
123impl Aead for Aes256Gcm {
124    const HPKE_AEAD_ID: u16 = 0x0002;
125    const MAX_PLAINTEXT_SIZE: u64 = AES_GCM_MAX_PLAINTEXT_SIZE;
126    const MAX_CIPHERTEXT_SIZE: u64 = AES_GCM_MAX_CIPHERTEXT_SIZE;
127
128    fn new(key: &[u8]) -> Result<Self, HpkeError> {
129        let key: &[u8; 32] = key.try_into().map_err(|_| HpkeError::InvalidKey)?;
130        return Ok(Aes256Gcm::new(key));
131    }
132}
133
134/// ChaCha20-Poly1305 (AEAD id `0x0003`).
135impl Aead for ChaCha20Poly1305 {
136    const HPKE_AEAD_ID: u16 = 0x0003;
137    const MAX_PLAINTEXT_SIZE: u64 = CHACHA20_POLY1305_MAX_PLAINTEXT_SIZE;
138    const MAX_CIPHERTEXT_SIZE: u64 = CHACHA20_POLY1305_MAX_CIPHERTEXT_SIZE;
139
140    fn new(key: &[u8]) -> Result<Self, HpkeError> {
141        let key: &[u8; 32] = key.try_into().map_err(|_| HpkeError::InvalidKey)?;
142        return Ok(ChaCha20Poly1305::new(key));
143    }
144}
145
146/// Export-Only pseudo-AEAD (AEAD id `0xffff`).
147///
148/// Ciphersuites using this "AEAD" can only derive secrets through
149/// [`SenderContext::export`](crate::SenderContext::export) and
150/// [`RecipientContext::export`](crate::RecipientContext::export); [`Aead::seal`]
151/// and [`Aead::open`] return [`HpkeError::NotSupported`].
152///
153/// The [`crypto::Aead`] implementation below only exists to satisfy the
154/// [`Aead`] supertrait bound and must not be called directly: HPKE never
155/// invokes it, and it does not perform any encryption.
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157pub struct ExportOnly;
158
159impl crypto::Aead for ExportOnly {
160    const KEY_SIZE: usize = 0;
161    const TAG_SIZE: usize = 0;
162    const NONCE_SIZE: usize = 0;
163
164    fn encrypt_in_place(&self, _in_out: &mut [u8], _nonce: &[u8], _associated_data: &[u8]) -> Hash {
165        return Hash::from([0u8; Self::TAG_SIZE]);
166    }
167
168    fn decrypt_in_place(
169        &self,
170        _in_out: &mut [u8],
171        _nonce: &[u8],
172        _associated_data: &[u8],
173        _tag: &[u8],
174    ) -> Result<(), crypto::AeadError> {
175        return Err(crypto::AeadError::Unsupported);
176    }
177}
178
179impl Aead for ExportOnly {
180    const HPKE_AEAD_ID: u16 = 0xffff;
181    // The Export-Only pseudo-AEAD never encrypts or decrypts, so it has no
182    // message length limits.
183    const MAX_PLAINTEXT_SIZE: u64 = 0;
184    const MAX_CIPHERTEXT_SIZE: u64 = 0;
185
186    fn new(key: &[u8]) -> Result<Self, HpkeError> {
187        if key.is_empty() {
188            return Ok(ExportOnly);
189        }
190        return Err(HpkeError::InvalidKey);
191    }
192
193    fn seal(&self, _in_out: &mut [u8], _nonce: &[u8], _associated_data: &[u8]) -> Result<Hash, HpkeError> {
194        return Err(HpkeError::NotSupported);
195    }
196
197    fn open(&self, _in_out: &mut [u8], _nonce: &[u8], _associated_data: &[u8], _tag: &[u8]) -> Result<(), HpkeError> {
198        return Err(HpkeError::NotSupported);
199    }
200}