bc_envelope/extension/
encrypt.rs

1use std::borrow::Cow;
2
3use anyhow::{Result, bail};
4use bc_components::{Digest, DigestProvider, Nonce, SymmetricKey, tags};
5use dcbor::prelude::*;
6
7use crate::{Envelope, Error, base::envelope::EnvelopeCase};
8
9/// Support for encrypting and decrypting envelopes using symmetric encryption.
10///
11/// This module extends Gordian Envelope with functions for symmetric encryption
12/// and decryption using the IETF-ChaCha20-Poly1305 construct. It enables
13/// privacy-enhancing operations by allowing envelope elements to be encrypted
14/// without changing the envelope's digest, similar to elision.
15///
16/// The encryption process preserves the envelope's digest tree structure, which
17/// means signatures, proofs, and other cryptographic artifacts remain valid
18/// even when parts of the envelope are encrypted.
19///
20/// # Examples
21///
22/// ```
23/// use bc_components::SymmetricKey;
24/// use bc_envelope::prelude::*;
25///
26/// // Create an envelope
27/// let envelope = Envelope::new("Hello world");
28///
29/// // Generate a symmetric key for encryption
30/// let key = SymmetricKey::new();
31///
32/// // Encrypt the envelope's subject
33/// let encrypted = envelope.encrypt_subject(&key).unwrap();
34///
35/// // The encrypted envelope has the same digest as the original
36/// assert_eq!(envelope.digest(), encrypted.digest());
37///
38/// // The subject is now encrypted
39/// assert!(encrypted.subject().is_encrypted());
40///
41/// // Decrypt the envelope
42/// let decrypted = encrypted.decrypt_subject(&key).unwrap();
43///
44/// // The decrypted envelope is equivalent to the original
45/// assert!(envelope.is_equivalent_to(&decrypted));
46/// ```
47///
48/// For encrypting the entire envelope including its assertions, you must first
49/// wrap the envelope:
50///
51/// ```
52/// use bc_components::SymmetricKey;
53/// use bc_envelope::prelude::*;
54///
55/// // Create an envelope with assertions
56/// let envelope = Envelope::new("Alice")
57///     .add_assertion("knows", "Bob")
58///     .add_assertion("knows", "Carol");
59///
60/// // Generate a symmetric key
61/// let key = SymmetricKey::new();
62///
63/// // Encrypt the entire envelope (wrapper method does the wrapping for you)
64/// let encrypted = envelope.encrypt(&key);
65///
66/// // Decrypt the entire envelope
67/// let decrypted = encrypted.decrypt(&key).unwrap();
68///
69/// // The decrypted envelope is equivalent to the original
70/// assert!(envelope.is_equivalent_to(&decrypted));
71/// ```
72impl Envelope {
73    /// Returns a new envelope with its subject encrypted.
74    ///
75    /// Encrypts only the subject of the envelope, leaving assertions
76    /// unencrypted. To encrypt an entire envelope including its assertions,
77    /// it must first be wrapped using the `wrap()` method, or you
78    /// can use the `encrypt()` convenience method.
79    ///
80    /// The encryption uses ChaCha20-Poly1305 and preserves the envelope's
81    /// digest, allowing for features like selective disclosure and
82    /// signature verification to work even on encrypted envelopes.
83    ///
84    /// # Parameters
85    ///
86    /// * `key` - The `SymmetricKey` to use for encryption
87    ///
88    /// # Returns
89    ///
90    /// A new envelope with its subject encrypted
91    ///
92    /// # Errors
93    ///
94    /// Returns an error if the envelope is already encrypted or elided
95    pub fn encrypt_subject(&self, key: &SymmetricKey) -> Result<Self> {
96        self.encrypt_subject_opt(key, None)
97    }
98
99    #[doc(hidden)]
100    /// Internal function for encrypting with an optional test nonce
101    pub fn encrypt_subject_opt(
102        &self,
103        key: &SymmetricKey,
104        test_nonce: Option<Nonce>,
105    ) -> Result<Self> {
106        let result: Self;
107        let original_digest: Cow<'_, Digest>;
108
109        match self.case() {
110            EnvelopeCase::Node {
111                subject,
112                assertions,
113                digest: envelope_digest,
114            } => {
115                if subject.is_encrypted() {
116                    bail!(Error::AlreadyEncrypted);
117                }
118                let encoded_cbor = subject.tagged_cbor().to_cbor_data();
119                let digest = subject.digest();
120                let encrypted_message =
121                    key.encrypt_with_digest(encoded_cbor, digest, test_nonce);
122                let encrypted_subject =
123                    Self::new_with_encrypted(encrypted_message).unwrap();
124                result = Self::new_with_unchecked_assertions(
125                    encrypted_subject,
126                    assertions.clone(),
127                );
128                original_digest = Cow::Borrowed(envelope_digest);
129            }
130            EnvelopeCase::Leaf { cbor, digest } => {
131                let encoded_cbor = CBOR::to_tagged_value(
132                    tags::TAG_ENVELOPE,
133                    CBOR::to_tagged_value(tags::TAG_LEAF, cbor.clone()),
134                )
135                .to_cbor_data();
136                let encrypted_message =
137                    key.encrypt_with_digest(encoded_cbor, digest, test_nonce);
138                result = Self::new_with_encrypted(encrypted_message).unwrap();
139                original_digest = Cow::Borrowed(digest);
140            }
141            EnvelopeCase::Wrapped { digest, .. } => {
142                let encoded_cbor = self.tagged_cbor().to_cbor_data();
143                let encrypted_message =
144                    key.encrypt_with_digest(encoded_cbor, digest, test_nonce);
145                result = Self::new_with_encrypted(encrypted_message).unwrap();
146                original_digest = Cow::Borrowed(digest);
147            }
148            EnvelopeCase::KnownValue { value, digest } => {
149                let encoded_cbor = CBOR::to_tagged_value(
150                    tags::TAG_ENVELOPE,
151                    value.untagged_cbor(),
152                )
153                .to_cbor_data();
154                let encrypted_message =
155                    key.encrypt_with_digest(encoded_cbor, digest, test_nonce);
156                result = Self::new_with_encrypted(encrypted_message).unwrap();
157                original_digest = Cow::Borrowed(digest);
158            }
159            EnvelopeCase::Assertion(assertion) => {
160                let digest = assertion.digest();
161                let encoded_cbor = CBOR::to_tagged_value(
162                    tags::TAG_ENVELOPE,
163                    assertion.clone(),
164                )
165                .to_cbor_data();
166                let encrypted_message =
167                    key.encrypt_with_digest(encoded_cbor, &digest, test_nonce);
168                result = Self::new_with_encrypted(encrypted_message).unwrap();
169                original_digest = digest;
170            }
171            EnvelopeCase::Encrypted { .. } => {
172                bail!(Error::AlreadyEncrypted);
173            }
174            #[cfg(feature = "compress")]
175            EnvelopeCase::Compressed(compressed) => {
176                let digest = compressed.digest();
177                let encoded_cbor = CBOR::to_tagged_value(
178                    tags::TAG_ENVELOPE,
179                    compressed.tagged_cbor(),
180                )
181                .to_cbor_data();
182                let encrypted_message =
183                    key.encrypt_with_digest(encoded_cbor, &digest, test_nonce);
184                result = Self::new_with_encrypted(encrypted_message).unwrap();
185                original_digest = digest;
186            }
187            EnvelopeCase::Elided { .. } => {
188                bail!(Error::AlreadyElided);
189            }
190        }
191        assert_eq!(result.digest(), original_digest);
192        Ok(result)
193    }
194
195    /// Returns a new envelope with its subject decrypted.
196    ///
197    /// Decrypts the subject of an envelope that was previously encrypted using
198    /// `encrypt_subject()`. The symmetric key used must be the same one
199    /// used for encryption.
200    ///
201    /// # Parameters
202    ///
203    /// * `key` - The `SymmetricKey` to use for decryption
204    ///
205    /// # Returns
206    ///
207    /// A new envelope with its subject decrypted
208    ///
209    /// # Errors
210    ///
211    /// * Returns an error if the envelope's subject is not encrypted
212    /// * Returns an error if the key is incorrect
213    /// * Returns an error if the digest of the decrypted envelope doesn't match
214    ///   the expected digest
215    pub fn decrypt_subject(&self, key: &SymmetricKey) -> Result<Self> {
216        match self.subject().case() {
217            EnvelopeCase::Encrypted(message) => {
218                let encoded_cbor = key.decrypt(message)?;
219                let subject_digest =
220                    message.aad_digest().ok_or(Error::MissingDigest)?;
221                let cbor = CBOR::try_from_data(encoded_cbor)?;
222                let result_subject = Self::from_tagged_cbor(cbor)?;
223                if *result_subject.digest() != subject_digest {
224                    bail!(Error::InvalidDigest);
225                }
226                match self.case() {
227                    EnvelopeCase::Node { assertions, digest, .. } => {
228                        let result = Self::new_with_unchecked_assertions(
229                            result_subject,
230                            assertions.clone(),
231                        );
232                        if *result.digest() != *digest {
233                            bail!(Error::InvalidDigest);
234                        }
235                        Ok(result)
236                    }
237                    _ => Ok(result_subject),
238                }
239            }
240            _ => bail!(Error::NotEncrypted),
241        }
242    }
243}
244
245impl Envelope {
246    /// Convenience method to encrypt an entire envelope including its
247    /// assertions.
248    ///
249    /// This method wraps the envelope and then encrypts its subject, which has
250    /// the effect of encrypting the entire original envelope including all
251    /// its assertions.
252    ///
253    /// # Parameters
254    ///
255    /// * `key` - The `SymmetricKey` to use for encryption
256    ///
257    /// # Returns
258    ///
259    /// A new envelope with the entire original envelope encrypted as its
260    /// subject
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use bc_components::SymmetricKey;
266    /// use bc_envelope::prelude::*;
267    ///
268    /// // Create an envelope with assertions
269    /// let envelope = Envelope::new("Alice").add_assertion("knows", "Bob");
270    ///
271    /// // Generate a symmetric key
272    /// let key = SymmetricKey::new();
273    ///
274    /// // Encrypt the entire envelope
275    /// let encrypted = envelope.encrypt(&key);
276    /// ```
277    pub fn encrypt(&self, key: &SymmetricKey) -> Envelope {
278        self.wrap().encrypt_subject(key).unwrap()
279    }
280
281    /// Convenience method to decrypt an entire envelope that was encrypted
282    /// using the `encrypt()` method.
283    ///
284    /// This method decrypts the subject and then unwraps the resulting
285    /// envelope, returning the original envelope with all its assertions.
286    ///
287    /// # Parameters
288    ///
289    /// * `key` - The `SymmetricKey` to use for decryption
290    ///
291    /// # Returns
292    ///
293    /// The original decrypted envelope
294    ///
295    /// # Errors
296    ///
297    /// * Returns an error if the envelope is not encrypted
298    /// * Returns an error if the key is incorrect
299    /// * Returns an error if the digest of the decrypted envelope doesn't match
300    ///   the expected digest
301    /// * Returns an error if the decrypted envelope cannot be unwrapped
302    ///
303    /// # Examples
304    ///
305    /// ```
306    /// use bc_components::SymmetricKey;
307    /// use bc_envelope::prelude::*;
308    ///
309    /// // Create an envelope with assertions
310    /// let envelope = Envelope::new("Alice").add_assertion("knows", "Bob");
311    ///
312    /// // Generate a symmetric key
313    /// let key = SymmetricKey::new();
314    ///
315    /// // Encrypt and then decrypt the entire envelope
316    /// let encrypted = envelope.encrypt(&key);
317    /// let decrypted = encrypted.decrypt(&key).unwrap();
318    ///
319    /// // The decrypted envelope is equivalent to the original
320    /// assert!(envelope.is_equivalent_to(&decrypted));
321    /// ```
322    pub fn decrypt(&self, key: &SymmetricKey) -> Result<Envelope> {
323        self.decrypt_subject(key)?.try_unwrap()
324    }
325}