openmls/ciphersuite/
hpke.rs

1//! ### Public-Key Encryption
2//!
3//! As with signing, MLS includes a label and context in encryption operations to
4//! avoid confusion between ciphertexts produced for different purposes.  Encryption
5//! and decryption including this label and context are done as follows:
6//!
7//! ```text
8//! EncryptWithLabel(PublicKey, Label, Context, Plaintext) =
9//!   SealBase(PublicKey, EncryptContext, "", Plaintext)
10//!
11//! DecryptWithLabel(PrivateKey, Label, Context, KEMOutput, Ciphertext) =
12//!   OpenBase(KEMOutput, PrivateKey, EncryptContext, "", Ciphertext)
13//! ```
14//!
15//! Where EncryptContext is specified as:
16//!
17//! ```text
18//! struct {
19//!   opaque label<V>;
20//!   opaque context<V>;
21//! } EncryptContext;
22//! ```
23//!
24//! And its fields set to:
25//!
26//! ```text
27//! label = "MLS 1.0 " + Label;
28//! context = Context;
29//! ```
30//!
31//! Here, the functions `SealBase` and `OpenBase` are defined RFC9180, using the
32//! HPKE algorithms specified by the group's ciphersuite.  If MLS extensions
33//! require HPKE encryption operations, they should re-use the EncryptWithLabel
34//! construction, using a distinct label.  To avoid collisions in these labels, an
35//! IANA registry is defined in mls-public-key-encryption-labels.
36
37use openmls_traits::{
38    crypto::OpenMlsCrypto,
39    types::{Ciphersuite, CryptoError, HpkeCiphertext},
40};
41use thiserror::Error;
42use tls_codec::{Serialize, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize, VLBytes};
43
44use super::LABEL_PREFIX;
45
46#[cfg(feature = "extensions-draft-08")]
47use crate::component::{ComponentId, ComponentOperationLabel};
48
49/// HPKE labeled encryption errors.
50#[derive(Error, Debug, PartialEq, Clone)]
51pub enum Error {
52    /// Error while serializing content. This should only happen if a bounds check was missing.
53    #[error(
54        "Error while serializing content. This should only happen if a bounds check was missing."
55    )]
56    MissingBoundCheck,
57
58    /// Decryption failed.
59    #[error("Decryption failed.")]
60    DecryptionFailed,
61}
62
63impl From<tls_codec::Error> for Error {
64    fn from(_: tls_codec::Error) -> Self {
65        Self::MissingBoundCheck
66    }
67}
68
69impl From<CryptoError> for Error {
70    fn from(_: CryptoError) -> Self {
71        Self::DecryptionFailed
72    }
73}
74
75/// Context for HPKE encryption
76#[derive(Debug, Clone, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
77pub struct EncryptContext {
78    /// Prefixed with LABEL_PREFIX
79    label: VLBytes,
80    context: VLBytes,
81}
82
83impl EncryptContext {
84    /// Create a new [`EncryptContext`] from a string label and the content bytes.
85    /// Ensures that the prefix LABEL_PREFIX is prepended to the label.
86    pub(crate) fn new(label: &str, context: VLBytes) -> Self {
87        let label_string = LABEL_PREFIX.to_owned() + label;
88        let label = label_string.as_bytes().into();
89        Self { label, context }
90    }
91
92    #[cfg(feature = "extensions-draft-08")]
93    pub(crate) fn new_from_component_operation_label(
94        label: ComponentOperationLabel,
95        context: VLBytes,
96    ) -> Result<Self, Error> {
97        let serialized_label = label.tls_serialize_detached()?;
98
99        // Prefix the serialized label with the LABEL_PREFIX bytes
100        // Note that the spec isn't precise here. There are different ways to
101        // combine these. https://github.com/mlswg/mls-extensions/issues/79
102        let mut label = LABEL_PREFIX.as_bytes().to_vec();
103        label.extend(serialized_label);
104
105        Ok(Self {
106            label: label.into(),
107            context,
108        })
109    }
110}
111
112impl From<(&str, &[u8])> for EncryptContext {
113    fn from((label, context): (&str, &[u8])) -> Self {
114        Self::new(label, context.into())
115    }
116}
117
118/// Encrypt to an HPKE key with a label.
119pub(crate) fn encrypt_with_label(
120    public_key: &[u8],
121    label: &str,
122    context: &[u8],
123    plaintext: &[u8],
124    ciphersuite: Ciphersuite,
125    crypto: &impl OpenMlsCrypto,
126) -> Result<HpkeCiphertext, Error> {
127    let context: EncryptContext = (label, context).into();
128
129    log_crypto!(
130        debug,
131        "HPKE Encrypt with label `{label}` and ciphersuite `{ciphersuite:?}`:"
132    );
133
134    encrypt_with_label_internal(public_key, context, plaintext, ciphersuite, crypto)
135}
136
137fn encrypt_with_label_internal(
138    public_key: &[u8],
139    context: EncryptContext,
140    plaintext: &[u8],
141    ciphersuite: Ciphersuite,
142    crypto: &impl OpenMlsCrypto,
143) -> Result<HpkeCiphertext, Error> {
144    let context = context.tls_serialize_detached()?;
145
146    log_crypto!(debug, "* context:     {context:x?}");
147    log_crypto!(debug, "* public key:  {public_key:x?}");
148    log_crypto!(debug, "* plaintext:   {plaintext:x?}");
149
150    let cipher = crypto.hpke_seal(
151        ciphersuite.hpke_config(),
152        public_key,
153        &context,
154        &[],
155        plaintext,
156    )?;
157
158    log_crypto!(debug, "* ciphertext:  {:x?}", cipher);
159
160    Ok(cipher)
161}
162
163/// Context for [`safe_encrypt_with_label`] and [`safe_decrypt_with_label`].
164#[cfg(feature = "extensions-draft-08")]
165pub struct SafeEncryptionContext<'a> {
166    /// The [`ComponentId`] to use.
167    pub component_id: ComponentId,
168
169    /// A label
170    pub label: &'a str,
171
172    /// An optional context.
173    pub context: &'a [u8],
174}
175
176/// Encrypt the provided `plaintext` for the `public_key`.
177/// The [`SafeEncryptionContext`] is used to set the [`ComponentId`], `label`,
178/// and optional `context`.
179///
180/// Returns an [`HpkeCiphertext`] or an [`enum@Error`].
181#[cfg(feature = "extensions-draft-08")]
182pub fn safe_encrypt_with_label(
183    public_key: &[u8],
184    plaintext: &[u8],
185    ciphersuite: Ciphersuite,
186    context: SafeEncryptionContext,
187    crypto: &impl OpenMlsCrypto,
188) -> Result<HpkeCiphertext, Error> {
189    let component_operation_label =
190        ComponentOperationLabel::new(context.component_id, context.label);
191
192    let context = EncryptContext::new_from_component_operation_label(
193        component_operation_label,
194        context.context.into(),
195    )?;
196
197    encrypt_with_label_internal(public_key, context, plaintext, ciphersuite, crypto)
198}
199
200/// Decrypt with HPKE and label.
201pub(crate) fn decrypt_with_label(
202    private_key: &[u8],
203    label: &str,
204    context: &[u8],
205    ciphertext: &HpkeCiphertext,
206    ciphersuite: Ciphersuite,
207    crypto: &impl OpenMlsCrypto,
208) -> Result<Vec<u8>, Error> {
209    log_crypto!(
210        debug,
211        "HPKE Decrypt with label `{label}` and `ciphersuite` {ciphersuite:?}:"
212    );
213
214    let context: EncryptContext = (label, context).into();
215
216    decrypt_with_label_internal(private_key, context, ciphertext, ciphersuite, crypto)
217}
218
219fn decrypt_with_label_internal(
220    private_key: &[u8],
221    context: EncryptContext,
222    ciphertext: &HpkeCiphertext,
223    ciphersuite: Ciphersuite,
224    crypto: &impl OpenMlsCrypto,
225) -> Result<Vec<u8>, Error> {
226    let context = context.tls_serialize_detached()?;
227
228    log_crypto!(debug, "* context:     {context:x?}");
229    log_crypto!(debug, "* private key: {private_key:x?}");
230    log_crypto!(debug, "* ciphertext:  {ciphertext:x?}");
231
232    let plaintext = crypto
233        .hpke_open(
234            ciphersuite.hpke_config(),
235            ciphertext,
236            private_key,
237            &context,
238            &[],
239        )
240        .map_err(|e| e.into());
241
242    log_crypto!(debug, "* plaintext:   {plaintext:x?}");
243
244    plaintext
245}
246
247#[cfg(feature = "extensions-draft-08")]
248/// Decrypt the provided `ciphertext` with the `private_key`.
249/// The [`SafeEncryptionContext`] is used to set the [`ComponentId`], `label`,
250/// and optional `context`.
251///
252/// Returns an [`HpkeCiphertext`] or an [`enum@Error`].
253pub fn safe_decrypt_with_label(
254    private_key: &[u8],
255    ciphertext: &HpkeCiphertext,
256    ciphersuite: Ciphersuite,
257    context: SafeEncryptionContext,
258    crypto: &impl OpenMlsCrypto,
259) -> Result<Vec<u8>, Error> {
260    let component_operation_label =
261        ComponentOperationLabel::new(context.component_id, context.label);
262
263    let context: EncryptContext = EncryptContext::new_from_component_operation_label(
264        component_operation_label,
265        context.context.into(),
266    )?;
267
268    decrypt_with_label_internal(private_key, context, ciphertext, ciphersuite, crypto)
269}