bc_components/symmetric/
encrypted_message.rs

1use std::borrow::Cow;
2
3use bc_ur::prelude::*;
4
5use crate::{AuthenticationTag, Digest, DigestProvider, Nonce, tags};
6
7/// A secure encrypted message using IETF ChaCha20-Poly1305 authenticated
8/// encryption.
9///
10/// `EncryptedMessage` represents data that has been encrypted using a symmetric
11/// key with the ChaCha20-Poly1305 AEAD (Authenticated Encryption with
12/// Associated Data) construction as specified in [RFC-8439](https://datatracker.ietf.org/doc/html/rfc8439).
13///
14/// An `EncryptedMessage` contains:
15/// - `ciphertext`: The encrypted data (same length as the original plaintext)
16/// - `aad`: Additional Authenticated Data that is not encrypted but is
17///   authenticated (optional)
18/// - `nonce`: A 12-byte number used once for this specific encryption operation
19/// - `auth`: A 16-byte authentication tag that verifies the integrity of the
20///   message
21///
22/// The `aad` field is often used to include the `Digest` of the plaintext,
23/// which allows verification of the plaintext after decryption and preserves
24/// the unique identity of the data when used with structures like Gordian
25/// Envelope.
26///
27/// To facilitate decoding, it is recommended that the plaintext of an
28/// `EncryptedMessage` be tagged CBOR.
29///
30/// CDDL:
31/// ```cddl
32/// EncryptedMessage =
33///     #6.40002([ ciphertext: bstr, nonce: bstr, auth: bstr, ? aad: bstr ]) ; TAG_ENCRYPTED
34/// ```
35#[derive(Clone, Eq, PartialEq)]
36pub struct EncryptedMessage {
37    ciphertext: Vec<u8>,
38    aad: Vec<u8>, // Additional authenticated data (AAD) per RFC8439
39    nonce: Nonce,
40    auth: AuthenticationTag,
41}
42
43impl EncryptedMessage {
44    /// Restores an EncryptedMessage from its CBOR representation.
45    ///
46    /// This is a low-level function that is not normally needed.
47    pub fn new(
48        ciphertext: impl AsRef<[u8]>,
49        aad: impl AsRef<[u8]>,
50        nonce: Nonce,
51        auth: AuthenticationTag,
52    ) -> Self {
53        Self {
54            ciphertext: ciphertext.as_ref().to_vec(),
55            aad: aad.as_ref().to_vec(),
56            nonce,
57            auth,
58        }
59    }
60
61    /// Returns a reference to the ciphertext data.
62    pub fn ciphertext(&self) -> &[u8] { &self.ciphertext }
63
64    /// Returns a reference to the additional authenticated data (AAD).
65    pub fn aad(&self) -> &[u8] { &self.aad }
66
67    /// Returns a reference to the nonce value used for encryption.
68    pub fn nonce(&self) -> &Nonce { &self.nonce }
69
70    /// Returns a reference to the authentication tag value used for encryption.
71    pub fn authentication_tag(&self) -> &AuthenticationTag { &self.auth }
72
73    /// Returns a CBOR representation in the AAD field, if it exists.
74    pub fn aad_cbor(&self) -> Option<CBOR> {
75        if self.aad.is_empty() {
76            None
77        } else {
78            CBOR::try_from_data(self.aad()).ok()
79        }
80    }
81
82    /// Returns a `Digest` instance if the AAD data can be parsed as
83    /// CBOR.
84    pub fn aad_digest(&self) -> Option<Digest> {
85        self.aad_cbor().and_then(|cbor| Digest::try_from(cbor).ok())
86    }
87
88    /// Returns `true` if the AAD data can be parsed as CBOR.
89    pub fn has_digest(&self) -> bool { self.aad_digest().is_some() }
90}
91
92/// Implements Debug formatting to display the message contents in a structured
93/// format.
94impl std::fmt::Debug for EncryptedMessage {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        f.debug_struct("EncryptedMessage")
97            .field("ciphertext", &hex::encode(&self.ciphertext))
98            .field("aad", &hex::encode(&self.aad))
99            .field("nonce", &self.nonce)
100            .field("auth", &self.auth)
101            .finish()
102    }
103}
104
105/// Implements `AsRef<EncryptedMessage>` to allow self-reference.
106impl AsRef<EncryptedMessage> for EncryptedMessage {
107    fn as_ref(&self) -> &EncryptedMessage { self }
108}
109
110/// Implements DigestProvider to provide the digest stored in the AAD field.
111impl DigestProvider for EncryptedMessage {
112    fn digest(&self) -> Cow<'_, Digest> {
113        let a = self.aad_digest().unwrap();
114        Cow::Owned(a)
115    }
116}
117
118/// Implements CBORTagged to provide the CBOR tag for the EncryptedMessage.
119impl CBORTagged for EncryptedMessage {
120    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_ENCRYPTED]) }
121}
122
123/// Implements conversion from EncryptedMessage to CBOR for serialization.
124impl From<EncryptedMessage> for CBOR {
125    fn from(value: EncryptedMessage) -> Self { value.tagged_cbor() }
126}
127
128/// Implements `TryFrom<CBOR>` for EncryptedMessage to support conversion from
129/// CBOR data.
130impl TryFrom<CBOR> for EncryptedMessage {
131    type Error = dcbor::Error;
132
133    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
134        Self::from_tagged_cbor(cbor)
135    }
136}
137
138/// Implements CBORTaggedEncodable to provide CBOR encoding functionality.
139impl CBORTaggedEncodable for EncryptedMessage {
140    fn untagged_cbor(&self) -> CBOR {
141        let mut a = vec![
142            CBOR::to_byte_string(&self.ciphertext),
143            CBOR::to_byte_string(self.nonce.data()),
144            CBOR::to_byte_string(self.auth.data()),
145        ];
146
147        if !self.aad.is_empty() {
148            a.push(CBOR::to_byte_string(&self.aad));
149        }
150
151        a.into()
152    }
153}
154
155/// Implements CBORTaggedDecodable to provide CBOR decoding functionality.
156impl CBORTaggedDecodable for EncryptedMessage {
157    fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
158        match cbor.as_case() {
159            CBORCase::Array(elements) => {
160                if elements.len() < 3 {
161                    return Err(
162                        "EncryptedMessage must have at least 3 elements".into(),
163                    );
164                }
165                let ciphertext =
166                    CBOR::try_into_byte_string(elements[0].clone())?;
167                let nonce_data =
168                    CBOR::try_into_byte_string(elements[1].clone())?;
169                let nonce = Nonce::from_data_ref(nonce_data)?;
170                let auth_data =
171                    CBOR::try_into_byte_string(elements[2].clone())?;
172                let auth = AuthenticationTag::from_data_ref(auth_data)?;
173                let aad = if elements.len() > 3 {
174                    CBOR::try_into_byte_string(elements[3].clone())?
175                } else {
176                    Vec::new()
177                };
178                Ok(Self::new(ciphertext, aad, nonce, auth))
179            }
180            _ => Err("EncryptedMessage must be an array".into()),
181        }
182    }
183}