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}