bc_envelope/extension/
encrypt.rs

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