bc_components/symmetric/
encrypted_message.rs

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