Skip to main content

rustls/crypto/
hpke.rs

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