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}