devolutions_crypto/secret_sharing/
mod.rs

1//! Module for creating keys splitted between multiple parties.
2//! Use this for "Break The Glass" scenarios or when you want to cryptographically enforce
3//! approval of multiple users.
4//!
5//! This module is used to generate a key that is splitted in multiple `Share`
6//! and that requires a specific amount of them to regenerate the key.
7//! You can think of it as a "Break The Glass" scenario. You can
8//! generate a key using this, lock your entire data by encrypting it
9//! and then you will need, let's say, 3 out of the 5 administrators to decrypt
10//! the data. That data could also be an API key or password of a super admin account.
11//!
12//! ```rust
13//! use devolutions_crypto::secret_sharing::{generate_shared_key, join_shares, SecretSharingVersion, Share};
14//!
15//! // You want a key of 32 bytes, splitted between 5 people, and I want a
16//! // minimum of 3 of these shares to regenerate the key.
17//! let shares: Vec<Share> = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).expect("generation shouldn't fail with the right parameters");
18//!
19//! assert_eq!(shares.len(), 5);
20//! let key = join_shares(&shares[2..5]).expect("joining shouldn't fail with the right shares");
21//! ```
22
23mod secret_sharing_v1;
24
25use super::DataType;
26use super::Error;
27use super::Header;
28use super::HeaderType;
29use super::Result;
30pub use super::SecretSharingVersion;
31use super::ShareSubtype;
32
33use secret_sharing_v1::ShareV1;
34
35use std::borrow::Borrow;
36use std::convert::TryFrom;
37
38#[cfg(feature = "fuzz")]
39use arbitrary::Arbitrary;
40
41/// A part of the secret key. You need multiple of them to recompute the secret key.
42#[derive(Clone, Debug)]
43#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
44pub struct Share {
45    pub(crate) header: Header<Share>,
46    payload: SharePayload,
47}
48
49impl HeaderType for Share {
50    type Version = SecretSharingVersion;
51    type Subtype = ShareSubtype;
52
53    fn data_type() -> DataType {
54        DataType::Share
55    }
56}
57
58#[derive(Clone, Debug)]
59#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
60enum SharePayload {
61    V1(ShareV1),
62}
63
64/// Generate a key and split it in `n_shares`. You will need `threshold` shares to recover the key.
65///
66/// # Arguments
67///
68/// * `n_shares` - Number of shares to generate
69/// * `threshold` - The number of shares needed to recover the key
70/// * `length` - The desired length of the key to generate
71///  * `version` - Version of the secret sharing scheme to use. Use `SecretSharingVersion::Latest` if you're not dealing with shared data.
72///
73/// # Returns
74/// Returns an array of `Share`.
75///
76/// # Example
77/// ```
78/// use devolutions_crypto::secret_sharing::{ generate_shared_key, SecretSharingVersion };
79/// let shares = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).unwrap();
80/// ```
81pub fn generate_shared_key(
82    n_shares: u8,
83    threshold: u8,
84    length: usize,
85    version: SecretSharingVersion,
86) -> Result<Vec<Share>> {
87    let mut header = Header::default();
88
89    match version {
90        SecretSharingVersion::V1 | SecretSharingVersion::Latest => {
91            header.version = SecretSharingVersion::V1;
92
93            let shares = ShareV1::generate_shared_key(n_shares, threshold, length)?;
94
95            Ok(shares
96                .map(|s| Share {
97                    header: header.clone(),
98                    payload: SharePayload::V1(s),
99                })
100                .collect())
101        }
102    }
103}
104
105/// Join multiple `Share` to regenerate a secret key.
106///
107/// # Arguments
108///
109/// * `shares` - The `Share`s to join
110///
111/// # Example
112/// ```
113/// use devolutions_crypto::secret_sharing::{generate_shared_key, join_shares, SecretSharingVersion};
114/// let shares = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).unwrap();
115///
116/// assert_eq!(shares.len(), 5);
117///
118/// let key = join_shares(&shares[2..5]).unwrap();
119/// ```
120pub fn join_shares<'a, I, J>(shares: I) -> Result<Vec<u8>>
121where
122    I: IntoIterator<Item = &'a Share, IntoIter = J>,
123    J: Iterator<Item = &'a Share> + Clone,
124{
125    let shares = shares.into_iter();
126
127    let version = match shares.clone().peekable().peek() {
128        Some(x) => x.header.version,
129        None => return Err(Error::NotEnoughShares),
130    };
131
132    if !shares.clone().all(|share| match share.payload {
133        SharePayload::V1(_) => version == SecretSharingVersion::V1,
134    }) {
135        return Err(Error::InconsistentVersion);
136    }
137
138    match version {
139        SecretSharingVersion::V1 => {
140            let shares = shares.map(|share| match &share.payload {
141                SharePayload::V1(s) => s,
142                //_ => unreachable!("This case should not happen because of previous check"),
143            });
144
145            ShareV1::join_shares(shares)
146        }
147        _ => Err(Error::UnknownVersion),
148    }
149}
150
151impl From<Share> for Vec<u8> {
152    /// Serialize the structure into a `Vec<u8>`, for storage, transmission or use in another language.
153    fn from(data: Share) -> Self {
154        let mut header: Self = data.header.borrow().into();
155        let mut payload: Self = data.payload.into();
156        header.append(&mut payload);
157        header
158    }
159}
160
161impl TryFrom<&[u8]> for Share {
162    type Error = Error;
163
164    /// Parses the data. Can return an Error of the data is invalid or unrecognized.
165    fn try_from(data: &[u8]) -> Result<Self> {
166        if data.len() < Header::len() {
167            return Err(Error::InvalidLength);
168        };
169
170        let header = Header::try_from(&data[0..Header::len()])?;
171
172        let payload = match header.version {
173            SecretSharingVersion::V1 => {
174                SharePayload::V1(ShareV1::try_from(&data[Header::len()..])?)
175            }
176            _ => return Err(Error::UnknownVersion),
177        };
178
179        Ok(Self { header, payload })
180    }
181}
182
183impl From<SharePayload> for Vec<u8> {
184    fn from(data: SharePayload) -> Self {
185        match data {
186            SharePayload::V1(x) => x.into(),
187        }
188    }
189}
190
191#[test]
192fn secret_sharing_test() {
193    let shares = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).unwrap();
194
195    assert_eq!(shares.len(), 5);
196
197    let key1 = join_shares(&shares[0..3]).unwrap();
198    let key2 = join_shares(&shares[2..5]).unwrap();
199    assert_eq!(key1.len(), 32);
200    assert_eq!(key2.len(), 32);
201    assert!(join_shares(&shares[2..4]).is_err());
202}