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