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}