bc_envelope/extension/sskr.rs
1use std::collections::HashMap;
2
3use anyhow::{Result, bail};
4pub use bc_components::{
5 SSKRError, SSKRGroupSpec, SSKRSecret, SSKRShare, SSKRSpec,
6};
7use bc_components::{SymmetricKey, sskr_combine, sskr_generate_using};
8use bc_rand::RandomNumberGenerator;
9#[cfg(feature = "known_value")]
10use known_values;
11
12use crate::{Envelope, Error};
13
14/// Support for splitting and combining envelopes using SSKR (Shamir's Secret
15/// Sharing).
16///
17/// This module extends Gordian Envelope with functions to support Sharded
18/// Secret Key Reconstruction (SSKR), which is an implementation of Shamir's
19/// Secret Sharing. SSKR allows splitting a secret (in this case, a symmetric
20/// encryption key) into multiple shares, with a threshold required for
21/// reconstruction.
22///
23/// SSKR provides social recovery for encrypted envelopes by allowing the owner
24/// to distribute shares to trusted individuals or storage locations, with a
25/// specified threshold required to reconstruct the original envelope.
26///
27/// # How SSKR Works with Envelopes
28///
29/// The overall process is as follows:
30///
31/// 1. A Gordian Envelope is encrypted with a symmetric key
32/// 2. The symmetric key is split into multiple SSKR shares using a group
33/// threshold and member thresholds
34/// 3. Each share is added as an assertion to a copy of the encrypted envelope
35/// 4. These envelope copies are distributed to trusted individuals or storage
36/// locations
37/// 5. Later, when enough shares are brought together, the original envelope can
38/// be reconstructed
39///
40/// Sharded Secret Key Reconstruction (SSKR) for Envelopes.
41///
42/// For a complete working example, see the `sskr_split()` and `sskr_join()`
43/// method documentation.
44impl Envelope {
45 /// Returns a new `Envelope` with a `sskrShare: SSKRShare` assertion added.
46 ///
47 /// This is an internal helper function used by the SSKR split methods.
48 fn add_sskr_share(&self, share: &SSKRShare) -> Self {
49 self.add_assertion(known_values::SSKR_SHARE, share.clone())
50 }
51
52 /// Splits the envelope into a set of SSKR shares.
53 ///
54 /// This method splits the symmetric key used to encrypt the envelope into
55 /// SSKR shares, and returns multiple copies of the original envelope,
56 /// each with a different SSKR share added as an assertion. The envelope
57 /// subject should already be encrypted with the provided `content_key`.
58 ///
59 /// The returned structure is a nested array that preserves the group
60 /// structure of the SSKR shares. Each outer array represents a group,
61 /// and each inner array contains the shares for that group.
62 ///
63 /// # Parameters
64 ///
65 /// * `spec` - The SSKR specification that defines the group structure and
66 /// thresholds
67 /// * `content_key` - The symmetric key that was used to encrypt the
68 /// envelope
69 ///
70 /// # Returns
71 ///
72 /// A nested array of envelopes organized by groups, each envelope
73 /// containing the original encrypted envelope with a unique SSKR share
74 /// added as an assertion
75 ///
76 /// # Errors
77 ///
78 /// Returns an error if the SSKR shares cannot be generated
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// use bc_envelope::prelude::*;
84 /// use bc_components::{SymmetricKey, SSKRGroupSpec, SSKRSpec};
85 ///
86 /// // Create and encrypt an envelope
87 /// let envelope = Envelope::new("Secret data").encrypt(&SymmetricKey::new());
88 ///
89 /// // Define a 2-of-3 SSKR split
90 /// let group_spec = SSKRGroupSpec::new(2, 3).unwrap();
91 /// let spec = SSKRSpec::new(1, vec![group_spec]).unwrap();
92 ///
93 /// // Create a symmetric key for the encrypted content
94 /// let content_key = SymmetricKey::new();
95 ///
96 /// // Split the envelope into shares
97 /// let shares = envelope.sskr_split(&spec, &content_key).unwrap();
98 ///
99 /// // The outer array represents groups (in this case, a single group)
100 /// assert_eq!(shares.len(), 1);
101 ///
102 /// // The inner array contains the shares for the group (3 shares in this example)
103 /// assert_eq!(shares[0].len(), 3);
104 ///
105 /// // Each share is an envelope with an 'sskrShare' assertion
106 /// for share in &shares[0] {
107 /// assert!(share.assertions_with_predicate(known_values::SSKR_SHARE).len() > 0);
108 /// }
109 /// ```
110 pub fn sskr_split(
111 &self,
112 spec: &SSKRSpec,
113 content_key: &SymmetricKey,
114 ) -> Result<Vec<Vec<Envelope>>> {
115 let mut rng = bc_rand::SecureRandomNumberGenerator;
116 self.sskr_split_using(spec, content_key, &mut rng)
117 }
118
119 /// Splits the envelope into a flattened set of SSKR shares.
120 ///
121 /// This method works like `sskr_split()` but returns a flat array of all
122 /// shares rather than preserving the group structure. This is
123 /// convenient when the group structure is not needed for distribution.
124 ///
125 /// # Parameters
126 ///
127 /// * `spec` - The SSKR specification that defines the group structure and
128 /// thresholds
129 /// * `content_key` - The symmetric key that was used to encrypt the
130 /// envelope
131 ///
132 /// # Returns
133 ///
134 /// A flat array of all envelopes containing SSKR shares
135 ///
136 /// # Errors
137 ///
138 /// Returns an error if the SSKR shares cannot be generated
139 ///
140 /// # Examples
141 ///
142 /// ```
143 /// use bc_components::{SSKRGroupSpec, SSKRSpec, SymmetricKey};
144 /// use bc_envelope::prelude::*;
145 ///
146 /// // Create and encrypt an envelope
147 /// let envelope = Envelope::new("Secret data").encrypt(&SymmetricKey::new());
148 ///
149 /// // Define a 2-of-3 SSKR split in a single group
150 /// let group_spec = SSKRGroupSpec::new(2, 3).unwrap();
151 /// let spec = SSKRSpec::new(1, vec![group_spec]).unwrap();
152 ///
153 /// // Create a symmetric key for the encrypted content
154 /// let content_key = SymmetricKey::new();
155 ///
156 /// // Split the envelope into a flat array of shares
157 /// let shares = envelope.sskr_split_flattened(&spec, &content_key).unwrap();
158 ///
159 /// // We get all 3 shares in a flat array
160 /// assert_eq!(shares.len(), 3);
161 ///
162 /// // Each share is an envelope with an 'sskrShare' assertion
163 /// for share in &shares {
164 /// assert!(
165 /// share
166 /// .assertions_with_predicate(known_values::SSKR_SHARE)
167 /// .len()
168 /// > 0
169 /// );
170 /// }
171 /// ```
172 pub fn sskr_split_flattened(
173 &self,
174 spec: &SSKRSpec,
175 content_key: &SymmetricKey,
176 ) -> Result<Vec<Envelope>> {
177 Ok(self
178 .sskr_split(spec, content_key)?
179 .into_iter()
180 .flatten()
181 .collect())
182 }
183
184 #[doc(hidden)]
185 /// Internal function that splits the envelope using a provided random
186 /// number generator.
187 ///
188 /// This method is primarily used for testing to ensure deterministic SSKR
189 /// shares.
190 pub fn sskr_split_using(
191 &self,
192 spec: &SSKRSpec,
193 content_key: &SymmetricKey,
194 test_rng: &mut impl RandomNumberGenerator,
195 ) -> Result<Vec<Vec<Envelope>>> {
196 let master_secret = SSKRSecret::new(content_key.data())?;
197 let shares = sskr_generate_using(spec, &master_secret, test_rng)?;
198 let mut result: Vec<Vec<Envelope>> = Vec::new();
199 for group in shares {
200 let mut group_result: Vec<Envelope> = Vec::new();
201 for share in group {
202 let share_result = self.add_sskr_share(&share);
203 group_result.push(share_result);
204 }
205 result.push(group_result);
206 }
207 Ok(result)
208 }
209
210 /// Internal helper function that extracts SSKR shares from a set of
211 /// envelopes.
212 ///
213 /// This function groups the shares by their identifier (which must match
214 /// for shares to be combined).
215 fn sskr_shares_in(
216 envelopes: &[&Envelope],
217 ) -> Result<HashMap<u16, Vec<SSKRShare>>> {
218 let mut result: HashMap<u16, Vec<SSKRShare>> = HashMap::new();
219 for envelope in envelopes {
220 for assertion in
221 envelope.assertions_with_predicate(known_values::SSKR_SHARE)
222 {
223 let share = assertion
224 .as_object()
225 .unwrap()
226 .extract_subject::<SSKRShare>()?;
227 let identifier = share.identifier();
228 result
229 .entry(identifier)
230 .and_modify(|shares| shares.push(share.clone()))
231 .or_insert(vec![share]);
232 }
233 }
234 Ok(result)
235 }
236
237 /// Reconstructs the original envelope from a set of SSKR shares.
238 ///
239 /// Given a set of envelopes with SSKR share assertions, this method
240 /// attempts to combine the shares to reconstruct the original symmetric
241 /// key. If successful, it uses the key to decrypt the envelope and
242 /// return the original envelope subject.
243 ///
244 /// The method will try all combinations of shares with matching identifiers
245 /// to find a valid reconstruction.
246 ///
247 /// # Parameters
248 ///
249 /// * `envelopes` - An array of envelope references containing SSKR shares
250 ///
251 /// # Returns
252 ///
253 /// The original envelope if reconstruction is successful
254 ///
255 /// # Errors
256 ///
257 /// * Returns `EnvelopeError::InvalidShares` if not enough valid shares are
258 /// provided
259 /// * Returns various errors if decryption fails
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// use bc_envelope::prelude::*;
265 /// use bc_components::{SymmetricKey, SSKRGroupSpec, SSKRSpec};
266 ///
267 /// // Create the original envelope with an assertion
268 /// let original = Envelope::new("Secret message")
269 /// .add_assertion("metadata", "This is a test");
270 ///
271 /// // Create a content key
272 /// let content_key = SymmetricKey::new();
273 ///
274 /// // Wrap the envelope (so the whole envelope including its assertions
275 /// // become the subject)
276 /// let wrapped_original = original
277 /// .wrap();
278 ///
279 /// // Encrypt the wrapped envelope
280 /// let encrypted = wrapped_original
281 /// .encrypt_subject(&content_key).unwrap();
282 ///
283 /// // Create a 2-of-3 SSKR split specification
284 /// let group = SSKRGroupSpec::new(2, 3).unwrap();
285 /// let spec = SSKRSpec::new(1, vec![group]).unwrap();
286 ///
287 /// // Split the encrypted envelope into shares
288 /// let shares = encrypted.sskr_split(&spec, &content_key).unwrap();
289 /// assert_eq!(shares[0].len(), 3);
290 ///
291 /// // The shares would normally be distributed to different people/places
292 /// // For recovery, we need at least the threshold number of shares (2 in this case)
293 /// let share1 = &shares[0][0];
294 /// let share2 = &shares[0][1];
295 ///
296 /// // Combine the shares to recover the original decrypted envelope
297 /// let recovered_wrapped = Envelope::sskr_join(&[share1, share2]).unwrap();
298 ///
299 /// // Unwrap the envelope to get the original envelope
300 /// let recovered = recovered_wrapped.try_unwrap().unwrap();
301 ///
302 /// // Check that the recovered envelope matches the original
303 /// assert!(recovered.is_identical_to(&original));
304 /// ```
305 pub fn sskr_join(envelopes: &[&Envelope]) -> Result<Envelope> {
306 if envelopes.is_empty() {
307 bail!(Error::InvalidShares);
308 }
309
310 let grouped_shares: Vec<_> =
311 Self::sskr_shares_in(envelopes)?.values().cloned().collect();
312 for shares in grouped_shares {
313 if let Ok(secret) = sskr_combine(&shares) {
314 if let Ok(content_key) = SymmetricKey::from_data_ref(&secret) {
315 if let Ok(envelope) =
316 envelopes.first().unwrap().decrypt_subject(&content_key)
317 {
318 return Ok(envelope.subject());
319 }
320 }
321 }
322 }
323 bail!(Error::InvalidShares)
324 }
325}
326
327#[cfg(all(test, feature = "sskr", feature = "types", feature = "known_value"))]
328mod tests {
329 use bc_components::{SSKRGroupSpec, SSKRSpec, SymmetricKey};
330
331 use crate::prelude::*;
332
333 #[test]
334 fn test_sskr_split_and_join() {
335 // Create the original envelope with an assertion
336 let original = Envelope::new("Secret message")
337 .add_assertion("metadata", "This is a test");
338
339 // Create a content key
340 let content_key = SymmetricKey::new();
341
342 // Wrap the envelope (so the whole envelope including its assertions
343 // become the subject)
344 let wrapped_original = original.wrap();
345
346 // Encrypt the wrapped envelope
347 let encrypted = wrapped_original.encrypt_subject(&content_key).unwrap();
348
349 // Create a 2-of-3 SSKR split specification
350 let group = SSKRGroupSpec::new(2, 3).unwrap();
351 let spec = SSKRSpec::new(1, vec![group]).unwrap();
352
353 // Split the encrypted envelope into shares
354 let shares = encrypted.sskr_split(&spec, &content_key).unwrap();
355 assert_eq!(shares[0].len(), 3);
356
357 // The shares would normally be distributed to different people/places
358 // For recovery, we need at least the threshold number of shares (2 in
359 // this case)
360 let share1 = &shares[0][0];
361 let share2 = &shares[0][1];
362
363 // Combine the shares to recover the original decrypted envelope
364 let recovered_wrapped = Envelope::sskr_join(&[share1, share2]).unwrap();
365
366 // Unwrap the envelope to get the original envelope
367 let recovered = recovered_wrapped.try_unwrap().unwrap();
368
369 // Check that the recovered envelope matches the original
370 assert!(recovered.is_identical_to(&original));
371 }
372}