p2panda_encryption/data_scheme/
group_secret.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Methods to create and maintain multiple secrets known by a group which are used to encrypt and decrypt data.
4use std::collections::HashMap;
5use std::collections::hash_map::{IntoIter, Iter, Keys, Values};
6use std::fmt;
7use std::hash::Hash as StdHash;
8use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};
9
10use p2panda_core::cbor::{DecodeError, EncodeError, decode_cbor, encode_cbor};
11use serde::de::{SeqAccess, Visitor};
12use serde::ser::SerializeSeq;
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16use crate::crypto::sha2::{SHA256_DIGEST_SIZE, sha2_256};
17use crate::crypto::{Rng, RngError, Secret};
18
19/// 256-bit secret group key.
20pub const GROUP_SECRET_SIZE: usize = 32;
21
22/// Public identifier for each secret. This is the SHA256 digest of the secret key itself.
23///
24/// Can be used to help the receiver of a ciphertext to understand which key they can use to
25/// decrypt the message.
26pub type GroupSecretId = [u8; SHA256_DIGEST_SIZE];
27
28/// UNIX timestamp indicating when the key was generated.
29///
30/// This helps peers to pick the "latest" key or remove keys based on their age (for forward secrecy)
31/// depending on the application. If other ordering strategies are applied by the application they
32/// can also be used instead to reason about the "latest" group secret.
33pub type Timestamp = u64;
34
35/// Secret known by a group which is used to encrypt and decrypt data.
36///
37/// Group secrets can be used multiple times and are dropped never or manually by the application,
38/// thus providing a weaker forward secrecy than p2panda's "message encryption" scheme.
39#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
40pub struct GroupSecret(Secret<GROUP_SECRET_SIZE>, Timestamp);
41
42impl GroupSecret {
43    #[cfg(any(test, feature = "test_utils"))]
44    pub fn new(bytes: [u8; GROUP_SECRET_SIZE], timestamp: Timestamp) -> Self {
45        Self(Secret::from_bytes(bytes), timestamp)
46    }
47
48    /// Create a new group secret with current timestamp from random-number generator.
49    pub(crate) fn from_rng(rng: &Rng) -> Result<Self, GroupSecretError> {
50        let bytes: [u8; GROUP_SECRET_SIZE] = rng.random_array()?;
51        Self::from_bytes(bytes)
52    }
53
54    /// Create a new group secret with current timestamp from random byte string.
55    pub(crate) fn from_bytes(bytes: [u8; GROUP_SECRET_SIZE]) -> Result<Self, GroupSecretError> {
56        let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
57        Ok(Self(Secret::from_bytes(bytes), now))
58    }
59
60    /// Deserialize group secret from CBOR representation.
61    pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, GroupSecretError> {
62        Ok(decode_cbor(bytes)?)
63    }
64
65    /// Returns identifier (SHA256 hash) for this secret.
66    pub fn id(&self) -> GroupSecretId {
67        sha2_256(&[self.0.as_bytes()])
68    }
69
70    /// Return creation date (UNIX timestamp in seconds) of this secret.
71    pub fn timestamp(&self) -> Timestamp {
72        self.1
73    }
74
75    /// Adjust the timestamp.
76    pub(crate) fn set_timestamp(&mut self, timestamp: Timestamp) {
77        self.1 = timestamp;
78    }
79
80    /// Returns secret key as bytes.
81    pub(crate) fn as_bytes(&self) -> &[u8; GROUP_SECRET_SIZE] {
82        self.0.as_bytes()
83    }
84
85    /// Serialize group secret into CBOR representation.
86    pub(crate) fn to_bytes(&self) -> Result<Vec<u8>, GroupSecretError> {
87        Ok(encode_cbor(self)?)
88    }
89}
90
91impl StdHash for GroupSecret {
92    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
93        self.id().hash(state);
94    }
95}
96
97/// Bundle of all secrets used by a group to encrypt and decrypt it's data.
98///
99/// Peers manage all secrets they generated or learned about in this bundle. New secrets are added
100/// to the bundle when a group got updated, a member was added or removed.
101///
102/// Secrets inside the bundle can be removed if the application considers them due, otherwise
103/// a bundle will grow in size and no forward secrecy is given.
104#[derive(Debug)]
105pub struct SecretBundle;
106
107#[derive(Debug, PartialEq, Eq)]
108#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
109pub struct SecretBundleState {
110    secrets: HashMap<GroupSecretId, GroupSecret>,
111    latest: Option<GroupSecretId>,
112}
113
114impl SecretBundleState {
115    /// Returns the latest known secret which should preferably used for encrypting new data.
116    pub fn latest(&self) -> Option<&GroupSecret> {
117        self.latest.as_ref().and_then(|id| self.secrets.get(id))
118    }
119
120    /// Returns a secret based on the id.
121    ///
122    /// This can be used to retrieve a secret to decrypt data where we know which secret id has been
123    /// used.
124    pub fn get(&self, id: &GroupSecretId) -> Option<&GroupSecret> {
125        self.secrets.get(id)
126    }
127
128    /// Returns true when the bundle contains a secret with the given id.
129    pub fn contains(&self, id: &GroupSecretId) -> bool {
130        self.secrets.contains_key(id)
131    }
132
133    /// Returns number of all secrets in this bundle.
134    pub fn len(&self) -> usize {
135        self.secrets.len()
136    }
137
138    /// Returns true if there's no secrets in this bundle.
139    pub fn is_empty(&self) -> bool {
140        self.secrets.is_empty()
141    }
142
143    /// Iterator over secrets (values) and their ids (keys).
144    pub fn iter(&self) -> Iter<'_, GroupSecretId, GroupSecret> {
145        self.secrets.iter()
146    }
147
148    /// Iterator over all ids.
149    pub fn ids(&self) -> Keys<'_, GroupSecretId, GroupSecret> {
150        self.secrets.keys()
151    }
152
153    /// Iterator over all secrets.
154    pub fn secrets(&self) -> Values<'_, GroupSecretId, GroupSecret> {
155        self.secrets.values()
156    }
157
158    /// Encodes bundle in CBOR format.
159    pub(crate) fn to_bytes(&self) -> Result<Vec<u8>, GroupSecretError> {
160        Ok(encode_cbor(self)?)
161    }
162}
163
164impl std::iter::IntoIterator for SecretBundleState {
165    type Item = (GroupSecretId, GroupSecret);
166
167    type IntoIter = IntoIter<GroupSecretId, GroupSecret>;
168
169    fn into_iter(self) -> Self::IntoIter {
170        self.secrets.into_iter()
171    }
172}
173
174impl Serialize for SecretBundleState {
175    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
176    where
177        S: serde::Serializer,
178    {
179        let mut s = serializer.serialize_seq(Some(self.secrets.len()))?;
180        for secret in self.secrets.values() {
181            s.serialize_element(secret)?;
182        }
183        s.end()
184    }
185}
186
187impl<'de> Deserialize<'de> for SecretBundleState {
188    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189    where
190        D: serde::Deserializer<'de>,
191    {
192        struct SecretListVisitor;
193
194        impl<'de> Visitor<'de> for SecretListVisitor {
195            type Value = Vec<GroupSecret>;
196
197            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
198                formatter.write_str("list of group secrets")
199            }
200
201            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
202            where
203                A: SeqAccess<'de>,
204            {
205                let mut result = Vec::new();
206                while let Some(secret) = seq.next_element()? {
207                    result.push(secret);
208                }
209                Ok(result)
210            }
211        }
212
213        let secrets = deserializer.deserialize_seq(SecretListVisitor)?;
214
215        Ok(SecretBundle::from_secrets(secrets))
216    }
217}
218
219impl SecretBundle {
220    /// Initialises empty secret bundle state.
221    pub fn init() -> SecretBundleState {
222        SecretBundleState {
223            secrets: HashMap::new(),
224            latest: None,
225        }
226    }
227
228    /// Initialises secret bundle state from a list of group secrets.
229    pub fn from_secrets(secrets: Vec<GroupSecret>) -> SecretBundleState {
230        let secrets = HashMap::from_iter(secrets.into_iter().map(|secret| (secret.id(), secret)));
231        SecretBundleState {
232            latest: find_latest(&secrets),
233            secrets,
234        }
235    }
236
237    /// Initialises secret bundle state from an encoded CBOR representation.
238    pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<SecretBundleState, GroupSecretError> {
239        Ok(decode_cbor(bytes)?)
240    }
241
242    /// Generates and returns a new secret.
243    ///
244    /// To prevent issues with invalid timestamps (too far in the future) of previous secrets, we
245    /// force our newly generated secret to be the "latest" when needed.
246    ///
247    /// The secret is not yet inserted into the bundle, this needs to be done manually.
248    pub fn generate(y: &SecretBundleState, rng: &Rng) -> Result<GroupSecret, GroupSecretError> {
249        let mut secret = GroupSecret::from_rng(rng)?;
250
251        let latest_timestamp = y.latest().map(|latest| latest.timestamp()).unwrap_or(0);
252        if secret.timestamp() <= latest_timestamp {
253            secret.set_timestamp(latest_timestamp + 1);
254        }
255
256        Ok(secret)
257    }
258
259    /// Inserts secret into bundle, ignoring duplicates.
260    pub fn insert(mut y: SecretBundleState, secret: GroupSecret) -> SecretBundleState {
261        y.secrets.insert(secret.id(), secret);
262        y.latest = find_latest(&y.secrets);
263        y
264    }
265
266    /// Removes secret from bundle.
267    pub fn remove(
268        mut y: SecretBundleState,
269        id: &GroupSecretId,
270    ) -> (SecretBundleState, Option<GroupSecret>) {
271        let result = y.secrets.remove(id);
272        y.latest = find_latest(&y.secrets);
273        (y, result)
274    }
275
276    /// Merges one bundle with another, ignoring duplicates.
277    pub fn extend(mut y: SecretBundleState, other: SecretBundleState) -> SecretBundleState {
278        y.secrets.extend(other.secrets);
279        y.latest = find_latest(&y.secrets);
280        y
281    }
282}
283
284/// Finds the "latest" secret to use from a list by comparing timestamps. If the timestamps of two
285/// distinct secrets match the id is used as a tie-breaker.
286fn find_latest(secrets: &HashMap<GroupSecretId, GroupSecret>) -> Option<GroupSecretId> {
287    let mut latest_timestamp: Timestamp = 0;
288    let mut latest_secret_id: Option<GroupSecretId> = None;
289    for (id, secret) in secrets {
290        let timestamp = secret.timestamp();
291        if latest_timestamp < timestamp
292            || (latest_timestamp == timestamp
293                && *id > latest_secret_id.unwrap_or([0; SHA256_DIGEST_SIZE]))
294        {
295            latest_timestamp = timestamp;
296            latest_secret_id = Some(id.to_owned());
297        }
298    }
299    latest_secret_id
300}
301
302#[derive(Debug, Error)]
303pub enum GroupSecretError {
304    #[error("the given key does not match the required 32 byte length")]
305    InvalidKeySize,
306
307    #[error(transparent)]
308    Rng(#[from] RngError),
309
310    #[error(transparent)]
311    Encode(#[from] EncodeError),
312
313    #[error(transparent)]
314    Decode(#[from] DecodeError),
315
316    #[error(transparent)]
317    SystemTime(#[from] SystemTimeError),
318}
319
320#[cfg(test)]
321mod tests {
322    use crate::Rng;
323
324    use super::{GroupSecret, SecretBundle};
325
326    #[test]
327    fn group_secret_bundle() {
328        let rng = Rng::from_seed([1; 32]);
329
330        let secret = GroupSecret::from_rng(&rng).unwrap();
331
332        let bundle_1 = SecretBundle::from_secrets(vec![secret.clone()]);
333        let bundle_2 = SecretBundle::from_secrets(vec![secret.clone()]);
334        assert_eq!(bundle_1.len(), 1);
335        assert_eq!(bundle_2.len(), 1);
336
337        assert_eq!(bundle_1.get(&secret.id()), bundle_2.get(&secret.id()));
338        assert!(bundle_1.get(&secret.id()).is_some());
339        assert!(bundle_1.contains(&secret.id()));
340
341        let unknown_secret = GroupSecret::from_rng(&rng).unwrap();
342        assert!(bundle_1.get(&unknown_secret.id()).is_none());
343        assert!(!bundle_1.contains(&unknown_secret.id()));
344
345        let secret_2 = GroupSecret::from_rng(&rng).unwrap();
346        let bundle_2 = SecretBundle::insert(bundle_2, secret_2.clone());
347        assert_eq!(bundle_2.len(), 2);
348
349        let bundle_1 = SecretBundle::extend(bundle_1, bundle_2);
350        assert_eq!(bundle_1.len(), 2);
351
352        let (bundle_1, result) = SecretBundle::remove(bundle_1, &secret_2.id());
353        assert_eq!(result, Some(secret_2));
354        assert_eq!(bundle_1.len(), 1);
355    }
356
357    #[test]
358    fn latest_secret() {
359        let bundle = SecretBundle::init();
360        assert!(bundle.latest().is_none());
361
362        let secret_1 = GroupSecret::new([1; 32], 234);
363        assert_eq!(secret_1.timestamp(), 234);
364        let secret_2 = GroupSecret::new([2; 32], 234); // same timestamp
365        assert_eq!(secret_2.timestamp(), 234);
366        let secret_3 = GroupSecret::new([3; 32], 345);
367        assert_eq!(secret_3.timestamp(), 345);
368        let secret_4 = GroupSecret::new([4; 32], 123);
369        assert_eq!(secret_4.timestamp(), 123);
370
371        // Inserted secret 1 is the latest.
372        let bundle = SecretBundle::insert(bundle, secret_1.clone());
373        assert_eq!(bundle.len(), 1);
374        assert_eq!(bundle.latest(), Some(&secret_1));
375
376        // Inserted secret 2 is the "latest" as the higher hash wins when both timestamps are the
377        // same.
378        let bundle = SecretBundle::insert(bundle, secret_2.clone());
379        assert_eq!(bundle.len(), 2);
380        assert_eq!(bundle.latest(), Some(&secret_2));
381
382        // Use a separate group to confirm that the order of insertion does not matter here.
383        {
384            let bundle_2 = SecretBundle::init();
385            let bundle_2 = SecretBundle::insert(bundle_2, secret_2.clone());
386            let bundle_2 = SecretBundle::insert(bundle_2, secret_1.clone());
387            assert_eq!(bundle_2.latest(), Some(&secret_2));
388        }
389
390        // Inserted 3 is the latest.
391        let bundle = SecretBundle::insert(bundle, secret_3.clone());
392        assert_eq!(bundle.len(), 3);
393        assert_eq!(bundle.latest(), Some(&secret_3));
394
395        // Inserted 3 is still the latest.
396        let bundle = SecretBundle::insert(bundle, secret_4.clone());
397        assert_eq!(bundle.len(), 4);
398        assert_eq!(bundle.latest(), Some(&secret_3));
399    }
400
401    #[test]
402    fn serde() {
403        let rng = Rng::from_seed([1; 32]);
404
405        // Serialize & deserialize bundle.
406        let bundle = SecretBundle::from_secrets(vec![
407            GroupSecret::from_rng(&rng).unwrap(),
408            GroupSecret::from_rng(&rng).unwrap(),
409            GroupSecret::from_rng(&rng).unwrap(),
410            GroupSecret::from_rng(&rng).unwrap(),
411        ]);
412
413        let bytes = bundle.to_bytes().unwrap();
414        assert_eq!(bundle, SecretBundle::try_from_bytes(&bytes).unwrap());
415
416        // Serialize & deserialize single secret.
417        let secret = GroupSecret::from_rng(&rng).unwrap();
418        let timestamp = secret.timestamp();
419
420        let bytes = secret.to_bytes().unwrap();
421        let secret_again = GroupSecret::try_from_bytes(&bytes).unwrap();
422        assert_eq!(secret, secret_again);
423        assert_eq!(timestamp, secret_again.timestamp());
424    }
425
426    #[test]
427    fn generated_always_latest() {
428        let rng = Rng::from_seed([1; 32]);
429
430        let bundle = SecretBundle::init();
431
432        let secret_0 = SecretBundle::generate(&bundle, &rng).unwrap();
433        let bundle = SecretBundle::insert(bundle, secret_0.clone());
434        assert_eq!(bundle.latest(), Some(&secret_0));
435
436        let secret_1 = SecretBundle::generate(&bundle, &rng).unwrap();
437        let bundle = SecretBundle::insert(bundle, secret_1.clone());
438        assert_eq!(bundle.latest(), Some(&secret_1));
439
440        let secret_2 = SecretBundle::generate(&bundle, &rng).unwrap();
441        let bundle = SecretBundle::insert(bundle, secret_2.clone());
442        assert_eq!(bundle.latest(), Some(&secret_2));
443    }
444}