1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! Module for creating keys splitted between multiple parties.
//! Use this for "Break The Glass" scenarios or when you want to cryptographically enforce
//! approval of multiple users.
//!
//! This module is used to generate a key that is splitted in multiple `Share`
//! and that requires a specific amount of them to regenerate the key.
//! You can think of it as a "Break The Glass" scenario. You can
//! generate a key using this, lock your entire data by encrypting it
//! and then you will need, let's say, 3 out of the 5 administrators to decrypt
//! the data. That data could also be an API key or password of a super admin account.
//!
//! ```rust
//! use devolutions_crypto::secret_sharing::{generate_shared_key, join_shares, SecretSharingVersion, Share};
//!
//! // You want a key of 32 bytes, splitted between 5 people, and I want a
//! // minimum of 3 of these shares to regenerate the key.
//! let shares: Vec<Share> = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).expect("generation shouldn't fail with the right parameters");
//!
//! assert_eq!(shares.len(), 5);
//! let key = join_shares(&shares[2..5]).expect("joining shouldn't fail with the right shares");
//! ```

mod secret_sharing_v1;

use super::DataType;
use super::Error;
use super::Header;
use super::HeaderType;
use super::Result;
pub use super::SecretSharingVersion;
use super::ShareSubtype;

use secret_sharing_v1::ShareV1;

use std::convert::TryFrom;

#[cfg(feature = "fuzz")]
use arbitrary::Arbitrary;

/// A part of the secret key. You need multiple of them to recompute the secret key.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub struct Share {
    pub(crate) header: Header<Share>,
    payload: SharePayload,
}

impl HeaderType for Share {
    type Version = SecretSharingVersion;
    type Subtype = ShareSubtype;

    fn data_type() -> DataType {
        DataType::Share
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
enum SharePayload {
    V1(ShareV1),
}

/// Generate a key and split it in `n_shares`. You will need `threshold` shares to recover the key.
///
/// # Arguments
///
/// * `n_shares` - Number of shares to generate
/// * `threshold` - The number of shares needed to recover the key
/// * `length` - The desired length of the key to generate
///  * `version` - Version of the secret sharing scheme to use. Use `SecretSharingVersion::Latest` if you're not dealing with shared data.
///
/// # Returns
/// Returns an array of `Share`.
///
/// # Example
/// ```
/// use devolutions_crypto::secret_sharing::{ generate_shared_key, SecretSharingVersion };
/// let shares = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).unwrap();
/// ```
pub fn generate_shared_key(
    n_shares: u8,
    threshold: u8,
    length: usize,
    version: SecretSharingVersion,
) -> Result<Vec<Share>> {
    let mut header = Header::default();

    match version {
        SecretSharingVersion::V1 | SecretSharingVersion::Latest => {
            header.version = SecretSharingVersion::V1;

            let shares = ShareV1::generate_shared_key(n_shares, threshold, length)?;

            Ok(shares
                .map(|s| Share {
                    header: header.clone(),
                    payload: SharePayload::V1(s),
                })
                .collect())
        }
    }
}

/// Join multiple `Share` to regenerate a secret key.
///
/// # Arguments
///
/// * `shares` - The `Share`s to join
///
/// # Example
/// ```
/// use devolutions_crypto::secret_sharing::{generate_shared_key, join_shares, SecretSharingVersion};
/// let shares = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).unwrap();
///
/// assert_eq!(shares.len(), 5);
///
/// let key = join_shares(&shares[2..5]).unwrap();
/// ```
pub fn join_shares<'a, I, J>(shares: I) -> Result<Vec<u8>>
where
    I: IntoIterator<Item = &'a Share, IntoIter = J>,
    J: Iterator<Item = &'a Share> + Clone,
{
    let shares = shares.into_iter();

    let version = match shares.clone().peekable().peek() {
        Some(x) => x.header.version,
        None => return Err(Error::NotEnoughShares),
    };

    if !shares.clone().all(|share| match share.payload {
        SharePayload::V1(_) => version == SecretSharingVersion::V1,
    }) {
        return Err(Error::InconsistentVersion);
    }

    match version {
        SecretSharingVersion::V1 => {
            let shares = shares.map(|share| match &share.payload {
                SharePayload::V1(s) => s,
                //_ => unreachable!("This case should not happen because of previous check"),
            });

            ShareV1::join_shares(shares)
        }
        _ => Err(Error::UnknownVersion),
    }
}

impl From<Share> for Vec<u8> {
    /// Serialize the structure into a `Vec<u8>`, for storage, transmission or use in another language.
    fn from(data: Share) -> Self {
        let mut header: Self = data.header.into();
        let mut payload: Self = data.payload.into();
        header.append(&mut payload);
        header
    }
}

impl TryFrom<&[u8]> for Share {
    type Error = Error;

    /// Parses the data. Can return an Error of the data is invalid or unrecognized.
    fn try_from(data: &[u8]) -> Result<Self> {
        if data.len() < Header::len() {
            return Err(Error::InvalidLength);
        };

        let header = Header::try_from(&data[0..Header::len()])?;

        let payload = match header.version {
            SecretSharingVersion::V1 => {
                SharePayload::V1(ShareV1::try_from(&data[Header::len()..])?)
            }
            _ => return Err(Error::UnknownVersion),
        };

        Ok(Self { header, payload })
    }
}

impl From<SharePayload> for Vec<u8> {
    fn from(data: SharePayload) -> Self {
        match data {
            SharePayload::V1(x) => x.into(),
        }
    }
}

#[test]
fn secret_sharing_test() {
    let shares = generate_shared_key(5, 3, 32, SecretSharingVersion::Latest).unwrap();

    assert_eq!(shares.len(), 5);

    let key1 = join_shares(&shares[0..3]).unwrap();
    let key2 = join_shares(&shares[2..5]).unwrap();
    assert_eq!(key1.len(), 32);
    assert_eq!(key2.len(), 32);
    assert!(join_shares(&shares[2..4]).is_err());
}