bc_envelope/extension/
encrypt.rs

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