rustls/crypto/
hpke.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::fmt::Debug;
4
5use zeroize::Zeroize;
6
7use crate::Error;
8use crate::error::InvalidMessage;
9use crate::msgs::codec::{Codec, ListLength, Reader, TlsListElement};
10
11/// An HPKE suite, specifying a key encapsulation mechanism and a symmetric cipher suite.
12#[expect(clippy::exhaustive_structs)]
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct HpkeSuite {
15    /// The choice of HPKE key encapsulation mechanism.
16    pub kem: HpkeKem,
17
18    /// The choice of HPKE symmetric cipher suite.
19    ///
20    /// This combines a choice of authenticated encryption with additional data (AEAD) algorithm
21    /// and a key derivation function (KDF).
22    pub sym: HpkeSymmetricCipherSuite,
23}
24
25/// An HPKE instance that can be used for base-mode single-shot encryption and decryption.
26pub trait Hpke: Debug + Send + Sync {
27    /// Seal the provided `plaintext` to the recipient public key `pub_key` with application supplied
28    /// `info`, and additional data `aad`.
29    ///
30    /// Returns ciphertext that can be used with [Self::open] by the recipient to recover plaintext
31    /// using the same `info` and `aad` and the private key corresponding to `pub_key`. RFC 9180
32    /// refers to `pub_key` as `pkR`.
33    fn seal(
34        &self,
35        info: &[u8],
36        aad: &[u8],
37        plaintext: &[u8],
38        pub_key: &HpkePublicKey,
39    ) -> Result<(EncapsulatedSecret, Vec<u8>), Error>;
40
41    /// Set up a sealer context for the receiver public key `pub_key` with application supplied `info`.
42    ///
43    /// Returns both an encapsulated ciphertext and a sealer context that can be used to seal
44    /// messages to the recipient. RFC 9180 refers to `pub_key` as `pkR`.
45    fn setup_sealer(
46        &self,
47        info: &[u8],
48        pub_key: &HpkePublicKey,
49    ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error>;
50
51    /// Open the provided `ciphertext` using the encapsulated secret `enc`, with application
52    /// supplied `info`, and additional data `aad`.
53    ///
54    /// Returns plaintext if  the `info` and `aad` match those used with [Self::seal], and
55    /// decryption with `secret_key` succeeds. RFC 9180 refers to `secret_key` as `skR`.
56    fn open(
57        &self,
58        enc: &EncapsulatedSecret,
59        info: &[u8],
60        aad: &[u8],
61        ciphertext: &[u8],
62        secret_key: &HpkePrivateKey,
63    ) -> Result<Vec<u8>, Error>;
64
65    /// Set up an opener context for the secret key `secret_key` with application supplied `info`.
66    ///
67    /// Returns an opener context that can be used to open sealed messages encrypted to the
68    /// public key corresponding to `secret_key`. RFC 9180 refers to `secret_key` as `skR`.
69    fn setup_opener(
70        &self,
71        enc: &EncapsulatedSecret,
72        info: &[u8],
73        secret_key: &HpkePrivateKey,
74    ) -> Result<Box<dyn HpkeOpener + 'static>, Error>;
75
76    /// Generate a new public key and private key pair compatible with this HPKE instance.
77    ///
78    /// Key pairs should be encoded as raw big endian fixed length integers sized based
79    /// on the suite's DH KEM algorithm.
80    fn generate_key_pair(&self) -> Result<(HpkePublicKey, HpkePrivateKey), Error>;
81
82    /// Return whether the HPKE instance is FIPS compatible.
83    fn fips(&self) -> bool {
84        false
85    }
86
87    /// Return the [HpkeSuite] that this HPKE instance supports.
88    fn suite(&self) -> HpkeSuite;
89}
90
91/// An HPKE sealer context.
92///
93/// This is a stateful object that can be used to seal messages for receipt by
94/// a receiver.
95pub trait HpkeSealer: Debug + Send + Sync + 'static {
96    /// Seal the provided `plaintext` with additional data `aad`, returning
97    /// ciphertext.
98    fn seal(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error>;
99}
100
101/// An HPKE opener context.
102///
103/// This is a stateful object that can be used to open sealed messages sealed
104/// by a sender.
105pub trait HpkeOpener: Debug + Send + Sync + 'static {
106    /// Open the provided `ciphertext` with additional data `aad`, returning plaintext.
107    fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error>;
108}
109
110/// An HPKE public key.
111#[expect(clippy::exhaustive_structs)]
112#[derive(Clone, Debug)]
113pub struct HpkePublicKey(pub Vec<u8>);
114
115/// An HPKE private key.
116pub struct HpkePrivateKey(Vec<u8>);
117
118impl HpkePrivateKey {
119    /// Return the private key bytes.
120    pub fn secret_bytes(&self) -> &[u8] {
121        self.0.as_slice()
122    }
123}
124
125impl From<Vec<u8>> for HpkePrivateKey {
126    fn from(bytes: Vec<u8>) -> Self {
127        Self(bytes)
128    }
129}
130
131impl Drop for HpkePrivateKey {
132    fn drop(&mut self) {
133        self.0.zeroize();
134    }
135}
136
137/// An HPKE symmetric cipher suite, combining a KDF and an AEAD algorithm.
138#[expect(clippy::exhaustive_structs)]
139#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
140pub struct HpkeSymmetricCipherSuite {
141    /// The KDF to use for this cipher suite.
142    pub kdf_id: HpkeKdf,
143    /// The AEAD to use for this cipher suite.
144    pub aead_id: HpkeAead,
145}
146
147impl Codec<'_> for HpkeSymmetricCipherSuite {
148    fn encode(&self, bytes: &mut Vec<u8>) {
149        self.kdf_id.encode(bytes);
150        self.aead_id.encode(bytes);
151    }
152
153    fn read(r: &mut Reader<'_>) -> Result<Self, InvalidMessage> {
154        Ok(Self {
155            kdf_id: HpkeKdf::read(r)?,
156            aead_id: HpkeAead::read(r)?,
157        })
158    }
159}
160
161/// draft-ietf-tls-esni-24: `HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;`
162impl TlsListElement for HpkeSymmetricCipherSuite {
163    const SIZE_LEN: ListLength = ListLength::NonZeroU16 {
164        empty_error: InvalidMessage::IllegalEmptyList("HpkeSymmetricCipherSuites"),
165    };
166}
167
168enum_builder! {
169    /// The Key Encapsulation Mechanism (`Kem`) type for HPKE operations.
170    /// Listed by IANA, as specified in [RFC 9180 Section 7.1]
171    ///
172    /// [RFC 9180 Section 7.1]: <https://datatracker.ietf.org/doc/html/rfc9180#kemid-values>
173    #[repr(u16)]
174    #[allow(non_camel_case_types)]
175    pub enum HpkeKem {
176        DHKEM_P256_HKDF_SHA256 => 0x0010,
177        DHKEM_P384_HKDF_SHA384 => 0x0011,
178        DHKEM_P521_HKDF_SHA512 => 0x0012,
179        DHKEM_X25519_HKDF_SHA256 => 0x0020,
180        DHKEM_X448_HKDF_SHA512 => 0x0021,
181    }
182}
183
184enum_builder! {
185    /// The Key Derivation Function (`Kdf`) type for HPKE operations.
186    /// Listed by IANA, as specified in [RFC 9180 Section 7.2]
187    ///
188    /// [RFC 9180 Section 7.2]: <https://datatracker.ietf.org/doc/html/rfc9180#name-key-derivation-functions-kd>
189    #[repr(u16)]
190    #[allow(non_camel_case_types)]
191    #[derive(Default)]
192    pub enum HpkeKdf {
193        // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now.
194        #[default]
195        HKDF_SHA256 => 0x0001,
196        HKDF_SHA384 => 0x0002,
197        HKDF_SHA512 => 0x0003,
198    }
199}
200
201enum_builder! {
202    /// The Authenticated Encryption with Associated Data (`Aead`) type for HPKE operations.
203    /// Listed by IANA, as specified in [RFC 9180 Section 7.3]
204    ///
205    /// [RFC 9180 Section 7.3]: <https://datatracker.ietf.org/doc/html/rfc9180#name-authenticated-encryption-wi>
206    #[repr(u16)]
207    #[allow(non_camel_case_types)]
208    #[derive(Default)]
209    pub enum HpkeAead {
210        // TODO(XXX): revisit the default configuration. This is just what Cloudflare ships right now.
211        #[default]
212        AES_128_GCM => 0x0001,
213        AES_256_GCM => 0x0002,
214        CHACHA20_POLY_1305 => 0x0003,
215        EXPORT_ONLY => 0xFFFF,
216    }
217}
218
219impl HpkeAead {
220    /// Returns the length of the tag for the AEAD algorithm, or none if the AEAD is EXPORT_ONLY.
221    pub(crate) fn tag_len(&self) -> Option<usize> {
222        match self {
223            // See RFC 9180 Section 7.3, column `Nt`, the length in bytes of the authentication tag
224            // for the algorithm.
225            // https://www.rfc-editor.org/rfc/rfc9180.html#section-7.3
226            Self::AES_128_GCM | Self::AES_256_GCM | Self::CHACHA20_POLY_1305 => Some(16),
227            _ => None,
228        }
229    }
230}
231
232/// An HPKE key pair, made of a matching public and private key.
233#[expect(clippy::exhaustive_structs)]
234pub struct HpkeKeyPair {
235    /// A HPKE public key.
236    pub public_key: HpkePublicKey,
237    /// A HPKE private key.
238    pub private_key: HpkePrivateKey,
239}
240
241/// An encapsulated secret returned from setting up a sender or receiver context.
242#[expect(clippy::exhaustive_structs)]
243#[derive(Debug)]
244pub struct EncapsulatedSecret(pub Vec<u8>);