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/// HPKE labeled encryption errors.
47#[derive(Error, Debug, PartialEq, Clone)]
48pub(crate) enum Error {
49    /// Error while serializing content. This should only happen if a bounds check was missing.
50    #[error(
51        "Error while serializing content. This should only happen if a bounds check was missing."
52    )]
53    MissingBoundCheck,
54    /// Decryption failed.
55    #[error("Decryption failed.")]
56    DecryptionFailed,
57}
58
59impl From<tls_codec::Error> for Error {
60    fn from(_: tls_codec::Error) -> Self {
61        Self::MissingBoundCheck
62    }
63}
64
65impl From<CryptoError> for Error {
66    fn from(_: CryptoError) -> Self {
67        Self::DecryptionFailed
68    }
69}
70
71/// Context for HPKE encryption
72#[derive(Debug, Clone, TlsSerialize, TlsDeserialize, TlsDeserializeBytes, TlsSize)]
73pub struct EncryptContext {
74    label: VLBytes,
75    context: VLBytes,
76}
77
78impl EncryptContext {
79    /// Create a new [`EncryptContext`] from a string label and the content bytes.
80    pub fn new(label: &str, context: VLBytes) -> Self {
81        let label_string = LABEL_PREFIX.to_owned() + label;
82        let label = label_string.as_bytes().into();
83        Self { label, context }
84    }
85}
86
87impl From<(&str, &[u8])> for EncryptContext {
88    fn from((label, context): (&str, &[u8])) -> Self {
89        Self::new(label, context.into())
90    }
91}
92
93/// Encrypt to an HPKE key with a label.
94pub(crate) fn encrypt_with_label(
95    public_key: &[u8],
96    label: &str,
97    context: &[u8],
98    plaintext: &[u8],
99    ciphersuite: Ciphersuite,
100    crypto: &impl OpenMlsCrypto,
101) -> Result<HpkeCiphertext, Error> {
102    let context: EncryptContext = (label, context).into();
103    let context = context.tls_serialize_detached()?;
104
105    log_crypto!(
106        debug,
107        "HPKE Encrypt with label `{label}` and ciphersuite `{ciphersuite:?}`:"
108    );
109    log_crypto!(debug, "* context:     {context:x?}");
110    log_crypto!(debug, "* public key:  {public_key:x?}");
111    log_crypto!(debug, "* plaintext:   {plaintext:x?}");
112
113    let cipher = crypto.hpke_seal(
114        ciphersuite.hpke_config(),
115        public_key,
116        &context,
117        &[],
118        plaintext,
119    )?;
120
121    log_crypto!(debug, "* ciphertext:  {:x?}", cipher);
122
123    Ok(cipher)
124}
125
126/// Decrypt with HPKE and label.
127pub(crate) fn decrypt_with_label(
128    private_key: &[u8],
129    label: &str,
130    context: &[u8],
131    ciphertext: &HpkeCiphertext,
132    ciphersuite: Ciphersuite,
133    crypto: &impl OpenMlsCrypto,
134) -> Result<Vec<u8>, Error> {
135    let context: EncryptContext = (label, context).into();
136    let context = context.tls_serialize_detached()?;
137
138    log_crypto!(
139        debug,
140        "HPKE Decrypt with label `{label}` and `ciphersuite` {ciphersuite:?}:"
141    );
142    log_crypto!(debug, "* context:     {context:x?}");
143    log_crypto!(debug, "* private key: {private_key:x?}");
144    log_crypto!(debug, "* ciphertext:  {ciphertext:x?}");
145
146    let plaintext = crypto
147        .hpke_open(
148            ciphersuite.hpke_config(),
149            ciphertext,
150            private_key,
151            &context,
152            &[],
153        )
154        .map_err(|e| e.into());
155
156    log_crypto!(debug, "* plaintext:   {plaintext:x?}");
157
158    plaintext
159}