bc_envelope/extension/
encrypt.rs

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