bc_components/symmetric/
encrypted_message.rs

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