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