bc_xid/provenance.rs
1use bc_components::{KeyDerivationMethod, Salt};
2use bc_envelope::prelude::*;
3use known_values::PROVENANCE_GENERATOR;
4use provenance_mark::{ProvenanceMark, ProvenanceMarkGenerator};
5
6use crate::{Error, Result};
7
8/// Provenance mark generator data that can be either decrypted or encrypted.
9#[derive(Debug, Clone)]
10pub enum GeneratorData {
11 /// Decrypted generator that can be used for mark generation.
12 Decrypted(ProvenanceMarkGenerator),
13
14 /// Encrypted generator envelope that cannot be used without decryption.
15 /// This preserves the encrypted assertion when a document is loaded
16 /// without the decryption password.
17 ///
18 /// Note: Envelope uses internal reference counting (Rc/Arc) so cloning
19 /// is cheap - no need for additional wrapper.
20 Encrypted(Envelope),
21}
22
23impl PartialEq for GeneratorData {
24 fn eq(&self, other: &Self) -> bool {
25 match (self, other) {
26 (Self::Decrypted(a), Self::Decrypted(b)) => a == b,
27 (Self::Encrypted(a), Self::Encrypted(b)) => {
28 // Compare envelopes by their UR string representation
29 a.ur_string() == b.ur_string()
30 }
31 _ => false,
32 }
33 }
34}
35
36impl Eq for GeneratorData {}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Provenance {
40 mark: ProvenanceMark,
41 generator: Option<(GeneratorData, Salt)>,
42}
43
44impl Provenance {
45 pub fn new(mark: ProvenanceMark) -> Self { Self { mark, generator: None } }
46
47 pub fn new_with_generator(
48 generator: ProvenanceMarkGenerator,
49 mark: ProvenanceMark,
50 ) -> Self {
51 let salt = Salt::new_with_len(32).unwrap();
52 Self {
53 mark,
54 generator: Some((GeneratorData::Decrypted(generator), salt)),
55 }
56 }
57
58 pub fn mark(&self) -> &ProvenanceMark { &self.mark }
59
60 pub fn generator(&self) -> Option<&ProvenanceMarkGenerator> {
61 self.generator.as_ref().and_then(|(data, _)| match data {
62 GeneratorData::Decrypted(generator) => Some(generator),
63 GeneratorData::Encrypted(_) => None,
64 })
65 }
66
67 pub fn has_generator(&self) -> bool {
68 matches!(
69 self.generator.as_ref(),
70 Some((GeneratorData::Decrypted(_), _))
71 )
72 }
73
74 pub fn has_encrypted_generator(&self) -> bool {
75 matches!(
76 self.generator.as_ref(),
77 Some((GeneratorData::Encrypted(_), _))
78 )
79 }
80
81 pub fn generator_salt(&self) -> Option<&Salt> {
82 self.generator.as_ref().map(|(_, salt)| salt)
83 }
84
85 /// Get a mutable reference to the generator, decrypting if necessary.
86 ///
87 /// # Returns
88 ///
89 /// - `Ok(Some(&mut ProvenanceMarkGenerator))` if generator is available
90 /// - `Ok(None)` if no generator is present
91 /// - `Err(Error::InvalidPassword)` if encrypted and wrong password provided
92 pub fn generator_mut(
93 &mut self,
94 password: Option<&[u8]>,
95 ) -> Result<Option<&mut ProvenanceMarkGenerator>> {
96 match &mut self.generator {
97 None => Ok(None),
98 Some((GeneratorData::Decrypted(generator), _)) => {
99 Ok(Some(generator))
100 }
101 Some((encrypted @ GeneratorData::Encrypted(_), _salt)) => {
102 if let Some(pwd) = password {
103 // Extract the encrypted envelope
104 let encrypted_envelope = match encrypted {
105 GeneratorData::Encrypted(env) => env.clone(),
106 _ => unreachable!(),
107 };
108
109 // Try to decrypt
110 match encrypted_envelope.unlock_subject(pwd) {
111 Ok(decrypted) => {
112 let unwrapped = decrypted.try_unwrap()?;
113 let generator =
114 ProvenanceMarkGenerator::try_from(unwrapped)?;
115
116 // Replace encrypted with decrypted
117 *encrypted = GeneratorData::Decrypted(generator);
118
119 // Now get the mutable reference
120 match encrypted {
121 GeneratorData::Decrypted(g) => Ok(Some(g)),
122 _ => unreachable!(),
123 }
124 }
125 Err(_) => Err(Error::InvalidPassword),
126 }
127 } else {
128 // Cannot decrypt without password
129 Err(Error::InvalidPassword)
130 }
131 }
132 }
133 }
134
135 /// Update the provenance mark.
136 pub fn set_mark(&mut self, mark: ProvenanceMark) { self.mark = mark; }
137
138 /// Extract the optional generator and salt for mutation or storage.
139 pub fn take_generator(&mut self) -> Option<(GeneratorData, Salt)> {
140 self.generator.take()
141 }
142
143 /// Set or replace the generator.
144 pub fn set_generator(&mut self, generator: ProvenanceMarkGenerator) {
145 let salt = Salt::new_with_len(32).unwrap();
146 self.generator = Some((GeneratorData::Decrypted(generator), salt));
147 }
148
149 /// Extract the generator envelope, optionally decrypting it.
150 ///
151 /// # Returns
152 ///
153 /// - `Ok(None)` if no generator is present
154 /// - `Ok(Some(Envelope))` containing:
155 /// - Decrypted `ProvenanceMarkGenerator` if unencrypted
156 /// - Decrypted `ProvenanceMarkGenerator` if encrypted and correct
157 /// password provided
158 /// - Encrypted envelope if encrypted and no password provided
159 /// - `Err(...)` if encrypted and wrong password provided
160 pub fn generator_envelope(
161 &self,
162 password: Option<&str>,
163 ) -> Result<Option<Envelope>> {
164 match &self.generator {
165 None => Ok(None),
166 Some((GeneratorData::Decrypted(generator), _)) => {
167 // Unencrypted generator - return as envelope
168 Ok(Some(Envelope::new(generator.clone())))
169 }
170 Some((GeneratorData::Encrypted(encrypted_envelope), _)) => {
171 if let Some(pwd) = password {
172 // Try to decrypt with provided password
173 match encrypted_envelope.clone().unlock_subject(pwd) {
174 Ok(decrypted) => {
175 // Successfully decrypted and unwrapped
176 let unwrapped = decrypted.try_unwrap()?;
177 Ok(Some(unwrapped))
178 }
179 Err(_) => {
180 // Wrong password
181 Err(Error::InvalidPassword)
182 }
183 }
184 } else {
185 // No password provided, return encrypted envelope as-is
186 Ok(Some(encrypted_envelope.clone()))
187 }
188 }
189 }
190 }
191}
192
193/// Options for handling generators in envelopes.
194#[derive(Clone, Debug, PartialEq, Eq, Default)]
195pub enum XIDGeneratorOptions {
196 /// Omit the generator from the envelope (default).
197 #[default]
198 Omit,
199
200 /// Include the generator in plaintext (with salt for decorrelation).
201 Include,
202
203 /// Include the generator assertion but elide it (maintains digest tree).
204 Elide,
205
206 /// Include the generator encrypted with a password using the specified
207 /// key derivation method.
208 Encrypt {
209 method: KeyDerivationMethod,
210 password: Vec<u8>,
211 },
212}
213
214impl Provenance {
215 fn generator_assertion_envelope(&self) -> Envelope {
216 let (generator_data, salt) = self.generator.clone().unwrap();
217 match generator_data {
218 GeneratorData::Decrypted(generator) => {
219 Envelope::new_assertion(PROVENANCE_GENERATOR, generator)
220 .add_salt_instance(salt)
221 }
222 GeneratorData::Encrypted(encrypted_envelope) => {
223 // Already encrypted, just wrap with provenanceGenerator
224 // predicate and salt
225 Envelope::new_assertion(
226 PROVENANCE_GENERATOR,
227 encrypted_envelope,
228 )
229 .add_salt_instance(salt)
230 }
231 }
232 }
233
234 fn extract_optional_generator_with_password(
235 envelope: &Envelope,
236 password: Option<&[u8]>,
237 ) -> Result<Option<(GeneratorData, Salt)>> {
238 if let Some(generator_assertion) =
239 envelope.optional_assertion_with_predicate(PROVENANCE_GENERATOR)?
240 {
241 let generator_object =
242 generator_assertion.subject().try_object()?;
243
244 // Extract the salt (always present)
245 let salt = generator_assertion
246 .extract_object_for_predicate::<Salt>(known_values::SALT)?;
247
248 // Check if the generator object is locked with a password
249 if generator_object.is_locked_with_password() {
250 // Need a password to decrypt
251 if let Some(pwd) = password {
252 // Try to unlock with the password
253 match generator_object.unlock_subject(pwd) {
254 Ok(decrypted) => {
255 // Successfully decrypted, unwrap and extract the
256 // generator
257 let unwrapped = decrypted.try_unwrap()?;
258 let generator =
259 ProvenanceMarkGenerator::try_from(unwrapped)?;
260 return Ok(Some((
261 GeneratorData::Decrypted(generator),
262 salt,
263 )));
264 }
265 Err(_) => {
266 // Wrong password or decryption failed
267 // Store the encrypted envelope for later
268 return Ok(Some((
269 GeneratorData::Encrypted(
270 generator_object.clone(),
271 ),
272 salt,
273 )));
274 }
275 }
276 } else {
277 // No password provided, store encrypted envelope
278 return Ok(Some((
279 GeneratorData::Encrypted(generator_object.clone()),
280 salt,
281 )));
282 }
283 }
284
285 // Extract plaintext generator
286 let generator =
287 ProvenanceMarkGenerator::try_from(generator_object.clone())?;
288 return Ok(Some((GeneratorData::Decrypted(generator), salt)));
289 }
290 Ok(None)
291 }
292
293 pub fn into_envelope_opt(
294 self,
295 generator_options: XIDGeneratorOptions,
296 ) -> Envelope {
297 let mut envelope = Envelope::new(self.mark().clone());
298 if let Some((generator_data, _)) = &self.generator {
299 match generator_data {
300 GeneratorData::Encrypted(_) => {
301 // Always preserve encrypted generators, regardless of
302 // options
303 let assertion_envelope =
304 self.generator_assertion_envelope();
305 envelope = envelope
306 .add_assertion_envelope(assertion_envelope)
307 .unwrap();
308 }
309 GeneratorData::Decrypted(_) => {
310 // For decrypted generators, respect the generator_options
311 match generator_options {
312 XIDGeneratorOptions::Include => {
313 let assertion_envelope =
314 self.generator_assertion_envelope();
315 envelope = envelope
316 .add_assertion_envelope(assertion_envelope)
317 .unwrap();
318 }
319 XIDGeneratorOptions::Elide => {
320 let assertion_envelope =
321 self.generator_assertion_envelope().elide();
322 envelope = envelope
323 .add_assertion_envelope(assertion_envelope)
324 .unwrap();
325 }
326 XIDGeneratorOptions::Encrypt { method, password } => {
327 let (generator, salt) =
328 self.generator.clone().unwrap();
329
330 match generator {
331 GeneratorData::Decrypted(generator) => {
332 // Create an envelope with the generator
333 let generator_envelope =
334 Envelope::new(generator);
335
336 // Wrap and encrypt it using lock_subject
337 let encrypted = generator_envelope
338 .wrap()
339 .lock_subject(method, password)
340 .expect("Failed to encrypt generator");
341
342 // Create the provenanceGenerator assertion
343 // with the encrypted envelope
344 let assertion_envelope =
345 Envelope::new_assertion(
346 PROVENANCE_GENERATOR,
347 encrypted,
348 )
349 .add_salt_instance(salt);
350
351 envelope = envelope
352 .add_assertion_envelope(
353 assertion_envelope,
354 )
355 .unwrap();
356 }
357 GeneratorData::Encrypted(
358 encrypted_envelope,
359 ) => {
360 // Already encrypted - we can't re-encrypt
361 // without decrypting first. Just preserve
362 // the
363 // existing encrypted envelope.
364 let assertion_envelope =
365 Envelope::new_assertion(
366 PROVENANCE_GENERATOR,
367 encrypted_envelope,
368 )
369 .add_salt_instance(salt);
370
371 envelope = envelope
372 .add_assertion_envelope(
373 assertion_envelope,
374 )
375 .unwrap();
376 }
377 }
378 }
379 XIDGeneratorOptions::Omit => {
380 // Omit decrypted generators
381 }
382 }
383 }
384 }
385 }
386
387 envelope
388 }
389}
390
391impl EnvelopeEncodable for Provenance {
392 fn into_envelope(self) -> Envelope {
393 self.into_envelope_opt(XIDGeneratorOptions::Omit)
394 }
395}
396
397impl TryFrom<&Envelope> for Provenance {
398 type Error = Error;
399
400 fn try_from(envelope: &Envelope) -> Result<Self> {
401 Self::try_from_envelope(envelope, None)
402 }
403}
404
405impl TryFrom<Envelope> for Provenance {
406 type Error = Error;
407
408 fn try_from(envelope: Envelope) -> Result<Self> {
409 Provenance::try_from(&envelope)
410 }
411}
412
413impl Provenance {
414 /// Try to extract a `Provenance` from an envelope, optionally providing a
415 /// password to decrypt an encrypted generator.
416 ///
417 /// If the generator is encrypted and no password is provided, the
418 /// `Provenance` will be created without the generator (it will be
419 /// `None`).
420 pub fn try_from_envelope(
421 envelope: &Envelope,
422 password: Option<&[u8]>,
423 ) -> Result<Self> {
424 let mark = ProvenanceMark::try_from(envelope.subject().try_leaf()?)?;
425 let generator = Provenance::extract_optional_generator_with_password(
426 envelope, password,
427 )?;
428
429 Ok(Self { mark, generator })
430 }
431}