diff --git a/beelay/beelay-wasm/src/membered.rs b/beelay/beelay-wasm/src/membered.rs deleted file mode 100644 index 293239c8..00000000 --- a/beelay/beelay-wasm/src/membered.rs +++ /dev/null @@ -1,6 +0,0 @@ -use beelay_core::{DocumentId, PeerId}; - -pub(crate) enum Membered { - Group(PeerId), - Document(DocumentId), -} diff --git a/keyhive_core/src/ability.rs b/keyhive_core/src/ability.rs index 7f2c9433..7f3b0b53 100644 --- a/keyhive_core/src/ability.rs +++ b/keyhive_core/src/ability.rs @@ -6,6 +6,7 @@ use crate::{ crypto::signer::async_signer::AsyncSigner, listener::{membership::MembershipListener, no_listener::NoListener}, principal::document::Document, + store::secret_key::traits::ShareSecretStore, }; use derive_where::derive_where; use std::{cell::RefCell, rc::Rc}; @@ -15,16 +16,19 @@ use std::{cell::RefCell, rc::Rc}; pub struct Ability< 'a, S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub(crate) doc: &'a Rc>>, + pub(crate) doc: &'a Rc>>, pub(crate) can: Access, } -impl> Ability<'_, S, T, L> { +impl> + Ability<'_, S, K, T, L> +{ /// Getter for the referenced [`Document`]. - pub fn doc(&self) -> &Rc>> { + pub fn doc(&self) -> &Rc>> { self.doc } diff --git a/keyhive_core/src/archive.rs b/keyhive_core/src/archive.rs index 10ee24d9..94739ef2 100644 --- a/keyhive_core/src/archive.rs +++ b/keyhive_core/src/archive.rs @@ -4,7 +4,6 @@ use crate::{ content::reference::ContentRef, crypto::digest::Digest, principal::{ - active::archive::ActiveArchive, document::{archive::DocumentArchive, id::DocumentId}, group::{id::GroupId, membership_operation::StaticMembershipOperation, GroupArchive}, individual::{id::IndividualId, Individual}, @@ -16,7 +15,7 @@ use std::collections::HashMap; /// Serialized representation of [`Keyhive`][crate::keyhive::Keyhive]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Archive { - pub(crate) active: ActiveArchive, + pub(crate) active: Individual, pub(crate) topsorted_ops: Vec<( Digest>, StaticMembershipOperation, @@ -29,6 +28,6 @@ pub struct Archive { impl Archive { /// Getter for the user that the [`Archive`] is for. pub fn id(&self) -> IndividualId { - self.active.individual.id() + self.active.id() } } diff --git a/keyhive_core/src/cgka.rs b/keyhive_core/src/cgka.rs index 9cf2e82e..6ab7fb61 100644 --- a/keyhive_core/src/cgka.rs +++ b/keyhive_core/src/cgka.rs @@ -1,3 +1,4 @@ +pub mod archive; pub mod beekem; pub mod error; pub mod keys; @@ -14,30 +15,32 @@ use crate::{ application_secret::{ApplicationSecret, PcsKey}, digest::Digest, encrypted::EncryptedContent, - share_key::{ShareKey, ShareSecretKey}, - signed::Signed, + share_key::{AsyncSecretKey, ShareKey}, + signed::{Signed, SigningError}, signer::async_signer::AsyncSigner, siv::Siv, symmetric_key::SymmetricKey, }, principal::{document::id::DocumentId, individual::id::IndividualId}, + store::secret_key::traits::ShareSecretStore, transact::{fork::Fork, merge::Merge}, - util::content_addressed_map::CaMap, }; -use beekem::BeeKem; +use archive::CgkaArchive; +use beekem::{BeeKem, DecryptTreeSecretError}; use derivative::Derivative; use dupe::Dupe; use error::CgkaError; -use keys::ShareKeyMap; use nonempty::NonEmpty; use operation::{CgkaEpoch, CgkaOperation, CgkaOperationGraph}; -use serde::{Deserialize, Serialize}; +use secret_store::DecryptSecretError; use std::{ borrow::Borrow, collections::{BTreeSet, HashMap, HashSet}, + fmt::{Debug, Display}, hash::{Hash, Hasher}, rc::Rc, }; +use thiserror::Error; use tracing::{info, instrument}; /// Exposes CGKA (Continuous Group Key Agreement) operations like deriving @@ -50,53 +53,61 @@ use tracing::{info, instrument}; /// /// We assume that all operations are received in causal order (a property /// guaranteed by Keyhive as a whole). -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Derivative)] -#[derivative(Hash)] -pub struct Cgka { +#[derive(Clone, Eq, Derivative)] +#[derivative(Hash, PartialEq)] +pub struct Cgka { doc_id: DocumentId, /// The id of the member who owns this tree. pub owner_id: IndividualId, - /// The secret keys of the member who owns this tree. - pub owner_sks: ShareKeyMap, tree: BeeKem, /// Graph of all operations seen (but not necessarily applied) so far. ops_graph: CgkaOperationGraph, /// Whether there are ops in the graph that have not been applied to the ///tree due to a structural change. pending_ops_for_structural_change: bool, - // TODO: Enable policies to evict older entries. - #[derivative(Hash(hash_with = "hash_pcs_keys"))] - pcs_keys: CaMap, + + #[derivative( + PartialEq(compare_with = "crate::util::partial_eq::hash_map_key_partial_eq"), + Hash(hash_with = "hash_pcs_keys") + )] + pcs_keys: HashMap>, /// The update operations for each PCS key. #[derivative(Hash(hash_with = "hashed_key_bytes"))] - pcs_key_ops: HashMap, Digest>>, + pcs_key_ops: HashMap>>, original_member: (IndividualId, ShareKey), init_add_op: Signed, + + #[derivative(PartialEq = "ignore", Hash = "ignore")] + pub(crate) store: K, } -fn hash_pcs_keys(pcs_keys: &CaMap, state: &mut H) { - pcs_keys.keys().collect::>().hash(state) +fn hash_pcs_keys(pcs_keys: &HashMap, state: &mut H) { + pcs_keys.keys().collect::>().hash(state) } -fn hashed_key_bytes(hmap: &HashMap, V>, state: &mut H) { +fn hashed_key_bytes( + hmap: &HashMap>>, + state: &mut H, +) { hmap.keys() - .map(|k| k.as_slice()) + .map(|k| k.as_bytes()) .collect::>() .hash(state) } -impl Cgka { - pub async fn new( +impl Cgka { + pub async fn new( doc_id: DocumentId, owner_id: IndividualId, owner_pk: ShareKey, - signer: &S, + store: K, + signer: &A, ) -> Result { let init_add_op = CgkaOperation::init_add(doc_id, owner_id, owner_pk); let signed_op = signer.try_sign_async(init_add_op).await?; - Self::new_from_init_add(doc_id, owner_id, owner_pk, signed_op) + Self::new_from_init_add(doc_id, owner_id, owner_pk, signed_op, store) } #[instrument(skip_all, fields(doc_id))] @@ -105,33 +116,29 @@ impl Cgka { owner_id: IndividualId, owner_pk: ShareKey, init_add_op: Signed, + store: K, ) -> Result { let tree = BeeKem::new(doc_id, owner_id, owner_pk)?; let mut cgka = Self { doc_id, owner_id, - owner_sks: ShareKeyMap::new(), tree, ops_graph: CgkaOperationGraph::new(), pending_ops_for_structural_change: false, - pcs_keys: CaMap::new(), + pcs_keys: HashMap::new(), pcs_key_ops: HashMap::new(), original_member: (owner_id, owner_pk), init_add_op: init_add_op.clone(), + store, }; cgka.ops_graph.add_local_op(&init_add_op); Ok(cgka) } #[instrument(skip_all, fields(doc_id, my_id))] - pub fn with_new_owner( - &self, - my_id: IndividualId, - owner_sks: ShareKeyMap, - ) -> Result { + pub fn with_new_owner(&self, my_id: IndividualId) -> Result { let mut cgka = self.clone(); cgka.owner_id = my_id; - cgka.owner_sks = owner_sks; cgka.pcs_keys = self.pcs_keys.clone(); cgka.pcs_key_ops = self.pcs_key_ops.clone(); Ok(cgka) @@ -148,7 +155,7 @@ impl Cgka { /// perform a leaf key rotation. #[instrument(skip_all, fields(content_ref))] pub async fn new_app_secret_for< - S: AsyncSigner, + A: AsyncSigner, T: ContentRef, R: rand::CryptoRng + rand::RngCore, >( @@ -156,34 +163,46 @@ impl Cgka { content_ref: &T, content: &[u8], pred_refs: &Vec, - signer: &S, + signer: &A, csprng: &mut R, - ) -> Result<(ApplicationSecret, Option>), CgkaError> { + ) -> Result<(ApplicationSecret, Option>), NewAppSecretError> { let mut op = None; let current_pcs_key = if !self.has_pcs_key() { - let new_share_secret_key = ShareSecretKey::generate(csprng); - let new_share_key = new_share_secret_key.share_key(); - let (pcs_key, update_op) = self - .update(new_share_key, new_share_secret_key, signer, csprng) - .await?; - self.insert_pcs_key(&pcs_key, Digest::hash(&update_op)); + let new_share_secret_key = self + .store + .generate_share_secret_key() + .await + .map_err(NewAppSecretError::GenerateSecretError)?; + let (pcs_key, update_op) = self.update(&new_share_secret_key, signer, csprng).await?; + self.insert_pcs_key(pcs_key.clone(), Digest::hash(&update_op)); op = Some(update_op); pcs_key } else { - self.pcs_key_from_tree_root()? + self.pcs_key_from_tree_root().await? }; - let pcs_key_hash = Digest::hash(¤t_pcs_key); - let nonce = Siv::new(¤t_pcs_key.into(), content, self.doc_id) - .map_err(|_e| CgkaError::Conversion)?; + let pcs_pk = current_pcs_key.0.to_share_key(); + let nonce = Siv::new( + ¤t_pcs_key + .to_symmetric_key(self.doc_id) + .await + .map_err(NewAppSecretError::EcdhError)?, + content, + self.doc_id, + ) + .map_err(|_e| CgkaError::Conversion)?; Ok(( - current_pcs_key.derive_application_secret( - &nonce, - content_ref, - &Digest::hash(pred_refs), - self.pcs_key_ops - .get(&pcs_key_hash) - .expect("PcsKey hash should be present becuase we derived it above"), - ), + current_pcs_key + .derive_application_secret( + self.doc_id, + &nonce, + content_ref, + &Digest::hash(pred_refs), + self.pcs_key_ops + .get(&pcs_pk) + .expect("PcsKey hash should be present becuase we derived it above"), + ) + .await + .map_err(NewAppSecretError::EcdhError)?, op, )) } @@ -193,21 +212,26 @@ impl Cgka { /// We must first derive a [`PcsKey`] for the encrypted data's associated /// hashes. Then we use that [`PcsKey`] to derive an [`ApplicationSecret`]. #[instrument(skip_all, fields(encrypted.content_ref))] - pub fn decryption_key_for( + pub async fn decryption_key_for( &mut self, encrypted: &EncryptedContent, - ) -> Result { - let pcs_key = - self.pcs_key_from_hashes(&encrypted.pcs_key_hash, &encrypted.pcs_update_op_hash)?; - if !self.pcs_keys.contains_key(&encrypted.pcs_key_hash) { - self.insert_pcs_key(&pcs_key, encrypted.pcs_update_op_hash); + ) -> Result> { + let pcs_key = self + .pcs_key_from_hashes(encrypted.pcs_pubkey, &encrypted.pcs_update_op_hash) + .await?; + if !self.pcs_keys.contains_key(&encrypted.pcs_pubkey) { + self.insert_pcs_key(pcs_key.clone(), encrypted.pcs_update_op_hash); } - let app_secret = pcs_key.derive_application_secret( - &encrypted.nonce, - &encrypted.content_ref, - &encrypted.pred_refs, - &encrypted.pcs_update_op_hash, - ); + let app_secret = pcs_key + .derive_application_secret( + self.doc_id, + &encrypted.nonce, + &encrypted.content_ref, + &encrypted.pred_refs, + &encrypted.pcs_update_op_hash, + ) + .await + .map_err(PcsKeyFromHashesError::EcdhError)?; Ok(app_secret.key()) } @@ -219,17 +243,17 @@ impl Cgka { /// Add member to group. #[instrument(skip_all, fields(id, pk))] - pub async fn add( + pub async fn add( &mut self, id: IndividualId, pk: ShareKey, - signer: &S, - ) -> Result>, CgkaError> { + signer: &A, + ) -> Result>, DecryptTreeSecretError> { if self.tree.contains_id(&id) { return Ok(None); } if self.should_replay() { - self.replay_ops_graph()?; + self.replay_ops_graph().await?; } let leaf_index = self.tree.push_leaf(id, pk.into()); let predecessors = Vec::from_iter(self.ops_graph.cgka_op_heads.iter().cloned()); @@ -243,39 +267,45 @@ impl Cgka { doc_id: self.doc_id, }; - let signed_op = signer.try_sign_async(op).await?; + let signed_op = signer + .try_sign_async(op) + .await + .map_err(DecryptSecretError::SigningError)?; + self.ops_graph.add_local_op(&signed_op); Ok(Some(signed_op)) } /// Add multiple members to group. - pub async fn add_multiple( + pub async fn add_multiple( &mut self, members: NonEmpty<(IndividualId, ShareKey)>, - signer: &S, - ) -> Result>, CgkaError> { + signer: &A, + ) -> Result>, DecryptTreeSecretError> { let mut ops = Vec::new(); for m in members { - ops.push(self.add(m.0, m.1, signer).await?); + if let Some(x) = self.add(m.0, m.1, signer).await? { + ops.push(x); + } } - Ok(ops.into_iter().flatten().collect()) + Ok(ops) } /// Remove member from group. #[instrument(skip_all, fields(doc_id))] - pub async fn remove( + pub async fn remove( &mut self, id: IndividualId, - signer: &S, - ) -> Result>, CgkaError> { + signer: &A, + ) -> Result>, RemoveError> { if !self.tree.contains_id(&id) { return Ok(None); } if self.should_replay() { - self.replay_ops_graph()?; + self.replay_ops_graph().await?; } if self.group_size() == 1 { - return Err(CgkaError::RemoveLastMember); + return Err(CgkaError::RemoveLastMember)?; } let (leaf_idx, removed_keys) = self.tree.remove_id(id)?; let predecessors = Vec::from_iter(self.ops_graph.cgka_op_heads.iter().cloned()); @@ -294,20 +324,27 @@ impl Cgka { /// Update leaf key pair for this Identifier. /// This also triggers a tree path update for that leaf. #[instrument(skip_all, fields(doc_id, new_pk))] - pub async fn update( + pub async fn update( &mut self, - new_pk: ShareKey, - new_sk: ShareSecretKey, - signer: &S, + new_sk: &K::SecretKey, // Proof that there's a storeed SK + signer: &A, csprng: &mut R, - ) -> Result<(PcsKey, Signed), CgkaError> { + ) -> Result<(PcsKey, Signed), UpdateLeafError> { if self.should_replay() { - self.replay_ops_graph()?; + self.replay_ops_graph() + .await + .map_err(|e| UpdateLeafError::DecryptTreeSecretError(Box::new(e)))?; } - self.owner_sks.insert(new_pk, new_sk); - let maybe_key_and_path = - self.tree - .encrypt_path(self.owner_id, new_pk, &mut self.owner_sks, csprng)?; + let maybe_key_and_path = self + .tree + .encrypt_path( + self.owner_id, + new_sk.to_share_key(), + &mut self.store, + csprng, + ) + .await + .map_err(|e| UpdateLeafError::DecryptTreeSecretError(Box::new(e)))?; if let Some((pcs_key, new_path)) = maybe_key_and_path { let predecessors = Vec::from_iter(self.ops_graph.cgka_op_heads.iter().cloned()); let op = CgkaOperation::Update { @@ -319,10 +356,10 @@ impl Cgka { let signed_op = signer.try_sign_async(op).await?; self.ops_graph.add_local_op(&signed_op); - self.insert_pcs_key(&pcs_key, Digest::hash(&signed_op)); + self.insert_pcs_key(pcs_key.clone(), Digest::hash(&signed_op)); Ok((pcs_key, signed_op)) } else { - Err(CgkaError::IdentifierNotFound) + Err(CgkaError::IdentifierNotFound)? } } @@ -338,16 +375,16 @@ impl Cgka { /// membership changes and we receive a concurrent update, we can apply it /// immediately. #[instrument(skip_all, fields(doc_id, op))] - pub fn merge_concurrent_operation( + pub async fn merge_concurrent_operation( &mut self, op: Rc>, - ) -> Result<(), CgkaError> { + ) -> Result<(), DecryptTreeSecretError> { if self.ops_graph.contains_op_hash(&Digest::hash(op.borrow())) { return Ok(()); } let predecessors = op.payload.predecessors(); if !self.ops_graph.contains_predecessors(&predecessors) { - return Err(CgkaError::OutOfOrderOperation); + Err(CgkaError::OutOfOrderOperation)?; } let is_concurrent = !self.ops_graph.heads_contained_in(&predecessors); if is_concurrent { @@ -364,7 +401,7 @@ impl Cgka { } } else { if self.should_replay() { - self.replay_ops_graph()?; + self.replay_ops_graph().await?; } self.apply_operation(op)?; } @@ -448,10 +485,13 @@ impl Cgka { } /// Decrypt tree secret to derive [`PcsKey`]. - fn pcs_key_from_tree_root(&mut self) -> Result { + async fn pcs_key_from_tree_root( + &mut self, + ) -> Result, DecryptTreeSecretError> { let key = self .tree - .decrypt_tree_secret(self.owner_id, &mut self.owner_sks)?; + .decrypt_tree_secret(self.owner_id, &mut self.store) + .await?; Ok(PcsKey::new(key)) } @@ -460,37 +500,42 @@ impl Cgka { /// If we have not seen this [`PcsKey`] before, we'll need to rebuild /// the tree state for its corresponding update operation. #[instrument(skip_all, fields(doc_id, pcs_key_hash, update_op_hash))] - fn pcs_key_from_hashes( + async fn pcs_key_from_hashes( &mut self, - pcs_key_hash: &Digest, + pcs_pubkey: ShareKey, update_op_hash: &Digest>, - ) -> Result { - if let Some(pcs_key) = self.pcs_keys.get(pcs_key_hash) { - Ok(*pcs_key.clone()) + ) -> Result, PcsKeyFromHashesError> { + if let Some(pcs_key) = self + .store + .get_secret_key(&pcs_pubkey) + .await + .map_err(PcsKeyFromHashesError::GetSecretError)? + { + Ok(PcsKey(pcs_key)) } else { if self.has_pcs_key() { - let pcs_key = self.pcs_key_from_tree_root()?; - if &Digest::hash(&pcs_key) == pcs_key_hash { + let pcs_key = self.pcs_key_from_tree_root().await?; + if pcs_key.0.to_share_key() == pcs_pubkey { return Ok(pcs_key); } } - self.derive_pcs_key_for_op(update_op_hash) + Ok(self.derive_pcs_key_for_op(update_op_hash).await?) } } /// Derive [`PcsKey`] for this operation hash. #[instrument(skip_all, fields(doc_id, op_hash))] - fn derive_pcs_key_for_op( + async fn derive_pcs_key_for_op( &mut self, op_hash: &Digest>, - ) -> Result { + ) -> Result, DecryptTreeSecretError> { if !self.ops_graph.contains_op_hash(op_hash) { - return Err(CgkaError::UnknownPcsKey); + return Err(CgkaError::UnknownPcsKey)?; } let mut heads = HashSet::new(); heads.insert(*op_hash); let ops = self.ops_graph.topsort_for_heads(&heads)?; - self.rebuild_pcs_key(ops) + self.rebuild_pcs_key(ops).await } /// Whether we have unresolved concurrency that requires a replay to resolve. @@ -501,28 +546,32 @@ impl Cgka { /// Replay all ops in our graph in a deterministic order. #[instrument(skip_all, fields(doc_id))] - fn replay_ops_graph(&mut self) -> Result<(), CgkaError> { + async fn replay_ops_graph(&mut self) -> Result<(), DecryptTreeSecretError> { let ordered_ops = self.ops_graph.topsort_graph()?; - let rebuilt_cgka = self.rebuild_cgka(ordered_ops)?; - self.update_cgka_from(&rebuilt_cgka); + let rebuilt_cgka = self.rebuild_cgka(ordered_ops).await?; + self.update_cgka_from(&rebuilt_cgka).await; self.pending_ops_for_structural_change = false; Ok(()) } /// Build a new [`Cgka`] for the provided non-empty list of [`CgkaEpoch`]s. #[instrument(skip_all, fields(doc_id, epochs))] - fn rebuild_cgka(&mut self, epochs: NonEmpty) -> Result { + async fn rebuild_cgka( + &mut self, + epochs: NonEmpty, + ) -> Result> { let mut rebuilt_cgka = Cgka::new_from_init_add( self.doc_id, self.original_member.0, self.original_member.1, self.init_add_op.clone(), + self.store.clone(), )? - .with_new_owner(self.owner_id, self.owner_sks.clone())?; + .with_new_owner(self.owner_id)?; rebuilt_cgka.apply_epochs(&epochs)?; if rebuilt_cgka.has_pcs_key() { - let pcs_key = rebuilt_cgka.pcs_key_from_tree_root()?; - rebuilt_cgka.insert_pcs_key(&pcs_key, Digest::hash(&epochs.last()[0])); + let pcs_key = rebuilt_cgka.pcs_key_from_tree_root().await?; + rebuilt_cgka.insert_pcs_key(pcs_key, Digest::hash(&epochs.last()[0])); } Ok(rebuilt_cgka) } @@ -530,37 +579,44 @@ impl Cgka { /// Derive a [`PcsKey`] by rebuilding a [`Cgka`] from the provided non-empty /// list of [`CgkaEpoch`]s. #[instrument(skip_all, fields(doc_id, epochs))] - fn rebuild_pcs_key(&mut self, epochs: NonEmpty) -> Result { + async fn rebuild_pcs_key( + &mut self, + epochs: NonEmpty, + ) -> Result, DecryptTreeSecretError> { debug_assert!(matches!( epochs.last()[0].payload, CgkaOperation::Update { .. } )); - let mut rebuilt_cgka = Cgka::new_from_init_add( + let mut rebuilt_cgka = Cgka::::new_from_init_add( self.doc_id, self.original_member.0, self.original_member.1, self.init_add_op.clone(), + self.store.clone(), )? - .with_new_owner(self.owner_id, self.owner_sks.clone())?; + .with_new_owner(self.owner_id)?; rebuilt_cgka.apply_epochs(&epochs)?; - let pcs_key = rebuilt_cgka.pcs_key_from_tree_root()?; - self.insert_pcs_key(&pcs_key, Digest::hash(&epochs.last()[0])); + let pcs_key = rebuilt_cgka.pcs_key_from_tree_root().await?; + self.insert_pcs_key(pcs_key.clone(), Digest::hash(&epochs.last()[0])); Ok(pcs_key) } #[instrument(skip_all, fields(doc_id, op_hash))] - fn insert_pcs_key(&mut self, pcs_key: &PcsKey, op_hash: Digest>) { - let digest = Digest::hash(pcs_key); - info!("{:?}", digest); - self.pcs_key_ops.insert(digest, op_hash); - self.pcs_keys.insert((*pcs_key).into()); + fn insert_pcs_key( + &mut self, + pcs_key: PcsKey, + op_hash: Digest>, + ) { + let pk = pcs_key.0.to_share_key(); + info!("{:?}", pk); + self.pcs_key_ops.insert(pk, op_hash); + self.pcs_keys.insert(pk, pcs_key); } /// Extend our state with that of the provided [`Cgka`]. #[instrument(skip_all, fields(doc_id))] - fn update_cgka_from(&mut self, other: &Self) { + async fn update_cgka_from(&mut self, other: &Self) { self.tree = other.tree.clone(); - self.owner_sks.extend(&other.owner_sks); self.pcs_keys.extend( other .pcs_keys @@ -570,9 +626,164 @@ impl Cgka { self.pcs_key_ops.extend(other.pcs_key_ops.iter()); self.pending_ops_for_structural_change = other.pending_ops_for_structural_change; } + + #[instrument(skip_all, fields(doc_id))] + pub fn into_archive(&self) -> CgkaArchive { + CgkaArchive { + doc_id: self.doc_id, + owner_id: self.owner_id, + tree: self.tree.clone(), + ops_graph: self.ops_graph.clone(), + pending_ops_for_structural_change: self.pending_ops_for_structural_change, + pcs_keys: self.pcs_keys.keys().cloned().collect(), + original_member: self.original_member.clone(), + init_add_op: self.init_add_op.clone(), + pcs_key_ops: self.pcs_key_ops.iter().map(|(k, v)| (*k, *v)).collect(), + } + } + + pub async fn try_from_archive( + archive: &CgkaArchive, + secret_store: K, + ) -> Result> { + let mut pcs_keys = HashMap::new(); + for k in archive.pcs_keys.iter() { + let v = secret_store + .get_secret_key(k) + .await + .map_err(TryCgkaFromArchiveError::GetSecretError)? + .ok_or(TryCgkaFromArchiveError::CannotFindSecret(*k))?; + pcs_keys.insert(*k, PcsKey(v)); + } + + Ok(Self { + doc_id: archive.doc_id, + owner_id: archive.owner_id, + tree: archive.tree.clone(), + ops_graph: archive.ops_graph.clone(), + pending_ops_for_structural_change: archive.pending_ops_for_structural_change, + pcs_keys, + pcs_key_ops: archive.pcs_key_ops.iter().cloned().collect(), + original_member: archive.original_member, + init_add_op: archive.init_add_op.clone(), + store: secret_store, + }) + } +} + +#[derive(Error, Debug)] +pub enum NewAppSecretError { + #[error(transparent)] + CgkaError(#[from] CgkaError), + + #[error(transparent)] + UpdateLeafError(#[from] UpdateLeafError), + + #[error("Error while trying to access secret key store: {0}")] + GenerateSecretError(K::GenerateSecretError), + + #[error("ECDH error: {0}")] + EcdhError(::EcdhError), + + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), +} + +pub enum UpdateLeafError { + CgkaError(CgkaError), + SigningError(SigningError), + ImportKeyError(K::ImportKeyError), + DecryptSecretError(DecryptSecretError), + DecryptTreeSecretError(Box>), +} + +impl From for UpdateLeafError { + fn from(e: SigningError) -> Self { + UpdateLeafError::SigningError(e) + } +} + +impl From for UpdateLeafError { + fn from(e: CgkaError) -> Self { + UpdateLeafError::CgkaError(e) + } } -impl Fork for Cgka { +impl From> for UpdateLeafError { + fn from(e: DecryptSecretError) -> Self { + UpdateLeafError::DecryptSecretError(e) + } +} + +impl Debug for UpdateLeafError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UpdateLeafError::CgkaError(e) => Debug::fmt(e, f), + UpdateLeafError::SigningError(e) => Debug::fmt(e, f), + UpdateLeafError::ImportKeyError(e) => Debug::fmt(e, f), + UpdateLeafError::DecryptSecretError(e) => Debug::fmt(e, f), + UpdateLeafError::DecryptTreeSecretError(e) => Debug::fmt(e, f), + } + } +} + +impl Display for UpdateLeafError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UpdateLeafError::CgkaError(e) => Display::fmt(e, f), + UpdateLeafError::SigningError(e) => Display::fmt(e, f), + UpdateLeafError::ImportKeyError(e) => Display::fmt( + &format!( + "Error while trying to import key into the secret key store: {}", + e + ), + f, + ), + UpdateLeafError::DecryptSecretError(e) => Display::fmt(e, f), + UpdateLeafError::DecryptTreeSecretError(e) => Display::fmt(e, f), + } + } +} + +#[derive(Error, Debug)] +pub enum PcsKeyFromHashesError { + #[error(transparent)] + CgkaError(#[from] CgkaError), + + #[error("Error while trying to access secret key store: {0}")] + GetSecretError(K::GetSecretError), + + #[error(transparent)] + DecryptSecretError(#[from] DecryptSecretError), + + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), + + #[error("Error while trying to derive a PcsKey: {0}")] + EcdhError(::EcdhError), +} + +#[derive(Error)] +pub enum TryCgkaFromArchiveError { + #[error("Error while trying to access secret key store: {0}")] + GetSecretError(K::GetSecretError), + + #[error("Secret key not found for public key: {0}")] + CannotFindSecret(ShareKey), +} + +impl Debug for TryCgkaFromArchiveError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TryCgkaFromArchiveError::GetSecretError(e) => Debug::fmt(e, f), + TryCgkaFromArchiveError::CannotFindSecret(k) => { + write!(f, "Cannot find secret for key: {k}") + } + } + } +} + +impl Fork for Cgka { type Forked = Self; fn fork(&self) -> Self::Forked { @@ -580,31 +791,75 @@ impl Fork for Cgka { } } -impl Merge for Cgka { +impl Merge for Cgka { fn merge(&mut self, fork: Self::Forked) { - self.owner_sks.merge(fork.owner_sks); self.ops_graph.merge(fork.ops_graph); self.pcs_keys.merge(fork.pcs_keys); - self.replay_ops_graph() - .expect("two valid graphs should always merge causal consistency"); + } +} + +impl Debug for Cgka { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Cgka { + doc_id, + owner_id, + tree, + ops_graph, + pending_ops_for_structural_change, + pcs_keys, + pcs_key_ops, + original_member, + init_add_op, + store: _, + } = self; + + f.debug_struct("Cgka") + .field("doc_id", doc_id) + .field("owner_id", owner_id) + .field("tree", tree) + .field("ops_graph", ops_graph) + .field( + "pending_ops_for_structural_change", + pending_ops_for_structural_change, + ) + .field("pcs_keys", pcs_keys) + .field("pcs_key_ops", pcs_key_ops) + .field("original_member", original_member) + .field("init_add_op", init_add_op) + .field("store", &"") + .finish() } } #[cfg(feature = "test_utils")] -impl Cgka { - pub fn secret_from_root(&mut self) -> Result { - self.pcs_key_from_tree_root() +impl Cgka { + pub async fn secret_from_root( + &mut self, + ) -> Result, DecryptTreeSecretError> { + self.pcs_key_from_tree_root().await } - pub fn secret( + pub async fn secret( &mut self, - pcs_key_hash: &Digest, + pcs_pubkey: ShareKey, update_op_hash: &Digest>, - ) -> Result { - self.pcs_key_from_hashes(pcs_key_hash, update_op_hash) + ) -> Result, PcsKeyFromHashesError> { + self.pcs_key_from_hashes(pcs_pubkey, update_op_hash).await } } +#[derive(Error, Debug)] +pub enum RemoveError { + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), + + #[error(transparent)] + SigningError(#[from] SigningError), + + #[error(transparent)] + CgkaError(#[from] CgkaError), +} + // FIXME // #[cfg(feature = "test_utils")] // #[cfg(test)] diff --git a/keyhive_core/src/cgka/archive.rs b/keyhive_core/src/cgka/archive.rs new file mode 100644 index 00000000..5691f0de --- /dev/null +++ b/keyhive_core/src/cgka/archive.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + crypto::{digest::Digest, share_key::ShareKey, signed::Signed}, + principal::{document::id::DocumentId, individual::id::IndividualId}, +}; + +use super::{ + beekem::BeeKem, + operation::{CgkaOperation, CgkaOperationGraph}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CgkaArchive { + pub(crate) doc_id: DocumentId, + pub(crate) owner_id: IndividualId, + pub(crate) tree: BeeKem, + pub(crate) ops_graph: CgkaOperationGraph, + pub(crate) pending_ops_for_structural_change: bool, + pub(crate) pcs_keys: Vec, + pub(crate) original_member: (IndividualId, ShareKey), + pub(crate) init_add_op: Signed, + pub(crate) pcs_key_ops: Vec<(ShareKey, Digest>)>, +} diff --git a/keyhive_core/src/cgka/beekem.rs b/keyhive_core/src/cgka/beekem.rs index 2282dde8..1e47a8aa 100644 --- a/keyhive_core/src/cgka/beekem.rs +++ b/keyhive_core/src/cgka/beekem.rs @@ -1,17 +1,25 @@ use super::{ - error::CgkaError, keys::NodeKey, keys::ShareKeyMap, secret_store::SecretStore, treemath, + error::CgkaError, + keys::NodeKey, + secret_store::{DecryptSecretError, SecretStore}, + treemath, UpdateLeafError, }; use crate::{ crypto::{ application_secret::PcsKey, encrypted::EncryptedSecret, - share_key::{ShareKey, ShareSecretKey}, + share_key::{AsyncSecretKey, ShareKey, ShareSecretKey}, siv::Siv, }, principal::{document::id::DocumentId, individual::id::IndividualId}, + store::secret_key::traits::ShareSecretStore, }; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashSet}; +use std::{ + collections::{BTreeMap, HashSet}, + fmt::Debug, +}; +use thiserror::Error; use tracing::instrument; use treemath::{InnerNodeIndex, LeafNodeIndex, TreeNodeIndex, TreeSize}; @@ -202,14 +210,15 @@ impl BeeKem { /// encrypted for any of these descendents (in cases like a blank node or /// conflicting keys on a node on the path). #[instrument(skip_all, fields(doc_id, epochs))] - pub(crate) fn decrypt_tree_secret( + pub(crate) async fn decrypt_tree_secret( &self, owner_id: IndividualId, - owner_sks: &mut ShareKeyMap, - ) -> Result { + owner_sks: &mut S, + ) -> Result> { + let local_pk: ShareKey = x25519_dalek::PublicKey::from(self.doc_id.to_bytes()).into(); let leaf_idx = *self.leaf_index_for_id(owner_id)?; if !self.has_root_key() { - return Err(CgkaError::NoRootKey); + return Err(CgkaError::NoRootKey)?; } let leaf = self .leaf(leaf_idx) @@ -217,11 +226,29 @@ impl BeeKem { .expect("Leaf should not be blank"); if Some(leaf_idx) == self.current_secret_encrypter_leaf_idx { let NodeKey::ShareKey(pk) = leaf.pk else { - return Err(CgkaError::ShareKeyNotFound); + return Err(CgkaError::ShareKeyNotFound)?; }; - let secret = owner_sks.get(&pk).ok_or(CgkaError::ShareKeyNotFound)?; - return Ok(secret - .ratchet_n_forward(treemath::direct_path(leaf_idx.into(), self.tree_size).len())); + let secret = owner_sks + .get_secret_key(&pk) + .await + .map_err(DecryptTreeSecretError::GetSecretError)? + .ok_or(CgkaError::ShareKeyNotFound)?; + + if let Some(nz) = + std::num::NonZero::new(treemath::direct_path(leaf_idx.into(), self.tree_size).len()) + { + let new_sk = secret + .ratchet_n_forward(local_pk, nz) + .await + .map_err(DecryptTreeSecretError::EcdhError)?; + let imported = owner_sks + .import_secret_key(new_sk) + .await + .map_err(DecryptTreeSecretError::ImportKeyError)?; + return Ok(imported); + } else { + return Err(DecryptTreeSecretError::CannotRatchet); + } } let lca_with_encrypter = treemath::lowest_common_ancestor( leaf_idx, @@ -241,8 +268,9 @@ impl BeeKem { parent_idx = treemath::parent(child_idx).into(); } debug_assert!(!self.is_root(child_idx)); - maybe_last_secret_decrypted = - self.maybe_decrypt_parent_key(child_idx, &child_node_key, &seen_idxs, owner_sks)?; + maybe_last_secret_decrypted = self + .maybe_decrypt_parent_key(child_idx, &child_node_key, &seen_idxs, owner_sks) + .await?; let Some(ref secret) = maybe_last_secret_decrypted else { panic!("Non-blank, non-conflict parent should have a secret we can decrypt"); }; @@ -250,15 +278,28 @@ impl BeeKem { // path, then we can ratchet this parent secret forward for each of the // remaining nodes in the path and return early. if parent_idx == TreeNodeIndex::Inner(lca_with_encrypter) { - return Ok(secret - .ratchet_n_forward(treemath::direct_path(parent_idx, self.tree_size).len())); + if let Some(nz) = + std::num::NonZero::new(treemath::direct_path(parent_idx, self.tree_size).len()) + { + let ratchetted = secret + .ratchet_n_forward(local_pk, nz) + .await + .map_err(DecryptTreeSecretError::EcdhError)?; + + let imported = owner_sks + .import_secret_key(ratchetted) + .await + .map_err(DecryptTreeSecretError::ImportKeyError)?; + + return Ok(imported); + } } seen_idxs.push(parent_idx); child_idx = parent_idx; child_node_key = self.node_key_for_index(child_idx)?; parent_idx = treemath::parent(child_idx).into(); } - maybe_last_secret_decrypted.ok_or(CgkaError::NoRootKey) + Ok(maybe_last_secret_decrypted.ok_or(CgkaError::NoRootKey)?) } /// Rotate key and encrypt new secrets along the provided [`IndividualId`]'s path. @@ -275,13 +316,14 @@ impl BeeKem { /// pair but encrypt the secret by doing Diffie Hellman with a different key pair /// generated just for that purpose. The secret store for that parent will then /// only have an entry for you. - pub(crate) fn encrypt_path( + pub(crate) async fn encrypt_path( &mut self, id: IndividualId, pk: ShareKey, - sks: &mut ShareKeyMap, + sks: &mut S, csprng: &mut R, - ) -> Result, CgkaError> { + ) -> Result, PathChange)>, DecryptTreeSecretError> { + let local_pk: ShareKey = x25519_dalek::PublicKey::from(self.doc_id.to_bytes()).into(); let leaf_idx = *self.leaf_index_for_id(id)?; debug_assert!(self.id_for_leaf(leaf_idx).unwrap() == id); let mut new_path = PathChange { @@ -298,22 +340,33 @@ impl BeeKem { // key at the start. And as it moves up the path, it will generate a new public // key for each ancestor up to the root. let mut child_pk = pk; - let mut child_sk = *sks.get(&pk).ok_or(CgkaError::SecretKeyNotFound)?; + let mut child_sk = sks + .get_secret_key(&pk) + .await + .map_err(DecryptTreeSecretError::GetSecretError)? + .ok_or(CgkaError::SecretKeyNotFound)?; let mut parent_idx = treemath::parent(child_idx); while !self.is_root(child_idx) { if let Some(store) = self.inner_node(parent_idx) { new_path.removed_keys.append(&mut store.node_key().keys()); } - let new_parent_sk = child_sk.ratchet_forward(); - let new_parent_pk = new_parent_sk.share_key(); + let mem_new_parent_sk = child_sk + .ratchet_forward(local_pk) + .await + .map_err(DecryptTreeSecretError::EcdhError)?; + let new_parent_sk = sks + .import_secret_key(mem_new_parent_sk) + .await + .map_err(DecryptTreeSecretError::ImportKeyError)?; + let new_parent_pk = new_parent_sk.to_share_key(); self.encrypt_key_for_parent( child_idx, child_pk, - &child_sk, new_parent_pk, - &new_parent_sk, + &mem_new_parent_sk, csprng, - )?; + ) + .await?; new_path.path.push(( parent_idx.u32(), self.inner_node(parent_idx) @@ -327,7 +380,7 @@ impl BeeKem { parent_idx = treemath::parent(child_idx); } self.current_secret_encrypter_leaf_idx = Some(leaf_idx); - Ok(Some((child_sk.into(), new_path))) + Ok(Some((PcsKey::new(child_sk), new_path))) } /// Applies a [`PathChange`] representing new public and encrypted secret keys for each @@ -397,13 +450,13 @@ impl BeeKem { /// Returns the secret if there is a single parent public key. /// In either case, adds any public key/decrypted secret key pairs /// it encounters to the [`ShareKeyMap`]. - fn maybe_decrypt_parent_key( + async fn maybe_decrypt_parent_key( &self, child_idx: TreeNodeIndex, child_node_key: &NodeKey, seen_idxs: &[TreeNodeIndex], - child_sks: &mut ShareKeyMap, - ) -> Result, CgkaError> { + child_sks: &mut S, + ) -> Result, DecryptSecretError> { debug_assert!(!self.is_root(child_idx)); let parent_idx = treemath::parent(child_idx); let Some(parent) = self.inner_node(parent_idx) else { @@ -413,11 +466,17 @@ impl BeeKem { let maybe_secret = match parent.node_key() { NodeKey::ConflictKeys(_) => None, NodeKey::ShareKey(parent_pk) => { - if child_sks.contains_key(&parent_pk) { - return Ok(child_sks.get(&parent_pk).cloned()); + if let Some(child_sk) = child_sks + .get_secret_key(&parent_pk) + .await + .map_err(DecryptSecretError::GetSecretError)? + { + return Ok(Some(child_sk)); } - let secret = parent.decrypt_secret(child_node_key, child_sks, seen_idxs)?; - child_sks.insert(parent_pk, secret); + let secret = parent + .decrypt_secret(child_node_key, child_sks, seen_idxs) + .await?; + Some(secret) } }; @@ -425,25 +484,25 @@ impl BeeKem { } /// Encrypt new secret for parent node. - fn encrypt_key_for_parent( + async fn encrypt_key_for_parent( &mut self, child_idx: TreeNodeIndex, child_pk: ShareKey, - child_sk: &ShareSecretKey, new_parent_pk: ShareKey, new_parent_sk: &ShareSecretKey, csprng: &mut R, ) -> Result<(), CgkaError> { debug_assert!(!self.is_root(child_idx)); let parent_idx = treemath::parent(child_idx); - let secret_store = self.encrypt_new_secret_store_for_parent( - child_idx, - child_pk, - child_sk, - new_parent_pk, - new_parent_sk, - csprng, - )?; + let secret_store = self + .encrypt_new_secret_store_for_parent( + child_idx, + child_pk, + new_parent_pk, + new_parent_sk, + csprng, + ) + .await?; self.insert_inner_node_at(parent_idx, secret_store); Ok(()) } @@ -454,11 +513,10 @@ impl BeeKem { /// node's resolution. These are then stored in the new [`SecretStore`], indexed /// by the tree index for each member of that resolution. #[allow(clippy::type_complexity)] - fn encrypt_new_secret_store_for_parent( + async fn encrypt_new_secret_store_for_parent( &self, child_idx: TreeNodeIndex, child_pk: ShareKey, - child_sk: &ShareSecretKey, new_parent_pk: ShareKey, new_parent_sk: &ShareSecretKey, csprng: &mut R, @@ -474,7 +532,7 @@ impl BeeKem { // just to do DH with when ecrypting the new parent secret. let paired_sk = ShareSecretKey::generate(csprng); let paired_pk = paired_sk.share_key(); - let encrypted_sk = encrypt_secret(self.doc_id, *new_parent_sk, child_sk, &paired_pk)?; + let encrypted_sk = encrypt_secret(self.doc_id, *new_parent_sk, paired_pk).await?; secret_map.insert(child_idx, encrypted_sk); } else { // Encrypt the secret for every node in the sibling resolution, using @@ -485,7 +543,7 @@ impl BeeKem { NodeKey::ShareKey(share_key) => share_key, _ => panic!("Sibling resolution nodes should have exactly one ShareKey"), }; - let encrypted_sk = encrypt_secret(self.doc_id, *new_parent_sk, child_sk, &next_pk)?; + let encrypted_sk = encrypt_secret(self.doc_id, *new_parent_sk, next_pk).await?; if !used_paired_sibling { secret_map.insert(child_idx, encrypted_sk.clone()); used_paired_sibling = true; @@ -640,17 +698,60 @@ pub struct LeafNode { pub pk: NodeKey, } -fn encrypt_secret( +async fn encrypt_secret( doc_id: DocumentId, secret: ShareSecretKey, - sk: &ShareSecretKey, - paired_pk: &ShareKey, + paired_pk: ShareKey, ) -> Result, CgkaError> { - let key = sk.derive_symmetric_key(paired_pk); + let key = secret + .derive_symmetric_key(paired_pk) + .await + .expect("infallable"); + let mut ciphertext: Vec = (&secret).into(); let nonce = Siv::new(&key, &ciphertext, doc_id) .map_err(|e| CgkaError::DeriveNonce(format!("{:?}", e)))?; + key.try_encrypt(nonce, &mut ciphertext) .map_err(CgkaError::Encryption)?; - Ok(EncryptedSecret::new(nonce, ciphertext, *paired_pk)) + + Ok(EncryptedSecret::new(nonce, ciphertext, paired_pk)) +} + +#[derive(Error)] +pub enum DecryptTreeSecretError { + #[error(transparent)] + CgkaError(#[from] CgkaError), + + #[error("Cannot get secret: {0}")] + GetSecretError(S::GetSecretError), + + #[error("Error importing key: {0}")] + ImportKeyError(S::ImportKeyError), + + #[error(transparent)] + UpdateLeafError(#[from] UpdateLeafError), + + #[error("ECDH error: {0}")] + EcdhError(::EcdhError), + + #[error(transparent)] + DecryptSecretError(#[from] DecryptSecretError), + + #[error("Cannot ratchet")] + CannotRatchet, +} + +impl Debug for DecryptTreeSecretError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DecryptTreeSecretError::CgkaError(e) => e.fmt(f), + DecryptTreeSecretError::GetSecretError(e) => e.fmt(f), + DecryptTreeSecretError::ImportKeyError(e) => e.fmt(f), + DecryptTreeSecretError::UpdateLeafError(e) => e.fmt(f), + DecryptTreeSecretError::EcdhError(e) => e.fmt(f), + DecryptTreeSecretError::DecryptSecretError(e) => e.fmt(f), + DecryptTreeSecretError::CannotRatchet => f.write_str("Cannot ratchet"), + } + } } diff --git a/keyhive_core/src/cgka/keys.rs b/keyhive_core/src/cgka/keys.rs index 39390abf..57d38648 100644 --- a/keyhive_core/src/cgka/keys.rs +++ b/keyhive_core/src/cgka/keys.rs @@ -1,77 +1,5 @@ -use crate::{ - crypto::{ - encrypted::EncryptedSecret, - share_key::{ShareKey, ShareSecretKey}, - }, - transact::{fork::Fork, merge::Merge}, -}; +use crate::crypto::share_key::ShareKey; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -use super::error::CgkaError; - -/// A [`ShareKeyMap`] is used to store the secret keys for all of the public keys -/// on your path that you have encountered so far (either because you added them -/// to your path as part of an update or decrypted them when decrypting your path). -#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] -pub struct ShareKeyMap(BTreeMap); - -impl ShareKeyMap { - pub fn new() -> Self { - ShareKeyMap(BTreeMap::new()) - } - - pub fn insert(&mut self, pk: ShareKey, sk: ShareSecretKey) { - self.0.insert(pk, sk); - } - - pub fn get(&self, pk: &ShareKey) -> Option<&ShareSecretKey> { - self.0.get(pk) - } - - pub fn contains_key(&self, pk: &ShareKey) -> bool { - self.0.contains_key(pk) - } - - pub fn try_decrypt_encryption( - &self, - encrypter_pk: ShareKey, - encrypted: &EncryptedSecret, - ) -> Result, CgkaError> { - let sk = self - .get(&encrypted.paired_pk) - .ok_or(CgkaError::SecretKeyNotFound)?; - let key = sk.derive_symmetric_key(&encrypter_pk); - let mut buf = encrypted.ciphertext.clone(); - key.try_decrypt(encrypted.nonce, &mut buf) - .map_err(|e| CgkaError::Decryption(e.to_string()))?; - Ok(buf) - } - - pub fn extend(&mut self, other: &ShareKeyMap) { - self.0.extend(other.0.iter()); - } -} - -impl Fork for ShareKeyMap { - type Forked = Self; - - fn fork(&self) -> Self::Forked { - self.clone() - } -} - -impl Merge for ShareKeyMap { - fn merge(&mut self, fork: Self::Forked) { - self.0.extend(fork.0) - } -} - -impl std::hash::Hash for ShareKeyMap { - fn hash(&self, state: &mut H) { - self.0.keys().for_each(|k| k.hash(state)); - } -} #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] diff --git a/keyhive_core/src/cgka/secret_store.rs b/keyhive_core/src/cgka/secret_store.rs index 5164fd85..d31721d7 100644 --- a/keyhive_core/src/cgka/secret_store.rs +++ b/keyhive_core/src/cgka/secret_store.rs @@ -1,17 +1,23 @@ use super::{ error::CgkaError, - keys::{ConflictKeys, NodeKey, ShareKeyMap}, + keys::{ConflictKeys, NodeKey}, treemath::TreeNodeIndex, }; -use crate::crypto::{ - encrypted::EncryptedSecret, - share_key::{ShareKey, ShareSecretKey}, +use crate::{ + crypto::{ + encrypted::EncryptedSecret, + share_key::{ShareKey, ShareSecretKey}, + signed::SigningError, + }, + store::secret_key::traits::{DecryptionError, ShareSecretStore}, }; +use derive_where::derive_where; use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, collections::{BTreeMap, HashSet}, }; +use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -72,16 +78,26 @@ impl SecretStore { } } - pub fn decrypt_secret( + pub async fn decrypt_secret( &self, child_node_key: &NodeKey, - child_sks: &mut ShareKeyMap, + child_sks: &mut S, seen_idxs: &[TreeNodeIndex], - ) -> Result { + ) -> Result> { if self.has_conflict() { - return Err(CgkaError::UnexpectedKeyConflict); + return Err(CgkaError::UnexpectedKeyConflict)?; } - self.versions[0].decrypt_secret(child_node_key, child_sks, seen_idxs) + + let secret = self.versions[0] + .decrypt_secret(child_node_key, child_sks, seen_idxs) + .await?; + + let imported = child_sks + .import_secret_key(secret) + .await + .map_err(DecryptSecretError::ImportKeyError)?; + + Ok(imported) } // TODO: Is it possible we're bringing in duplicate keys here? @@ -104,6 +120,25 @@ impl SecretStore { } } +#[derive(Error)] +#[derive_where(Debug)] +pub enum DecryptSecretError { + #[error(transparent)] + CgkaError(#[from] CgkaError), + + #[error("Failed to decrypt the secret: {0}")] + DecryptSecretError(#[from] StoreDecryptSecretError), + + #[error("Failed to find a secret key: {0}")] + GetSecretError(K::GetSecretError), + + #[error("Failed to import the secret key: {0}")] + ImportKeyError(K::ImportKeyError), + + #[error("Unable to sign: {0}")] + SigningError(#[from] SigningError), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub(crate) struct SecretStoreVersion { @@ -118,12 +153,12 @@ pub(crate) struct SecretStoreVersion { } impl SecretStoreVersion { - pub(crate) fn decrypt_secret( + pub(crate) async fn decrypt_secret( &self, child_node_key: &NodeKey, - child_sks: &ShareKeyMap, + child_sks: &S, seen_idxs: &[TreeNodeIndex], - ) -> Result { + ) -> Result> { let is_encrypter = child_node_key.contains_key(&self.encrypter_pk); let mut lookup_idx = seen_idxs.last().ok_or(CgkaError::EncryptedSecretNotFound)?; if !self.sk.contains_key(lookup_idx) { @@ -136,7 +171,7 @@ impl SecretStoreVersion { } } if !found { - return Err(CgkaError::EncryptedSecretNotFound); + return Err(CgkaError::EncryptedSecretNotFound)?; } } let encrypted = self @@ -146,14 +181,19 @@ impl SecretStoreVersion { let decrypted: Vec = if is_encrypter { let secret_key = child_sks - .get(&self.encrypter_pk) + .get_secret_key(&self.encrypter_pk) + .await + .map_err(StoreDecryptSecretError::GetSecretError)? .ok_or(CgkaError::SecretKeyNotFound)?; encrypted - .try_encrypter_decrypt(secret_key) + .try_encrypter_decrypt(&secret_key) + .await .map_err(|e| CgkaError::Decryption(e.to_string()))? } else { - child_sks.try_decrypt_encryption(self.encrypter_pk, encrypted)? + child_sks + .try_decrypt_encryption(self.encrypter_pk, encrypted) + .await? }; let arr: [u8; 32] = decrypted.try_into().map_err(|_| CgkaError::Conversion)?; @@ -172,3 +212,16 @@ impl PartialOrd for SecretStoreVersion { Some(self.cmp(other)) } } + +#[derive(Error)] +#[derive_where(Debug)] +pub enum StoreDecryptSecretError { + #[error(transparent)] + DecryptionError(#[from] DecryptionError), + + #[error(transparent)] + CgkaError(#[from] CgkaError), + + #[error("Failed to get the secret key: {0}")] + GetSecretError(K::GetSecretError), +} diff --git a/keyhive_core/src/crypto/application_secret.rs b/keyhive_core/src/crypto/application_secret.rs index 5a43d7cf..5b7815e2 100644 --- a/keyhive_core/src/crypto/application_secret.rs +++ b/keyhive_core/src/crypto/application_secret.rs @@ -1,13 +1,17 @@ //! Encryption keys, key derivation, and associated metadata. -use super::signed::Signed; +use super::{ + share_key::{AsyncSecretKey, ShareKey}, + signed::Signed, +}; use crate::{ cgka::operation::CgkaOperation, content::reference::ContentRef, crypto::{ - digest::Digest, encrypted::EncryptedContent, separable::Separable, - share_key::ShareSecretKey, siv::Siv, symmetric_key::SymmetricKey, + digest::Digest, encrypted::EncryptedContent, separable::Separable, siv::Siv, + symmetric_key::SymmetricKey, }, + principal::document::id::DocumentId, }; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -18,7 +22,7 @@ const STATIC_CONTEXT: &str = "/keyhive/beekem/app_secret/"; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ApplicationSecret { key: SymmetricKey, - pcs_key_hash: Digest, + pcs_pubkey: ShareKey, pcs_update_op_hash: Digest>, nonce: Siv, content_ref: Cr, @@ -29,7 +33,7 @@ impl ApplicationSecret { /// Construct a new [`ApplicationSecret`]. pub fn new( key: SymmetricKey, - pcs_key_hash: Digest, + pcs_pubkey: ShareKey, pcs_update_op_hash: Digest>, nonce: Siv, content_ref: Cr, @@ -37,7 +41,7 @@ impl ApplicationSecret { ) -> Self { Self { key, - pcs_key_hash, + pcs_pubkey, pcs_update_op_hash, nonce, content_ref, @@ -65,7 +69,7 @@ impl ApplicationSecret { Ok(EncryptedContent::new( self.nonce, ciphertext, - self.pcs_key_hash, + self.pcs_pubkey, self.pcs_update_op_hash, self.content_ref.clone(), self.pred_refs, @@ -75,49 +79,47 @@ impl ApplicationSecret { /// A key used to derive application secrets. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] -pub struct PcsKey(pub ShareSecretKey); +pub struct PcsKey(pub T); -impl PcsKey { +impl PcsKey { /// Lift a `ShareSecretKey` into a `PcsKey`. - pub fn new(share_secret_key: ShareSecretKey) -> Self { + pub fn new(share_secret_key: T) -> Self { Self(share_secret_key) } - #[instrument] - pub(crate) fn derive_application_secret( + #[instrument(skip_all)] + pub(crate) async fn derive_application_secret( &self, + doc_id: DocumentId, nonce: &Siv, content_ref: &Cr, pred_refs: &Digest>, pcs_update_op_hash: &Digest>, - ) -> ApplicationSecret { - let pcs_hash = Digest::hash(&self.0); + ) -> Result, T::EcdhError> { + let pcs_hash = Digest::hash(&self.0.to_share_key()); let display_ref = Digest::hash(&content_ref); let mut app_secret_context = format!("epoch:{pcs_hash}/pred:{pred_refs}/content:{display_ref}").into_bytes(); - let mut key_material = self.0.clone().as_slice().to_vec(); + let local_pk: ShareKey = x25519_dalek::PublicKey::from(doc_id.to_bytes()).into(); + let mut key_material = self.0.derive_bytes(local_pk).await?.to_vec(); key_material.append(&mut app_secret_context); let app_secret_bytes = blake3::derive_key(STATIC_CONTEXT, key_material.as_slice()); let symmetric_key = SymmetricKey::derive_from_bytes(&app_secret_bytes); - ApplicationSecret::new( + Ok(ApplicationSecret::new( symmetric_key, - Digest::hash(self), + self.0.to_share_key(), *pcs_update_op_hash, *nonce, content_ref.clone(), *pred_refs, - ) - } -} - -impl From for PcsKey { - fn from(share_secret_key: ShareSecretKey) -> PcsKey { - PcsKey(share_secret_key) + )) } -} -impl From for SymmetricKey { - fn from(pcs_key: PcsKey) -> SymmetricKey { - SymmetricKey::derive_from_bytes(pcs_key.0.as_slice()) + pub(crate) async fn to_symmetric_key( + &self, + doc_id: DocumentId, + ) -> Result { + let local_pk: ShareKey = x25519_dalek::PublicKey::from(doc_id.to_bytes()).into(); + self.0.derive_symmetric_key(local_pk).await } } diff --git a/keyhive_core/src/crypto/digest.rs b/keyhive_core/src/crypto/digest.rs index 6772652a..1b513bec 100644 --- a/keyhive_core/src/crypto/digest.rs +++ b/keyhive_core/src/crypto/digest.rs @@ -10,6 +10,7 @@ use crate::{ membership_operation::{MembershipOperation, StaticMembershipOperation}, revocation::{Revocation, StaticRevocation}, }, + store::secret_key::traits::ShareSecretStore, }; use serde::{Deserialize, Serialize}; use std::{ @@ -251,64 +252,64 @@ impl From> for Vec { // Casts -impl> - From>>> for Digest>> +impl> + From>>> for Digest>> { - fn from(hash: Digest>>) -> Self { + fn from(hash: Digest>>) -> Self { hash.coerce() } } -impl> - From>>> for Digest>> +impl> + From>>> for Digest>> { fn from(hash: Digest>>) -> Self { hash.coerce() } } -impl> - From>>> for Digest>> +impl> + From>>> for Digest>> { - fn from(hash: Digest>>) -> Self { + fn from(hash: Digest>>) -> Self { hash.coerce() } } -impl> - From>>> for Digest>> +impl> + From>>> for Digest>> { fn from(hash: Digest>>) -> Self { hash.coerce() } } -impl> - From<&Digest>>> for Digest>> +impl> + From<&Digest>>> for Digest>> { - fn from(hash: &Digest>>) -> Self { + fn from(hash: &Digest>>) -> Self { hash.coerce() } } -impl> - From<&Digest>>> for Digest>> +impl> + From<&Digest>>> for Digest>> { fn from(hash: &Digest>>) -> Self { hash.coerce() } } -impl> - From<&Digest>>> for Digest>> +impl> + From<&Digest>>> for Digest>> { - fn from(hash: &Digest>>) -> Self { + fn from(hash: &Digest>>) -> Self { hash.coerce() } } -impl> - From<&Digest>>> for Digest>> +impl> + From<&Digest>>> for Digest>> { fn from(hash: &Digest>>) -> Self { hash.coerce() @@ -331,10 +332,10 @@ impl From>>> } } -impl> - From>> for Digest> +impl> + From>> for Digest> { - fn from(hash: Digest>) -> Self { + fn from(hash: Digest>) -> Self { hash.coerce() } } @@ -347,16 +348,16 @@ impl From>> } } -impl> - From>> for Digest>> +impl> + From>> for Digest>> { fn from(hash: Digest>) -> Self { hash.coerce() } } -impl> - From>> for Digest>> +impl> + From>> for Digest>> { fn from(hash: Digest>) -> Self { hash.coerce() @@ -371,98 +372,98 @@ impl From>> } } -impl> - From>> for Digest> +impl> + From>> for Digest> { fn from(hash: Digest>) -> Self { hash.coerce() } } -impl> - From<&Digest>> for Digest> +impl> + From<&Digest>> for Digest> { fn from(hash: &Digest>) -> Self { hash.coerce() } } -impl> - From>> for Digest>> +impl> + From>> for Digest>> { - fn from(hash: Digest>) -> Self { + fn from(hash: Digest>) -> Self { hash.coerce() } } -impl> - From>> for Digest>> +impl> + From>> for Digest>> { - fn from(hash: Digest>) -> Self { + fn from(hash: Digest>) -> Self { hash.coerce() } } -impl> - From<&Digest>> for Digest>> +impl> + From<&Digest>> for Digest>> { - fn from(hash: &Digest>) -> Self { + fn from(hash: &Digest>) -> Self { hash.coerce() } } -impl> - From<&Digest>> for Digest>> +impl> + From<&Digest>> for Digest>> { - fn from(hash: &Digest>) -> Self { + fn from(hash: &Digest>) -> Self { hash.coerce() } } -impl> - From>>> for Digest> +impl> + From>>> for Digest> { - fn from(hash: Digest>>) -> Self { + fn from(hash: Digest>>) -> Self { hash.coerce() } } -impl> - From<&Digest>>> for Digest> +impl> + From<&Digest>>> for Digest> { - fn from(hash: &Digest>>) -> Self { + fn from(hash: &Digest>>) -> Self { hash.coerce() } } -impl> - From>>> for Digest> +impl> + From>>> for Digest> { - fn from(hash: Digest>>) -> Self { + fn from(hash: Digest>>) -> Self { hash.coerce() } } -impl> - From<&Digest>>> for Digest> +impl> + From<&Digest>>> for Digest> { - fn from(hash: &Digest>>) -> Self { + fn from(hash: &Digest>>) -> Self { hash.coerce() } } -impl> - From>> for Digest> +impl> + From>> for Digest> { - fn from(hash: Digest>) -> Self { + fn from(hash: Digest>) -> Self { hash.coerce() } } -impl> From>> - for Digest> +impl> + From>> for Digest> { - fn from(hash: Digest>) -> Self { + fn from(hash: Digest>) -> Self { hash.coerce() } } diff --git a/keyhive_core/src/crypto/encrypted.rs b/keyhive_core/src/crypto/encrypted.rs index 35bfe863..c4bbe046 100644 --- a/keyhive_core/src/crypto/encrypted.rs +++ b/keyhive_core/src/crypto/encrypted.rs @@ -1,9 +1,8 @@ //! Ciphertext with public metadata. use super::{ - application_secret::PcsKey, digest::Digest, - share_key::{ShareKey, ShareSecretKey}, + share_key::{AsyncSecretKey, ShareKey}, signed::Signed, siv::Siv, symmetric_key::SymmetricKey, @@ -11,6 +10,7 @@ use super::{ use crate::{cgka::operation::CgkaOperation, content::reference::ContentRef}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; +use thiserror::Error; use tracing::instrument; /// The public information for an encrypted content ciphertext. @@ -24,7 +24,7 @@ pub struct EncryptedContent { /// The encrypted data. pub ciphertext: Vec, // TODO wrap in newtype /// Hash of the PCS key used to derive the application secret for encrypting. - pub pcs_key_hash: Digest, // TODO use pubkey instead of hash? + pub pcs_pubkey: ShareKey, /// Hash of the PCS update operation corresponding to the PCS key pub pcs_update_op_hash: Digest>, // TODO check if thi really needs to be a digest? /// The content ref hash used to derive the application secret for encrypting. @@ -41,7 +41,7 @@ impl EncryptedContent { pub fn new( nonce: Siv, ciphertext: Vec, - pcs_key_hash: Digest, + pcs_pubkey: ShareKey, pcs_update_op_hash: Digest>, content_ref: Cr, pred_refs: Digest>, @@ -49,7 +49,7 @@ impl EncryptedContent { EncryptedContent { nonce, ciphertext, - pcs_key_hash, + pcs_pubkey, pcs_update_op_hash, content_ref, pred_refs, @@ -95,14 +95,20 @@ impl EncryptedSecret { } } - #[instrument(skip(self))] - pub fn try_encrypter_decrypt( + #[instrument(skip_all)] + pub async fn try_encrypter_decrypt( &self, - encrypter_secret_key: &ShareSecretKey, - ) -> Result, chacha20poly1305::Error> { + encrypter_secret_key: &Sk, + ) -> Result, TryDecryptError> { let mut buf: Vec = self.ciphertext.clone(); - let key = encrypter_secret_key.derive_symmetric_key(&self.paired_pk); - key.try_decrypt(self.nonce, &mut buf)?; + let key = encrypter_secret_key + .derive_symmetric_key(self.paired_pk) + .await + .map_err(TryDecryptError::EcdhError)?; + + key.try_decrypt(self.nonce, &mut buf) + .map_err(TryDecryptError::DecryptionError)?; + Ok(buf) } } @@ -112,7 +118,7 @@ impl std::hash::Hash for EncryptedContent std::hash::Hash for EncryptedContent { + #[error("Failed to decrypt the ciphertext: {0}")] + DecryptionError(chacha20poly1305::Error), + + #[error("Failed to derive the symmetric key: {0}")] + EcdhError(Sk::EcdhError), +} diff --git a/keyhive_core/src/crypto/share_key.rs b/keyhive_core/src/crypto/share_key.rs index 4cc9bd7a..32ef53d3 100644 --- a/keyhive_core/src/crypto/share_key.rs +++ b/keyhive_core/src/crypto/share_key.rs @@ -2,10 +2,16 @@ //! //! [ECDH]: https://wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman -use super::{separable::Separable, symmetric_key::SymmetricKey}; +use super::{digest::Digest, separable::Separable, symmetric_key::SymmetricKey}; use dupe::Dupe; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{ + convert::Infallible, + fmt::{self, Debug, Display}, + future::Future, + num::NonZero, + rc::Rc, +}; use tracing::instrument; /// Newtype around [x25519_dalek::PublicKey]. @@ -44,6 +50,12 @@ impl Dupe for ShareKey { } } +impl From<[u8; 32]> for ShareKey { + fn from(bytes: [u8; 32]) -> Self { + Self(x25519_dalek::PublicKey::from(bytes)) + } +} + impl fmt::LowerHex for ShareKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { crate::util::hex::bytes_as_hex(self.0.as_bytes().iter(), f) @@ -110,36 +122,20 @@ impl ShareSecretKey { &self.0 } - #[instrument] - pub fn derive_new_secret_key(&self, other: &ShareKey) -> Self { - let bytes: [u8; 32] = x25519_dalek::StaticSecret::from(*self) - .diffie_hellman(&other.0) - .to_bytes(); - - Self::derive_from_bytes(bytes.as_slice()) - } - - #[instrument] - pub fn derive_symmetric_key(&self, other: &ShareKey) -> SymmetricKey { - let secret = x25519_dalek::StaticSecret::from(*self) - .diffie_hellman(&other.0) - .to_bytes(); - - Self::derive_from_bytes(secret.as_slice()).0.into() - } - - #[instrument] - pub fn ratchet_forward(&self) -> Self { - let bytes = self.to_bytes(); - Self::derive_from_bytes(bytes.as_slice()) + pub(crate) fn force_from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) } +} - pub fn ratchet_n_forward(&self, n: usize) -> Self { - (0..n).fold(*self, |acc, _| acc.ratchet_forward()) +impl From for ShareKey { + fn from(secret: ShareSecretKey) -> Self { + secret.share_key() } +} - pub(crate) fn force_from_bytes(bytes: [u8; 32]) -> Self { - Self(bytes) +impl> From> for ShareKey { + fn from(secret: Rc) -> Self { + secret.into() } } @@ -184,3 +180,99 @@ impl fmt::Debug for ShareSecretKey { write!(f, "ShareSecretKey(SECRET)") } } + +pub trait AsyncSecretKey { + type EcdhError: Debug + Display; + + fn to_share_key(&self) -> ShareKey; + + fn ecdh_derive_shared_secret( + &self, + counterparty: ShareKey, + ) -> impl Future>; + + fn derive_bytes( + &self, + counterparty: ShareKey, + ) -> impl Future> { + async move { + let secret = self + .ecdh_derive_shared_secret(counterparty) + .await? + .to_bytes(); + + let extended = secret.to_vec().extend(b"/keyhive/ecdh/derive-bytes/"); + Ok(Digest::hash(&extended).into()) + } + } + + #[instrument(skip(self), fields(pk = %self.to_share_key()))] + fn derive_symmetric_key( + &self, + other: ShareKey, + ) -> impl Future> { + async { + let secret = self.derive_bytes(other).await?; + Ok(SymmetricKey::from(secret)) + } + } + + #[instrument(skip(self), fields(pk = %self.to_share_key()))] + fn ratchet_forward( + &self, + other: ShareKey, + ) -> impl Future> { + async { + let bytes = self.derive_bytes(other).await?; + Ok(ShareSecretKey::force_from_bytes(bytes)) + } + } + + #[instrument(skip(self), fields(pk = %self.to_share_key()))] + fn ratchet_n_forward( + &self, + other: ShareKey, + n: NonZero, + ) -> impl Future> { + async { + let mut acc = self.derive_bytes(other).await?; + let max = n.get() - 1; + for _ in 0..max { + let acc_sk = ShareSecretKey::force_from_bytes(acc); + let acc_pk = acc_sk.share_key(); + acc = self.derive_bytes(acc_pk).await?; + } + Ok(ShareSecretKey::force_from_bytes(acc)) + } + } +} + +impl AsyncSecretKey for ShareSecretKey { + type EcdhError = Infallible; + + fn to_share_key(&self) -> ShareKey { + self.share_key() + } + + async fn ecdh_derive_shared_secret( + &self, + counterparty: ShareKey, + ) -> Result { + Ok(x25519_dalek::StaticSecret::from(*self).diffie_hellman(&counterparty.0)) + } +} + +impl AsyncSecretKey for Rc { + type EcdhError = T::EcdhError; + + fn to_share_key(&self) -> ShareKey { + self.as_ref().to_share_key() + } + + async fn ecdh_derive_shared_secret( + &self, + counterparty: ShareKey, + ) -> Result { + self.as_ref().ecdh_derive_shared_secret(counterparty).await + } +} diff --git a/keyhive_core/src/debug_events.rs b/keyhive_core/src/debug_events.rs index 7e67761a..12e6eb5a 100644 --- a/keyhive_core/src/debug_events.rs +++ b/keyhive_core/src/debug_events.rs @@ -2,11 +2,12 @@ use crate::{ crypto::{digest::Digest, signer::async_signer::AsyncSigner}, event::Event, listener::membership::MembershipListener, + store::secret_key::traits::ShareSecretStore, }; -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Debug, hash::Hash}; -mod hash; -pub use hash::Hash; +pub mod hash; +use hash::DebugHash; use serde::Serialize; pub mod terminal; @@ -24,8 +25,8 @@ pub struct DebugEventTable { pub struct DebugEventRow { pub index: usize, pub event_type: String, - pub event_hash: Hash, - pub issuer: Hash, + pub event_hash: DebugHash, + pub issuer: DebugHash, pub details: DebugEventDetails, } @@ -33,27 +34,27 @@ pub struct DebugEventRow { #[derive(Debug, Clone)] pub enum DebugEventDetails { PrekeysExpanded { - share_key: Hash, + share_key: DebugHash, }, PrekeyRotated { - old_key: Hash, - new_key: Hash, + old_key: DebugHash, + new_key: DebugHash, }, CgkaOperation { op_type: String, - doc_id: Hash, + doc_id: DebugHash, op_details: CgkaOperationDetails, }, Delegated { - subject: Hash, + subject: DebugHash, can_access: String, - delegate: Hash, + delegate: DebugHash, after_revocations_count: usize, after_content_count: usize, }, Revoked { - subject: Hash, - revoke: Hash, + subject: DebugHash, + revoke: DebugHash, has_proof: bool, after_content_count: usize, }, @@ -63,33 +64,36 @@ pub enum DebugEventDetails { #[derive(Debug, Clone)] pub enum CgkaOperationDetails { Add { - id: Hash, - sharekey: Hash, + id: DebugHash, + sharekey: DebugHash, leaf_index: u32, - predecessors: Vec, + predecessors: Vec, }, Remove { - id: Hash, + id: DebugHash, leaf_index: u32, - removed_keys: Vec, - predecessors: Vec, + removed_keys: Vec, + predecessors: Vec, }, Update { - id: Hash, - new_keys: Vec, + id: DebugHash, + new_keys: Vec, path_length: usize, - predecessors: Vec, + predecessors: Vec, }, } impl DebugEventTable { /// Create a new debug event table from a vector of events. - pub fn from_events(events: Vec>, nicknames: Nicknames) -> Self - where + pub fn from_events< S: AsyncSigner, - T: std::fmt::Debug + Eq + Clone + std::hash::Hash + PartialOrd + Serialize, - L: MembershipListener, - { + T: Debug + Eq + Clone + Hash + PartialOrd + Serialize, + K: ShareSecretStore, + L: MembershipListener, + >( + events: Vec>, + nicknames: Nicknames, + ) -> Self { if events.is_empty() { return Self { rows: Vec::new(), @@ -122,19 +126,24 @@ impl DebugEventTable { impl DebugEventRow { /// Create a new debug event row from an event. - pub fn from_event(idx: usize, event: &Event, nicknames: &Nicknames) -> Self + pub fn from_event( + idx: usize, + event: &Event, + nicknames: &Nicknames, + ) -> Self where S: AsyncSigner, + K: ShareSecretStore, T: std::fmt::Debug + Eq + Clone + std::hash::Hash + PartialOrd + Serialize, - L: MembershipListener, + L: MembershipListener, { match event { Event::PrekeysExpanded(signed) => { let payload = signed.payload(); - let event_hash = Hash::new(Digest::hash(signed).raw.as_bytes(), nicknames); - let issuer = Hash::new(signed.issuer().as_bytes(), nicknames); + let event_hash = DebugHash::new(Digest::hash(signed).raw.as_bytes(), nicknames); + let issuer = DebugHash::new(signed.issuer().as_bytes(), nicknames); let details = DebugEventDetails::PrekeysExpanded { - share_key: Hash::new(payload.share_key.as_bytes(), nicknames), + share_key: DebugHash::new(payload.share_key.as_bytes(), nicknames), }; Self { @@ -147,11 +156,11 @@ impl DebugEventRow { } Event::PrekeyRotated(signed) => { let payload = signed.payload(); - let event_hash = Hash::new(Digest::hash(signed).raw.as_bytes(), nicknames); - let issuer = Hash::new(signed.issuer().as_bytes(), nicknames); + let event_hash = DebugHash::new(Digest::hash(signed).raw.as_bytes(), nicknames); + let issuer = DebugHash::new(signed.issuer().as_bytes(), nicknames); let details = DebugEventDetails::PrekeyRotated { - old_key: Hash::new(payload.old.as_bytes(), nicknames), - new_key: Hash::new(payload.new.as_bytes(), nicknames), + old_key: DebugHash::new(payload.old.as_bytes(), nicknames), + new_key: DebugHash::new(payload.new.as_bytes(), nicknames), }; Self { @@ -164,9 +173,9 @@ impl DebugEventRow { } Event::CgkaOperation(signed) => { let payload = signed.payload(); - let event_hash = Hash::new(Digest::hash(signed).raw.as_bytes(), nicknames); - let issuer = Hash::new(signed.issuer().as_bytes(), nicknames); - let doc_id = Hash::new(payload.doc_id().as_bytes(), nicknames); + let event_hash = DebugHash::new(Digest::hash(signed).raw.as_bytes(), nicknames); + let issuer = DebugHash::new(signed.issuer().as_bytes(), nicknames); + let doc_id = DebugHash::new(payload.doc_id().as_bytes(), nicknames); let (op_type, op_details) = match payload { crate::cgka::operation::CgkaOperation::Add { @@ -177,12 +186,14 @@ impl DebugEventRow { .. } => { let op_details = CgkaOperationDetails::Add { - id: Hash::new(added_id.as_bytes(), nicknames), - sharekey: Hash::new(pk.as_bytes(), nicknames), + id: DebugHash::new(added_id.as_bytes(), nicknames), + sharekey: DebugHash::new(pk.as_bytes(), nicknames), leaf_index: *leaf_index, predecessors: predecessors .iter() - .map(|predecessor| Hash::new(predecessor.as_slice(), nicknames)) + .map(|predecessor| { + DebugHash::new(predecessor.as_slice(), nicknames) + }) .collect(), }; ("Add", op_details) @@ -195,15 +206,17 @@ impl DebugEventRow { .. } => { let op_details = CgkaOperationDetails::Remove { - id: Hash::new(id.as_bytes(), nicknames), + id: DebugHash::new(id.as_bytes(), nicknames), leaf_index: *leaf_idx, removed_keys: removed_keys .iter() - .map(|key| Hash::new(key.as_bytes(), nicknames)) + .map(|key| DebugHash::new(key.as_bytes(), nicknames)) .collect(), predecessors: predecessors .iter() - .map(|predecessor| Hash::new(predecessor.as_slice(), nicknames)) + .map(|predecessor| { + DebugHash::new(predecessor.as_slice(), nicknames) + }) .collect(), }; ("Remove", op_details) @@ -216,23 +229,25 @@ impl DebugEventRow { } => { let new_keys = match &new_path.leaf_pk { crate::cgka::keys::NodeKey::ShareKey(share_key) => { - vec![Hash::new(share_key.as_bytes(), nicknames)] + vec![DebugHash::new(share_key.as_bytes(), nicknames)] } crate::cgka::keys::NodeKey::ConflictKeys(conflict_keys) => { // For conflict keys, we'll just use the first one for display purposes conflict_keys .iter() - .map(|k| Hash::new(k.as_bytes(), nicknames)) + .map(|k| DebugHash::new(k.as_bytes(), nicknames)) .collect() } }; let op_details = CgkaOperationDetails::Update { - id: Hash::new(id.as_bytes(), nicknames), + id: DebugHash::new(id.as_bytes(), nicknames), new_keys, path_length: new_path.path.len(), predecessors: predecessors .iter() - .map(|predecessor| Hash::new(predecessor.as_slice(), nicknames)) + .map(|predecessor| { + DebugHash::new(predecessor.as_slice(), nicknames) + }) .collect(), }; ("Update", op_details) @@ -255,17 +270,17 @@ impl DebugEventRow { } Event::Delegated(signed) => { let payload = signed.payload(); - let event_hash = Hash::new(Digest::hash(signed).raw.as_bytes(), nicknames); - let issuer = Hash::new(signed.issuer().as_bytes(), nicknames); + let event_hash = DebugHash::new(Digest::hash(signed).raw.as_bytes(), nicknames); + let issuer = DebugHash::new(signed.issuer().as_bytes(), nicknames); let subject = if let Some(proof) = &payload.proof { - Hash::new(proof.subject_id().as_bytes(), nicknames) + DebugHash::new(proof.subject_id().as_bytes(), nicknames) } else { - Hash::new(signed.issuer().as_bytes(), nicknames) + DebugHash::new(signed.issuer().as_bytes(), nicknames) }; let details = DebugEventDetails::Delegated { subject, can_access: format!("{:?}", payload.can), - delegate: Hash::new(payload.delegate.id().as_bytes(), nicknames), + delegate: DebugHash::new(payload.delegate.id().as_bytes(), nicknames), after_revocations_count: payload.after_revocations.len(), after_content_count: payload.after_content.len(), }; @@ -280,16 +295,16 @@ impl DebugEventRow { } Event::Revoked(signed) => { let payload = signed.payload(); - let event_hash = Hash::new(Digest::hash(signed).raw.as_bytes(), nicknames); - let issuer = Hash::new(signed.issuer().as_bytes(), nicknames); + let event_hash = DebugHash::new(Digest::hash(signed).raw.as_bytes(), nicknames); + let issuer = DebugHash::new(signed.issuer().as_bytes(), nicknames); let subject = if let Some(proof) = &payload.proof { - Hash::new(proof.subject_id().as_bytes(), nicknames) + DebugHash::new(proof.subject_id().as_bytes(), nicknames) } else { - Hash::new(signed.issuer().as_bytes(), nicknames) + DebugHash::new(signed.issuer().as_bytes(), nicknames) }; let details = DebugEventDetails::Revoked { subject, - revoke: Hash::new(Digest::hash(&payload.revoke).as_slice(), nicknames), + revoke: DebugHash::new(Digest::hash(&payload.revoke).as_slice(), nicknames), has_proof: payload.proof.is_some(), after_content_count: payload.after_content.len(), }; diff --git a/keyhive_core/src/debug_events/hash.rs b/keyhive_core/src/debug_events/hash.rs index 3f686001..f9b530bb 100644 --- a/keyhive_core/src/debug_events/hash.rs +++ b/keyhive_core/src/debug_events/hash.rs @@ -2,12 +2,12 @@ use std::fmt; /// A wrapper around a byte array representing a hash #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Hash { +pub enum DebugHash { Hash(Vec), Nickname { original: Vec, nickname: String }, } -impl Hash { +impl DebugHash { /// Create a new Hash from bytes pub fn new(bytes: &[u8], nicknames: &super::Nicknames) -> Self { if let Some(nickname) = nicknames.names.get(bytes) { @@ -40,7 +40,7 @@ impl Hash { } } -impl fmt::Display for Hash { +impl fmt::Display for DebugHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.short_hex()) } diff --git a/keyhive_core/src/event.rs b/keyhive_core/src/event.rs index 3b21d31c..fa9f41b3 100644 --- a/keyhive_core/src/event.rs +++ b/keyhive_core/src/event.rs @@ -19,7 +19,7 @@ use crate::{ }, individual::op::{add_key::AddKeyOp, rotate_key::RotateKeyOp, KeyOp}, }, - store::ciphertext::CiphertextStore, + store::{ciphertext::CiphertextStore, secret_key::traits::ShareSecretStore}, }; use derive_more::{From, TryInto}; use derive_where::derive_where; @@ -31,7 +31,12 @@ use tracing::instrument; /// Top-level event variants. #[derive(PartialEq, Eq, From, TryInto)] #[derive_where(Debug, Hash; T)] -pub enum Event = NoListener> { +pub enum Event< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef = [u8; 32], + L: MembershipListener = NoListener, +> { /// Prekeys were expanded. PrekeysExpanded(Rc>), @@ -42,17 +47,19 @@ pub enum Event>), /// A delegation was created. - Delegated(Rc>>), + Delegated(Rc>>), /// A delegation was revoked. - Revoked(Rc>>), + Revoked(Rc>>), } -impl> Event { +impl> + Event +{ #[allow(clippy::type_complexity)] #[instrument(level = "debug", skip(ciphertext_store))] pub async fn now_decryptable>( - new_events: &[Event], + new_events: &[Event], ciphertext_store: &C, ) -> Result>>>, C::GetCiphertextError> { let mut acc: HashMap>>> = HashMap::new(); @@ -73,7 +80,9 @@ impl> Event } } -impl> From for Event { +impl> From + for Event +{ fn from(key_op: KeyOp) -> Self { match key_op { KeyOp::Add(add) => Event::PrekeysExpanded(add), @@ -82,10 +91,10 @@ impl> From for } } -impl> From> - for Event +impl> + From> for Event { - fn from(op: MembershipOperation) -> Self { + fn from(op: MembershipOperation) -> Self { match op { MembershipOperation::Delegation(d) => Event::Delegated(d), MembershipOperation::Revocation(r) => Event::Revoked(r), @@ -93,10 +102,10 @@ impl> From> From> - for StaticEvent +impl> + From> for StaticEvent { - fn from(op: Event) -> Self { + fn from(op: Event) -> Self { match op { Event::Delegated(d) => StaticEvent::Delegated(Rc::unwrap_or_clone(d).map(Into::into)), Event::Revoked(r) => StaticEvent::Revoked(Rc::unwrap_or_clone(r).map(Into::into)), @@ -115,13 +124,17 @@ impl> From> Serialize for Event { +impl> Serialize + for Event +{ fn serialize(&self, serializer: Z) -> Result { StaticEvent::from(self.clone()).serialize(serializer) } } -impl> Clone for Event { +impl> Clone + for Event +{ fn clone(&self) -> Self { match self { Event::Delegated(d) => Event::Delegated(Rc::clone(d)), @@ -135,7 +148,9 @@ impl> Clone for Event } } -impl> Dupe for Event { +impl> Dupe + for Event +{ fn dupe(&self) -> Self { self.clone() } @@ -156,8 +171,11 @@ mod tests { agent::Agent, individual::{id::IndividualId, Individual}, }, - store::ciphertext::memory::MemoryCiphertextStore, + store::{ + ciphertext::memory::MemoryCiphertextStore, secret_key::memory::MemorySecretKeyStore, + }, }; + use rand::rngs::ThreadRng; use std::{cell::RefCell, collections::BTreeMap}; use test_utils::init_logging; use testresult::TestResult; @@ -201,7 +219,9 @@ mod tests { let hash2 = Digest::hash(&cgka_op_2); let hash3 = Digest::hash(&cgka_op_3); - let events: Vec> = vec![ + let events: Vec< + Event, [u8; 32], NoListener>, + > = vec![ Event::CgkaOperation(Rc::new(cgka_op_1)), Event::CgkaOperation(Rc::new(cgka_op_2)), Event::PrekeysExpanded(Rc::new( @@ -227,7 +247,7 @@ mod tests { let ciphertext1 = Rc::new(EncryptedContent::new( Siv::new(&SymmetricKey::generate(&mut csprng), &[4, 5, 6], doc_id1)?, vec![4, 5, 6], - [1u8; 32].into(), + ShareKey::from([1u8; 32]), hash1, [1u8; 32], [1u8; 32].into(), diff --git a/keyhive_core/src/invocation.rs b/keyhive_core/src/invocation.rs index fafd85e9..fd6282bf 100644 --- a/keyhive_core/src/invocation.rs +++ b/keyhive_core/src/invocation.rs @@ -3,6 +3,7 @@ use crate::{ crypto::{digest::Digest, signed::Signed, signer::async_signer::AsyncSigner}, listener::{membership::MembershipListener, no_listener::NoListener}, principal::group::delegation::{Delegation, StaticDelegation}, + store::secret_key::traits::ShareSecretStore, }; use derive_where::derive_where; use serde::{Deserialize, Serialize}; @@ -12,16 +13,22 @@ use std::rc::Rc; #[derive_where(Clone; T)] pub struct Invocation< S: AsyncSigner, + K: ShareSecretStore, C: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, T: Clone = C, > { pub(crate) invoke: T, - pub(crate) proof: Option>>>, + pub(crate) proof: Option>>>, } -impl, T: Clone + Serialize> Serialize - for Invocation +impl< + S: AsyncSigner, + K: ShareSecretStore, + C: ContentRef, + L: MembershipListener, + T: Clone + Serialize, + > Serialize for Invocation { fn serialize(&self, serializer: Z) -> Result { StaticInvocation::from(self.clone()).serialize(serializer) @@ -34,10 +41,15 @@ pub struct StaticInvocation { pub(crate) proof: Option>>>, } -impl, T: Clone> - From> for StaticInvocation +impl< + S: AsyncSigner, + K: ShareSecretStore, + C: ContentRef, + L: MembershipListener, + T: Clone, + > From> for StaticInvocation { - fn from(invocation: Invocation) -> Self { + fn from(invocation: Invocation) -> Self { let invoke = invocation.invoke; let proof = invocation .proof diff --git a/keyhive_core/src/keyhive.rs b/keyhive_core/src/keyhive.rs index f389c4be..17fd48bd 100644 --- a/keyhive_core/src/keyhive.rs +++ b/keyhive_core/src/keyhive.rs @@ -4,7 +4,10 @@ use crate::{ ability::Ability, access::Access, archive::Archive, - cgka::{error::CgkaError, operation::CgkaOperation}, + cgka::{ + beekem::DecryptTreeSecretError, error::CgkaError, operation::CgkaOperation, + secret_store::DecryptSecretError, TryCgkaFromArchiveError, + }, contact_card::ContactCard, content::reference::ContentRef, crypto::{ @@ -19,12 +22,12 @@ use crate::{ event::{static_event::StaticEvent, Event}, listener::{log::Log, membership::MembershipListener, no_listener::NoListener}, principal::{ - active::Active, + active::{Active, ExpandPrekeyError, GenerateActiveError, RotatePrekeyError}, agent::{id::AgentId, Agent}, document::{ id::DocumentId, AddMemberError, AddMemberUpdate, DecryptError, - DocCausalDecryptionError, Document, EncryptError, EncryptedContentWithUpdate, - GenerateDocError, MissingIndividualError, RevokeMemberUpdate, + DocCausalDecryptionError, DocFromGroupError, Document, EncryptedContentWithUpdate, + GenerateDocError, PcsUpdateErrorError, RevokeMemberUpdate, TryEncryptError, }, group::{ delegation::{Delegation, StaticDelegation}, @@ -48,9 +51,10 @@ use crate::{ ciphertext::{memory::MemoryCiphertextStore, CausalDecryptionState, CiphertextStore}, delegation::DelegationStore, revocation::RevocationStore, + secret_key::traits::ShareSecretStore, }, transact::{ - fork::Fork, + fork::ForkAsync, merge::{Merge, MergeAsync}, }, }; @@ -74,31 +78,32 @@ use tracing::instrument; #[derivative(PartialEq, Eq, Clone)] pub struct Keyhive< S: AsyncSigner + Clone, + K: ShareSecretStore, T: ContentRef = [u8; 32], P: for<'de> Deserialize<'de> = Vec, C: CiphertextStore = MemoryCiphertextStore, - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, R: rand::CryptoRng = rand::rngs::ThreadRng, > { /// The [`Active`] user agent. - active: Rc>>, + active: Rc>>, /// The [`Individual`]s that are known to this agent. individuals: HashMap>>, /// The [`Group`]s that are known to this agent. #[allow(clippy::type_complexity)] - groups: HashMap>>>, + groups: HashMap>>>, /// The [`Document`]s that are known to this agent. #[allow(clippy::type_complexity)] - docs: HashMap>>>, + docs: HashMap>>>, /// All applied [`Delegation`]s - delegations: DelegationStore, + delegations: DelegationStore, /// All applied [`Revocation`]s - revocations: RevocationStore, + revocations: RevocationStore, /// Obsever for [`Event`]s. Intended for running live updates. event_listener: L, @@ -107,6 +112,9 @@ pub struct Keyhive< #[derivative(PartialEq = "ignore")] ciphertext_store: C, + #[derivative(PartialEq = "ignore")] + share_secret_store: K, + /// Cryptographically secure (pseudo)random number generator. #[derivative(PartialEq = "ignore")] csprng: R, @@ -116,12 +124,13 @@ pub struct Keyhive< impl< S: AsyncSigner + Clone, + K: ShareSecretStore, T: ContentRef, P: for<'de> Deserialize<'de>, C: CiphertextStore, - L: MembershipListener, + L: MembershipListener, R: rand::CryptoRng + rand::RngCore, - > Keyhive + > Keyhive { #[instrument(skip_all)] pub fn id(&self) -> IndividualId { @@ -136,13 +145,15 @@ impl< #[instrument(skip_all)] pub async fn generate( signer: S, + share_secret_store: K, ciphertext_store: C, event_listener: L, - mut csprng: R, - ) -> Result { + csprng: R, + ) -> Result> { Ok(Self { active: Rc::new(RefCell::new( - Active::generate(signer, event_listener.clone(), &mut csprng).await?, + Active::generate(signer, share_secret_store.clone(), event_listener.clone()) + .await?, )), individuals: HashMap::from_iter([( Public.id().into(), @@ -152,6 +163,7 @@ impl< docs: HashMap::new(), delegations: DelegationStore::new(), revocations: RevocationStore::new(), + share_secret_store, ciphertext_store, event_listener, csprng, @@ -161,7 +173,7 @@ impl< /// The current [`Active`] Keyhive user. #[instrument(skip(self), fields(khid = %self.id()))] - pub fn active(&self) -> &Rc>> { + pub fn active(&self) -> &Rc>> { &self.active } @@ -178,13 +190,13 @@ impl< #[allow(clippy::type_complexity)] #[instrument(skip(self), fields(khid = %self.id()))] - pub fn groups(&self) -> &HashMap>>> { + pub fn groups(&self) -> &HashMap>>> { &self.groups } #[allow(clippy::type_complexity)] #[instrument(skip(self), fields(khid = %self.id()))] - pub fn documents(&self) -> &HashMap>>> { + pub fn documents(&self) -> &HashMap>>> { &self.docs } @@ -192,8 +204,8 @@ impl< #[instrument(skip_all, fields(khid = %self.id()))] pub async fn generate_group( &mut self, - coparents: Vec>, - ) -> Result>>, SigningError> { + coparents: Vec>, + ) -> Result>>, SigningError> { let g = Rc::new(RefCell::new( Group::generate( NonEmpty { @@ -217,9 +229,9 @@ impl< #[instrument(skip_all, fields(khid = %self.id()))] pub async fn generate_doc( &mut self, - coparents: Vec>, + coparents: Vec>, initial_content_heads: NonEmpty, - ) -> Result>>, GenerateDocError> { + ) -> Result>>, GenerateDocError> { for peer in coparents.iter() { if self.get_agent(peer.id()).is_none() { self.register_peer(peer.dupe()); @@ -236,6 +248,7 @@ impl< self.delegations.dupe(), self.revocations.dupe(), self.event_listener.clone(), + self.share_secret_store.clone(), &signer, &mut self.csprng, ) @@ -258,13 +271,8 @@ impl< #[allow(clippy::await_holding_refcell_ref)] // FIXME #[instrument(skip(self), fields(khid = %self.id()))] - pub async fn contact_card(&mut self) -> Result { - let rot_key_op = self - .active - .borrow_mut() - .generate_private_prekey(&mut self.csprng) - .await?; - + pub async fn contact_card(&mut self) -> Result> { + let rot_key_op = self.active.borrow_mut().generate_private_prekey().await?; Ok(ContactCard(KeyOp::Rotate(rot_key_op))) } @@ -291,20 +299,14 @@ impl< pub async fn rotate_prekey( &mut self, prekey: ShareKey, - ) -> Result>, SigningError> { - self.active - .borrow_mut() - .rotate_prekey(prekey, &mut self.csprng) - .await + ) -> Result>, RotatePrekeyError> { + self.active.borrow_mut().rotate_prekey(prekey).await } #[allow(clippy::await_holding_refcell_ref)] // FIXME #[instrument(skip(self), fields(khid = %self.id()))] - pub async fn expand_prekeys(&mut self) -> Result>, SigningError> { - self.active - .borrow_mut() - .expand_prekeys(&mut self.csprng) - .await + pub async fn expand_prekeys(&mut self) -> Result>, ExpandPrekeyError> { + self.active.borrow_mut().expand_prekeys().await } #[instrument(skip(self), fields(khid = %self.id()))] @@ -314,7 +316,7 @@ impl< } #[instrument(skip(self), fields(khid = %self.id()))] - pub fn register_peer(&mut self, peer: Peer) -> bool { + pub fn register_peer(&mut self, peer: Peer) -> bool { let id = peer.id(); if self.get_peer(id).is_some() { @@ -349,7 +351,10 @@ impl< } #[instrument(skip_all, fields(khid = %self.id()))] - pub async fn register_group(&mut self, root_delegation: Signed>) -> bool { + pub async fn register_group( + &mut self, + root_delegation: Signed>, + ) -> bool { if self .groups .contains_key(&GroupId(root_delegation.subject_id())) @@ -375,8 +380,8 @@ impl< #[instrument(level = "debug", skip(self), fields(khid = %self.id()))] pub fn get_membership_operation( &self, - digest: &Digest>, - ) -> Option> { + digest: &Digest>, + ) -> Option> { self.delegations .get(&digest.into()) .map(|d| d.dupe().into()) @@ -391,20 +396,29 @@ impl< #[allow(clippy::type_complexity)] pub async fn add_member( &mut self, - to_add: Agent, - resource: &mut Membered, + to_add: Agent, + resource: &mut Membered, can: Access, - other_relevant_docs: &[Rc>>], // TODO make this automatic - ) -> Result, AddMemberError> { - let signer = self.active.borrow().signer.clone(); + other_relevant_docs: &[Rc>>], // TODO make this automatic + ) -> Result, AddMemberError> { match resource { Membered::Group(group) => Ok(group .borrow_mut() - .add_member(to_add, can, &signer, other_relevant_docs) + .add_member( + to_add, + can, + &self.active.borrow().signer, + other_relevant_docs, + ) .await?), Membered::Document(doc) => { doc.borrow_mut() - .add_member(to_add, can, &signer, other_relevant_docs) + .add_member( + to_add, + can, + &self.active.borrow().signer, + other_relevant_docs, + ) .await } } @@ -416,8 +430,8 @@ impl< &mut self, to_revoke: Identifier, retain_all_other_members: bool, - resource: &mut Membered, - ) -> Result, RevokeMemberError> { + resource: &mut Membered, + ) -> Result, RevokeMemberError> { let mut relevant_docs = BTreeMap::new(); for (doc_id, Ability { doc, .. }) in self.reachable_docs() { relevant_docs.insert(doc_id, doc.borrow().content_heads.iter().cloned().collect()); @@ -438,15 +452,20 @@ impl< #[instrument(skip_all, fields(khid = %self.id(), doc_id = %doc.borrow().id(), content_ref))] pub async fn try_encrypt_content( &mut self, - doc: Rc>>, + doc: Rc>>, content_ref: &T, pred_refs: &Vec, content: &[u8], - ) -> Result, EncryptContentError> { - let signer = self.active.borrow().signer.clone(); + ) -> Result, EncryptContentError> { let result = doc .borrow_mut() - .try_encrypt_content(content_ref, content, pred_refs, &signer, &mut self.csprng) + .try_encrypt_content( + content_ref, + content, + pred_refs, + &self.active.borrow().signer, + &mut self.csprng, + ) .await?; if let Some(op) = &result.update_op { self.event_listener.on_cgka_op(&Rc::new(op.clone())).await; @@ -454,18 +473,18 @@ impl< Ok(result) } - pub fn try_decrypt_content( + pub async fn try_decrypt_content( &mut self, - doc: Rc>>, + doc: Rc>>, encrypted: &EncryptedContent, ) -> Result, DecryptError> { - doc.borrow_mut().try_decrypt_content(encrypted) + doc.borrow_mut().try_decrypt_content(encrypted).await } #[allow(clippy::await_holding_refcell_ref)] // FIXME pub async fn try_causal_decrypt_content( &mut self, - doc: Rc>>, + doc: Rc>>, encrypted: &EncryptedContent, ) -> Result, DocCausalDecryptionError> where @@ -481,22 +500,22 @@ impl< #[instrument(level = "debug", skip(self), fields(khid = %self.id()))] pub async fn force_pcs_update( &mut self, - doc: Rc>>, - ) -> Result, EncryptError> { + doc: Rc>>, + ) -> Result, PcsUpdateErrorError> { let signer = self.active.borrow().signer.clone(); doc.borrow_mut().pcs_update(&signer, &mut self.csprng).await } #[instrument(level = "debug", skip(self), fields(khid = %self.id()))] - pub fn reachable_docs(&self) -> BTreeMap> { + pub fn reachable_docs(&self) -> BTreeMap> { self.docs_reachable_by_agent(&self.active.dupe().into()) } #[instrument(level = "debug", skip_all, fields(khid = %self.id(), membered_id = %membered.membered_id()))] pub fn reachable_members( &self, - membered: Membered, - ) -> HashMap, Access)> { + membered: Membered, + ) -> HashMap, Access)> { match membered { Membered::Group(group) => group.borrow().transitive_members(), Membered::Document(doc) => doc.borrow().transitive_members(), @@ -506,9 +525,9 @@ impl< #[instrument(level = "debug", skip_all, fields(khid = %self.id(), agent_id = %agent.id()))] pub fn docs_reachable_by_agent( &self, - agent: &Agent, - ) -> BTreeMap> { - let mut caps: BTreeMap> = BTreeMap::new(); + agent: &Agent, + ) -> BTreeMap> { + let mut caps: BTreeMap> = BTreeMap::new(); // TODO will be very slow on large hives. Old code here: https://github.com/inkandswitch/keyhive/pull/111/files: for doc in self.docs.values() { @@ -523,8 +542,8 @@ impl< #[instrument(skip(self), fields(khid = %self.id()))] pub fn membered_reachable_by_agent( &self, - agent: &Agent, - ) -> HashMap, Access)> { + agent: &Agent, + ) -> HashMap, Access)> { let mut caps = HashMap::new(); for group in self.groups.values() { @@ -549,8 +568,8 @@ impl< #[instrument(skip_all, fields(khid = %self.id()))] pub fn events_for_agent( &self, - agent: &Agent, - ) -> Result>, Event>, CgkaError> { + agent: &Agent, + ) -> Result>, Event>, CgkaError> { let mut ops: HashMap<_, _> = self .membership_ops_for_agent(agent) .into_iter() @@ -559,13 +578,13 @@ impl< for key_ops in self.reachable_prekey_ops_for_agent(agent).values() { for key_op in key_ops.iter() { - let op = Event::::from(key_op.as_ref().dupe()); + let op = Event::::from(key_op.as_ref().dupe()); ops.insert(Digest::hash(&op), op); } } for cgka_op in self.cgka_ops_reachable_by_agent(agent)?.into_iter() { - let op = Event::::from(cgka_op); + let op = Event::::from(cgka_op); ops.insert(Digest::hash(&op), op); } @@ -575,7 +594,7 @@ impl< #[instrument(skip(self), fields(khid = %self.id()))] pub fn static_events_for_agent( &self, - agent: &Agent, + agent: &Agent, ) -> Result>, StaticEvent>, CgkaError> { Ok(self .events_for_agent(agent)? @@ -587,7 +606,7 @@ impl< #[instrument(skip(self), fields(khid = %self.id()))] pub fn cgka_ops_reachable_by_agent( &self, - agent: &Agent, + agent: &Agent, ) -> Result>>, CgkaError> { let mut ops = vec![]; for (_doc_id, ability) in self.docs_reachable_by_agent(agent) { @@ -617,15 +636,15 @@ impl< #[instrument(skip_all, fields(khid = %self.id()))] pub fn membership_ops_for_agent( &self, - agent: &Agent, - ) -> HashMap>, MembershipOperation> { + agent: &Agent, + ) -> HashMap>, MembershipOperation> { let mut ops = HashMap::new(); let mut visited_hashes = HashSet::new(); #[allow(clippy::type_complexity)] let mut heads: Vec<( - Digest>, - MembershipOperation, + Digest>, + MembershipOperation, )> = vec![]; for (mem_rc, _max_acces) in self.membered_reachable_by_agent(agent).values() { @@ -673,7 +692,7 @@ impl< #[instrument(skip(self), fields(khid = %self.id()))] pub fn reachable_prekey_ops_for_agent( &self, - agent: &Agent, + agent: &Agent, ) -> HashMap>> { fn add_many_keys( map: &mut HashMap>>, @@ -769,18 +788,18 @@ impl< #[allow(clippy::type_complexity)] #[instrument(skip(self), fields(khid = %self.id()))] - pub fn get_group(&self, id: GroupId) -> Option<&Rc>>> { + pub fn get_group(&self, id: GroupId) -> Option<&Rc>>> { self.groups.get(&id) } #[allow(clippy::type_complexity)] #[instrument(skip(self), fields(khid = %self.id()))] - pub fn get_document(&self, id: DocumentId) -> Option<&Rc>>> { + pub fn get_document(&self, id: DocumentId) -> Option<&Rc>>> { self.docs.get(&id) } #[instrument(skip(self), fields(khid = %self.id()))] - pub fn get_peer(&self, id: Identifier) -> Option> { + pub fn get_peer(&self, id: Identifier) -> Option> { let indie_id = IndividualId(id); if let Some(doc) = self.docs.get(&DocumentId(id)) { @@ -799,7 +818,7 @@ impl< } #[instrument(skip(self), fields(khid = %self.id()))] - pub fn get_agent(&self, id: Identifier) -> Option> { + pub fn get_agent(&self, id: Identifier) -> Option> { let indie_id = id.into(); if indie_id == self.active.borrow().id() { @@ -865,7 +884,7 @@ impl< pub async fn receive_delegation( &mut self, static_dlg: &Signed>, - ) -> Result<(), ReceieveStaticDelegationError> { + ) -> Result<(), ReceieveStaticDelegationError> { if self .delegations .contains_key(&Digest::hash(static_dlg).into()) @@ -877,7 +896,7 @@ impl< // FIXME add a Verified newtype wapper static_dlg.try_verify()?; - let proof: Option>>> = static_dlg + let proof: Option>>> = static_dlg .payload() .proof .map(|proof_hash| { @@ -887,7 +906,7 @@ impl< .transpose()?; let delegate_id = static_dlg.payload().delegate; - let delegate: Agent = self + let delegate: Agent = self .get_agent(delegate_id) .ok_or(ReceieveStaticDelegationError::UnknownAgent(delegate_id))?; @@ -898,7 +917,7 @@ impl< let revs = self.revocations.borrow(); let resolved_rev = revs.get(&rev_hash).ok_or(MissingDependency(rev_hash))?; acc.push(resolved_rev.dupe()); - Ok::<_, ReceieveStaticDelegationError>(acc) + Ok::<_, ReceieveStaticDelegationError>(acc) }, )?; @@ -939,7 +958,13 @@ impl< .get(&subject_id.into()) .and_then(|content_heads| NonEmpty::collect(content_heads.iter().cloned())) { - let doc = Document::from_group(group, &self.active.borrow(), content_heads)?; + let doc = Document::from_group( + group, + &self.active.borrow(), + self.share_secret_store.clone(), + content_heads, + ) + .await?; self.docs.insert(doc.doc_id(), Rc::new(RefCell::new(doc))); } else { self.groups @@ -958,7 +983,7 @@ impl< pub async fn receive_revocation( &mut self, static_rev: &Signed>, - ) -> Result<(), ReceieveStaticDelegationError> { + ) -> Result<(), ReceieveStaticDelegationError> { if self .revocations .borrow() @@ -971,12 +996,12 @@ impl< static_rev.try_verify()?; let revoke_hash = static_rev.payload.revoke.into(); - let revoke: Rc>> = self + let revoke: Rc>> = self .delegations .get(&revoke_hash) .ok_or(MissingDependency(revoke_hash))?; - let proof: Option>>> = static_rev + let proof: Option>>> = static_rev .payload() .proof .map(|proof_hash| { @@ -1041,7 +1066,7 @@ impl< pub async fn receive_static_event( &mut self, static_event: StaticEvent, - ) -> Result<(), ReceiveStaticEventError> { + ) -> Result<(), ReceiveStaticEventError> { match static_event { StaticEvent::PrekeysExpanded(add_op) => { self.receive_prekey_op(&Rc::new(*add_op).into())? @@ -1060,7 +1085,7 @@ impl< pub async fn receive_membership_op( &mut self, static_op: &StaticMembershipOperation, - ) -> Result<(), ReceieveStaticDelegationError> { + ) -> Result<(), ReceieveStaticDelegationError> { match static_op { StaticMembershipOperation::Delegation(d) => self.receive_delegation(d).await?, StaticMembershipOperation::Revocation(r) => self.receive_revocation(r).await?, @@ -1072,7 +1097,7 @@ impl< pub async fn receive_cgka_op( &mut self, signed_op: Signed, - ) -> Result<(), ReceiveCgkaOpError> { + ) -> Result<(), ReceiveCgkaOpError> { signed_op.try_verify()?; let doc_id = signed_op.payload.doc_id(); @@ -1082,30 +1107,27 @@ impl< .ok_or(ReceiveCgkaOpError::UnknownDocument(*doc_id))?; let signed_op = Rc::new(signed_op); - if let CgkaOperation::Add { added_id, pk, .. } = signed_op.payload { + if let CgkaOperation::Add { added_id, .. } = signed_op.payload { let active_id = self.active.borrow().id(); if active_id == added_id { - tracing::info!("one of us!"); - let sk = { - let active = self.active.borrow(); - *active - .prekey_pairs - .get(&pk) - .ok_or(ReceiveCgkaOpError::UnknownInvitePrekey(pk))? - }; doc.borrow_mut() - .merge_cgka_invite_op(signed_op.clone(), &sk)?; + .merge_cgka_invite_op(signed_op.dupe()) + .await?; self.event_listener.on_cgka_op(&signed_op).await; return Ok(()); } else if Public.individual().id() == added_id { - let sk = Public.share_secret_key(); + self.share_secret_store + .import_secret_key(Public.share_secret_key()) + .await + .map_err(ReceiveCgkaOpError::ImportKeyError)?; doc.borrow_mut() - .merge_cgka_invite_op(signed_op.clone(), &sk)?; + .merge_cgka_invite_op(signed_op.clone()) + .await?; self.event_listener.on_cgka_op(&signed_op).await; return Ok(()); } } - doc.borrow_mut().merge_cgka_op(signed_op.clone())?; + doc.borrow_mut().merge_cgka_op(signed_op.clone()).await?; self.event_listener.on_cgka_op(&signed_op).await; Ok(()) } @@ -1119,8 +1141,8 @@ impl< pub async fn promote_individual_to_group( &mut self, individual: Rc>, - head: Rc>>, - ) -> Rc>> { + head: Rc>>, + ) -> Rc>> { let indie = individual.borrow().clone(); let group = Rc::new(RefCell::new( Group::from_individual( @@ -1186,7 +1208,7 @@ impl< pub fn into_archive(&self) -> Archive { Archive { active: self.active.borrow().into_archive(), - topsorted_ops: MembershipOperation::::topsort( + topsorted_ops: MembershipOperation::::topsort( &self.delegations.borrow(), &self.revocations.borrow(), ) @@ -1212,21 +1234,23 @@ impl< } #[instrument(skip_all, fields(archive_id = %archive.id()))] - pub fn try_from_archive( + pub async fn try_from_archive( archive: &Archive, signer: S, + share_secret_store: K, ciphertext_store: C, listener: L, csprng: R, - ) -> Result> { + ) -> Result> { let active = Rc::new(RefCell::new(Active::from_archive( &archive.active, + share_secret_store.clone(), signer, listener.clone(), ))); - let delegations: DelegationStore = DelegationStore::new(); - let revocations: RevocationStore = RevocationStore::new(); + let delegations: DelegationStore = DelegationStore::new(); + let revocations: RevocationStore = RevocationStore::new(); let mut individuals = HashMap::new(); for (k, v) in archive.individuals.iter() { @@ -1237,7 +1261,7 @@ impl< for (group_id, group_archive) in archive.groups.iter() { groups.insert( *group_id, - Rc::new(RefCell::new(Group::::dummy_from_archive( + Rc::new(RefCell::new(Group::::dummy_from_archive( group_archive.clone(), delegations.dupe(), revocations.dupe(), @@ -1250,19 +1274,23 @@ impl< for (doc_id, doc_archive) in archive.docs.iter() { docs.insert( *doc_id, - Rc::new(RefCell::new(Document::::dummy_from_archive( - doc_archive.clone(), - delegations.dupe(), - revocations.dupe(), - listener.clone(), - )?)), + Rc::new(RefCell::new( + Document::::dummy_from_archive( + doc_archive.clone(), + delegations.dupe(), + revocations.dupe(), + share_secret_store.clone(), + listener.clone(), + ) + .await?, + )), ); } for (digest, static_op) in archive.topsorted_ops.iter() { match static_op { StaticMembershipOperation::Delegation(sd) => { - let proof: Option>>> = sd + let proof: Option>>> = sd .payload .proof .map(|proof_digest| { @@ -1274,7 +1302,7 @@ impl< let mut after_revocations = vec![]; for rev_digest in sd.payload.after_revocations.iter() { - let r: Rc>> = revocations + let r: Rc>> = revocations .borrow() .get(&rev_digest.into()) .ok_or(TryFromArchiveError::MissingRevocation(rev_digest.into()))? @@ -1284,7 +1312,7 @@ impl< } let id = sd.payload.delegate; - let delegate: Agent = if id == archive.active.individual.id().into() { + let delegate: Agent = if id == archive.active.id().into() { active.dupe().into() } else { individuals @@ -1343,19 +1371,24 @@ impl< } #[allow(clippy::type_complexity)] - fn reify_ops>( - group: &mut Group, - dlg_store: DelegationStore, - rev_store: RevocationStore, + fn reify_ops< + Z: AsyncSigner, + V: ShareSecretStore, + U: ContentRef, + M: MembershipListener, + >( + group: &mut Group, + dlg_store: DelegationStore, + rev_store: RevocationStore, dlg_head_hashes: &HashSet>>>, rev_head_hashes: &HashSet>>>, - members: HashMap>>>>, - ) -> Result<(), TryFromArchiveError> { + members: HashMap>>>>, + ) -> Result<(), TryFromArchiveError> { let read_dlgs = dlg_store.borrow(); let read_revs = rev_store.borrow(); for dlg_hash in dlg_head_hashes.iter() { - let actual_dlg: Rc>> = read_dlgs + let actual_dlg: Rc>> = read_dlgs .get(&dlg_hash.into()) .ok_or(TryFromArchiveError::MissingDelegation(dlg_hash.into()))? .dupe(); @@ -1437,6 +1470,7 @@ impl< delegations, revocations, csprng, + share_secret_store, ciphertext_store, event_listener: listener, _plaintext_phantom: PhantomData, @@ -1448,15 +1482,15 @@ impl< pub async fn ingest_archive( &mut self, archive: Archive, - ) -> Result<(), ReceiveStaticEventError> { - self.active - .borrow_mut() - .prekey_pairs - .extend(archive.active.prekey_pairs); - self.active - .borrow_mut() - .individual - .merge(archive.active.individual); + ) -> Result<(), ReceiveStaticEventError> { + for share_key in archive.active.prekeys.iter() { + self.share_secret_store + .get_secret_key(share_key) + .await + .expect("FIXME") + .ok_or(ReceiveCgkaOpError::UnknownInvitePrekey(*share_key))?; + } + self.active.borrow_mut().individual.merge(archive.active); for (id, indie) in archive.individuals { if let Some(our_indie) = self.individuals.get_mut(&id) { our_indie.merge(indie); @@ -1486,7 +1520,7 @@ impl< pub async fn ingest_unsorted_static_events( &mut self, events: Vec>, - ) -> Result<(), ReceiveStaticEventError> { + ) -> Result<(), ReceiveStaticEventError> { let mut epoch = events; loop { @@ -1519,8 +1553,8 @@ impl< #[instrument(level = "trace", skip_all, fields(khid = %self.id()))] pub async fn ingest_event_table( &mut self, - events: HashMap>, Event>, - ) -> Result<(), ReceiveStaticEventError> { + events: HashMap>, Event>, + ) -> Result<(), ReceiveStaticEventError> { self.ingest_unsorted_static_events( events.values().cloned().map(Into::into).collect::>(), ) @@ -1530,12 +1564,13 @@ impl< impl< S: AsyncSigner + Clone, + K: ShareSecretStore + Debug, T: ContentRef + Debug, P: for<'de> Deserialize<'de>, C: CiphertextStore, - L: MembershipListener, + L: MembershipListener, R: rand::CryptoRng + rand::RngCore, - > Debug for Keyhive + > Debug for Keyhive { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { f.debug_struct("Keyhive") @@ -1545,6 +1580,7 @@ impl< .field("docs", &self.docs) .field("delegations", &self.delegations) .field("revocations", &self.revocations) + .field("share_secret_store", &"") .field("ciphertext_store", &"") .field("csprng", &"") .finish() @@ -1553,36 +1589,67 @@ impl< impl< S: AsyncSigner + Clone, + K: ShareSecretStore, T: ContentRef + Clone, P: for<'de> Deserialize<'de> + Clone, C: CiphertextStore + Clone, // FIXME make the default Rc> - L: MembershipListener, + L: MembershipListener, R: rand::CryptoRng + rand::RngCore + Clone, - > Fork for Keyhive + > ForkAsync for Keyhive { - type Forked = Keyhive, R>; + type AsyncForked = Keyhive, R>; - fn fork(&self) -> Self::Forked { + async fn fork_async(&self) -> Self::AsyncForked { // TODO this is probably fairly slow, and due to the logger type changing Keyhive::try_from_archive( &self.into_archive(), self.active.borrow().signer.clone(), + self.share_secret_store.clone(), self.ciphertext_store.clone(), Log::new(), self.csprng.clone(), ) - .expect("local round trip to work") + .await + .expect("local round trip to work") // FIXME } } impl< S: AsyncSigner + Clone, + K: ShareSecretStore, + T: ContentRef + Clone, + P: for<'de> Deserialize<'de> + Clone, + C: CiphertextStore + Clone, // FIXME make the default Rc> + L: MembershipListener, + R: rand::CryptoRng + rand::RngCore + Clone, + > ForkAsync for Rc>> +{ + type AsyncForked = Keyhive, R>; + + async fn fork_async(&self) -> Self::AsyncForked { + // TODO this is probably fairly slow, and due to the logger type changing + Keyhive::try_from_archive( + &self.borrow().into_archive(), + self.borrow().active.borrow().signer.clone(), + self.borrow().share_secret_store.clone(), + self.borrow().ciphertext_store.clone(), + Log::new(), + self.borrow().csprng.clone(), + ) + .await + .expect("local round trip to work") // FIXME + } +} + +impl< + S: AsyncSigner + Clone, + K: ShareSecretStore, T: ContentRef + Clone, P: for<'de> Deserialize<'de> + Clone, C: CiphertextStore + Clone, - L: MembershipListener, + L: MembershipListener, R: rand::CryptoRng + rand::RngCore + Clone, - > MergeAsync for Rc>> + > MergeAsync for Rc>> { #[allow(clippy::await_holding_refcell_ref)] // FIXME async fn merge_async(&self, mut fork: Self::AsyncForked) { @@ -1626,12 +1693,13 @@ impl< impl< S: AsyncSigner + Clone, + K: ShareSecretStore, T: ContentRef, P: for<'de> Deserialize<'de>, C: CiphertextStore, - L: MembershipListener, + L: MembershipListener, R: rand::CryptoRng + rand::RngCore, - > Verifiable for Keyhive + > Verifiable for Keyhive { fn verifying_key(&self) -> ed25519_dalek::VerifyingKey { self.active.borrow().verifying_key() @@ -1640,36 +1708,50 @@ impl< impl< S: AsyncSigner + Clone, + K: ShareSecretStore, T: ContentRef, P: for<'de> Deserialize<'de>, C: CiphertextStore, - L: MembershipListener, + L: MembershipListener, R: rand::CryptoRng + rand::RngCore, - > From<&Keyhive> for Agent + > From<&Keyhive> for Agent { - fn from(context: &Keyhive) -> Self { + fn from(context: &Keyhive) -> Self { context.active.dupe().into() } } #[derive(Error)] -#[derive_where(Debug; T)] -pub enum ReceiveStaticEventError> { +pub enum ReceiveStaticEventError< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef, + L: MembershipListener, +> { #[error(transparent)] ReceivePrekeyOpError(#[from] ReceivePrekeyOpError), #[error(transparent)] - ReceiveCgkaOpError(#[from] ReceiveCgkaOpError), + ReceiveCgkaOpError(#[from] ReceiveCgkaOpError), #[error(transparent)] - ReceieveStaticMembershipError(#[from] ReceieveStaticDelegationError), + ReceieveStaticMembershipError(#[from] ReceieveStaticDelegationError), } -impl ReceiveStaticEventError -where - S: AsyncSigner, - T: ContentRef, - L: MembershipListener, +impl> Debug + for ReceiveStaticEventError +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Self::ReceivePrekeyOpError(e) => e.fmt(f), + Self::ReceiveCgkaOpError(e) => e.fmt(f), + Self::ReceieveStaticMembershipError(e) => e.fmt(f), + } + } +} + +impl> + ReceiveStaticEventError { pub fn is_missing_dependency(&self) -> bool { match self { @@ -1684,17 +1766,18 @@ where #[derive_where(Debug; T)] pub enum ReceieveStaticDelegationError< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { #[error(transparent)] VerificationError(#[from] VerificationError), #[error("Missing proof: {0}")] - MissingProof(#[from] MissingDependency>>>), + MissingProof(#[from] MissingDependency>>>), #[error("Missing revocation dependency: {0}")] - MissingRevocationDependency(#[from] MissingDependency>>>), + MissingRevocationDependency(#[from] MissingDependency>>>), #[error("Cgka init error: {0}")] CgkaInitError(#[from] CgkaError), @@ -1704,13 +1787,17 @@ pub enum ReceieveStaticDelegationError< #[error("Missing agent: {0}")] UnknownAgent(Identifier), + + #[error(transparent)] + DocFromGroupError(#[from] DocFromGroupError), } -impl ReceieveStaticDelegationError +impl ReceieveStaticDelegationError where S: AsyncSigner, + K: ShareSecretStore, T: ContentRef, - L: MembershipListener, + L: MembershipListener, { pub fn is_missing_dependency(&self) -> bool { match self { @@ -1720,18 +1807,24 @@ where Self::GroupReceiveError(_) => false, Self::UnknownAgent(_) => true, Self::VerificationError(_) => false, + Self::DocFromGroupError(_) => false, } } } -#[derive(Clone, PartialEq, Eq, Error)] -#[derive_where(Debug)] -pub enum TryFromArchiveError> { +#[derive(Error)] +#[derive_where(Debug; T)] +pub enum TryFromArchiveError< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef, + L: MembershipListener, +> { #[error("Missing delegation: {0}")] - MissingDelegation(#[from] Digest>>), + MissingDelegation(#[from] Digest>>), #[error("Missing revocation: {0}")] - MissingRevocation(#[from] Digest>>), + MissingRevocation(#[from] Digest>>), #[error("Missing individual: {0}")] MissingIndividual(Box), @@ -1744,10 +1837,13 @@ pub enum TryFromArchiveError), + + #[error(transparent)] + DocFromArchiveError(#[from] TryCgkaFromArchiveError), } -#[derive(Debug, Error)] -pub enum ReceiveCgkaOpError { +#[derive(Error)] +pub enum ReceiveCgkaOpError { #[error(transparent)] CgkaError(#[from] CgkaError), @@ -1759,31 +1855,49 @@ pub enum ReceiveCgkaOpError { #[error("Unknown invite prekey for received CGKA add op: {0}")] UnknownInvitePrekey(ShareKey), + + #[error(transparent)] + DecryptSecretError(#[from] DecryptSecretError), + + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), + + #[error("Error importing secret key: {0}")] + ImportKeyError(K::ImportKeyError), +} + +impl Debug for ReceiveCgkaOpError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Self::CgkaError(e) => e.fmt(f), + Self::VerificationError(e) => e.fmt(f), + Self::UnknownDocument(e) => e.fmt(f), + Self::UnknownInvitePrekey(e) => e.fmt(f), + Self::DecryptSecretError(e) => e.fmt(f), + Self::DecryptTreeSecretError(e) => e.fmt(f), + Self::ImportKeyError(e) => e.fmt(f), + } + } } -impl ReceiveCgkaOpError { +impl ReceiveCgkaOpError { pub fn is_missing_dependency(&self) -> bool { match self { Self::CgkaError(e) => e.is_missing_dependency(), Self::VerificationError(_) => false, Self::UnknownDocument(_) => false, Self::UnknownInvitePrekey(_) => false, + Self::DecryptSecretError(_) => false, + Self::DecryptTreeSecretError(_) => false, + Self::ImportKeyError(_) => false, } } } -impl> From - for TryFromArchiveError -{ - fn from(e: MissingIndividualError) -> Self { - TryFromArchiveError::MissingIndividual(e.0) - } -} - #[derive(Debug, Error)] -pub enum EncryptContentError { +pub enum EncryptContentError { #[error(transparent)] - EncryptError(#[from] EncryptError), + EncryptError(#[from] TryEncryptError), #[error("Error signing Cgka op: {0}")] SignCgkaOpError(SigningError), @@ -1792,17 +1906,18 @@ pub enum EncryptContentError { #[derive(Debug, Error)] pub enum ReceiveEventError< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { #[error(transparent)] - ReceieveStaticDelegationError(#[from] ReceieveStaticDelegationError), + ReceieveStaticDelegationError(#[from] ReceieveStaticDelegationError), #[error(transparent)] ReceivePrekeyOpError(#[from] ReceivePrekeyOpError), #[error(transparent)] - ReceiveCgkaOpError(#[from] ReceiveCgkaOpError), + ReceiveCgkaOpError(#[from] ReceiveCgkaOpError), } #[cfg(test)] @@ -1810,14 +1925,16 @@ mod tests { use super::*; use crate::{ access::Access, crypto::signer::memory::MemorySigner, principal::public::Public, - transact::transact_async, + store::secret_key::memory::MemorySecretKeyStore, transact::transact_async, }; use nonempty::nonempty; use pretty_assertions::assert_eq; + use rand::rngs::ThreadRng; use testresult::TestResult; async fn make_keyhive() -> Keyhive< MemorySigner, + MemorySecretKeyStore, [u8; 32], Vec, Rc>>>, @@ -1827,6 +1944,7 @@ mod tests { let store: MemoryCiphertextStore<[u8; 32], Vec> = MemoryCiphertextStore::new(); Keyhive::generate( sk, + MemorySecretKeyStore::new(rand::thread_rng()), Rc::new(RefCell::new(store)), NoListener, rand::thread_rng(), @@ -1845,8 +1963,14 @@ mod tests { let store = Rc::new(RefCell::new( MemoryCiphertextStore::<[u8; 32], String>::new(), )); - let mut hive = - Keyhive::generate(sk.clone(), store.clone(), NoListener, rand::thread_rng()).await?; + let mut hive = Keyhive::generate( + sk.clone(), + MemorySecretKeyStore::new(rand::thread_rng()), + store.clone(), + NoListener, + rand::thread_rng(), + ) + .await?; let indie_sk = MemorySigner::generate(&mut csprng); let indie = Rc::new(RefCell::new( @@ -1858,7 +1982,7 @@ mod tests { hive.generate_doc(vec![indie.into()], nonempty![[1u8; 32], [2u8; 32]]) .await?; - assert!(!hive.active.borrow().prekey_pairs.is_empty()); + assert!(!hive.share_secret_store.keys.is_empty()); assert_eq!(hive.individuals.len(), 2); assert_eq!(hive.groups.len(), 1); assert_eq!(hive.docs.len(), 1); @@ -1873,8 +1997,16 @@ mod tests { assert_eq!(archive.docs.len(), 1); assert_eq!(archive.topsorted_ops.len(), 4); - let hive_from_archive = - Keyhive::try_from_archive(&archive, sk, store, NoListener, rand::thread_rng()).unwrap(); + let hive_from_archive = Keyhive::try_from_archive( + &archive, + sk, + hive.share_secret_store.clone(), + store, + NoListener, + rand::thread_rng(), + ) + .await + .unwrap(); assert_eq!(hive, hive_from_archive); @@ -2156,14 +2288,15 @@ mod tests { bob.ingest_event_table(events).await.unwrap(); } - #[allow(clippy::await_holding_refcell_ref)] // FIXME + #[allow(clippy::await_holding_refcell_ref)] #[tokio::test] async fn test_async_transaction() -> TestResult { test_utils::init_logging(); let sk = MemorySigner::generate(&mut rand::thread_rng()); - let hive = Keyhive::<_, [u8; 32], Vec, _, NoListener, _>::generate( + let hive = Keyhive::<_, _, [u8; 32], Vec, _, NoListener, _>::generate( sk, + MemorySecretKeyStore::new(rand::thread_rng()), Rc::new(RefCell::new(MemoryCiphertextStore::new())), NoListener, rand::rngs::OsRng, @@ -2172,14 +2305,15 @@ mod tests { let trunk = Rc::new(RefCell::new(hive)); - let alice: Peer = Rc::new(RefCell::new( - Individual::generate( - &MemorySigner::generate(&mut rand::rngs::OsRng), - &mut rand::rngs::OsRng, - ) - .await?, - )) - .into(); + let alice: Peer, [u8; 32], NoListener> = + Rc::new(RefCell::new( + Individual::generate( + &MemorySigner::generate(&mut rand::rngs::OsRng), + &mut rand::rngs::OsRng, + ) + .await?, + )) + .into(); trunk .borrow_mut() @@ -2191,14 +2325,22 @@ mod tests { .generate_group(vec![alice.dupe()]) .await?; - assert_eq!(trunk.borrow().active.borrow().prekey_pairs.len(), 7); + assert_eq!(trunk.borrow().active.borrow().secret_store.len(), 7); assert_eq!(trunk.borrow().delegations.borrow().len(), 4); assert_eq!(trunk.borrow().groups.len(), 1); assert_eq!(trunk.borrow().docs.len(), 1); let tx = transact_async( &trunk, - |mut fork: Keyhive<_, _, _, _, Log<_, [u8; 32]>, _>| async move { + |mut fork: Keyhive< + MemorySigner, + MemorySecretKeyStore, + _, + _, + _, + Log<_, _, [u8; 32]>, + _, + >| async move { // Depending on when the async runs let init_dlg_count = fork.delegations.borrow().len(); assert!(init_dlg_count >= 4); @@ -2212,11 +2354,16 @@ mod tests { let init_group_count = fork.groups.len(); assert_eq!(init_group_count, 1); - assert_eq!(fork.active.borrow().prekey_pairs.len(), 7); + assert_eq!(fork.active.borrow().secret_store.len(), 7); fork.expand_prekeys().await.unwrap(); // 1 event (prekey) - assert_eq!(fork.active.borrow().prekey_pairs.len(), 8); - - let bob: Peer> = Rc::new(RefCell::new( + assert_eq!(fork.active.borrow().secret_store.len(), 8); + + let bob: Peer< + MemorySigner, + MemorySecretKeyStore, + [u8; 32], + Log, + > = Rc::new(RefCell::new( Individual::generate( &MemorySigner::generate(&mut rand::rngs::OsRng), &mut rand::rngs::OsRng, @@ -2276,11 +2423,11 @@ mod tests { assert!(!trunk.borrow().docs.is_empty()); assert!(trunk.borrow().docs.len() <= 3); - // FIXME add transact right on Keyhive taht aslo dispatches new events + // FIXME add transact right on Keyhive that aslo dispatches new events let () = tx?; // tx is done, so should be all caught up. Counts are now certain. - assert_eq!(trunk.borrow().active.borrow().prekey_pairs.len(), 8); + assert_eq!(trunk.borrow().active.borrow().secret_store.keys.len(), 8); assert_eq!(trunk.borrow().docs.len(), 3); assert_eq!(trunk.borrow().groups.len(), 4); diff --git a/keyhive_core/src/listener/deque.rs b/keyhive_core/src/listener/deque.rs index afc923a3..5824c4e7 100644 --- a/keyhive_core/src/listener/deque.rs +++ b/keyhive_core/src/listener/deque.rs @@ -8,6 +8,7 @@ use crate::{ group::{delegation::Delegation, revocation::Revocation}, individual::op::{add_key::AddKeyOp, rotate_key::RotateKeyOp}, }, + store::secret_key::traits::ShareSecretStore, }; use derive_more::{From, Into}; use dupe::Dupe; @@ -20,28 +21,28 @@ use std::{ use tracing::instrument; #[derive(Debug, Default, PartialEq, Eq, From, Into)] -pub struct Deque( - #[allow(clippy::type_complexity)] pub Rc>>>>, +pub struct Deque( + #[allow(clippy::type_complexity)] pub Rc>>>>, ); -impl Deque { +impl Deque { pub fn new() -> Self { Self(Rc::new(RefCell::new(VecDeque::new()))) } - pub fn push(&self, event: Event) { + pub fn push(&self, event: Event) { let rc = self.0.dupe(); let mut deq = (*rc).borrow_mut(); deq.push_back(event) } - pub fn pop_latest(&self) -> Option> { + pub fn pop_latest(&self) -> Option> { let rc = self.0.dupe(); let mut deq = (*rc).borrow_mut(); deq.pop_front() } - pub fn pop_earliest(&self) -> Option> { + pub fn pop_earliest(&self) -> Option> { let rc = self.0.dupe(); let mut deq = (*rc).borrow_mut(); deq.pop_back() @@ -58,28 +59,28 @@ impl Deque { } } -impl Clone for Deque { +impl Clone for Deque { fn clone(&self) -> Self { Self(self.0.dupe()) } } -impl Dupe for Deque { +impl Dupe for Deque { fn dupe(&self) -> Self { self.clone() } } -impl Hash for Deque +impl Hash for Deque where - Event>: Hash, + Event>: Hash, { fn hash(&self, state: &mut H) { self.0.borrow().hash(state) } } -impl PrekeyListener for Deque { +impl PrekeyListener for Deque { #[instrument(skip(self))] async fn on_prekeys_expanded(&self, new_prekey: &Rc>) { self.push(Event::PrekeysExpanded(new_prekey.dupe())) @@ -91,19 +92,21 @@ impl PrekeyListener for Deque { } } -impl MembershipListener for Deque { +impl MembershipListener + for Deque +{ #[instrument(skip(self))] - async fn on_delegation(&self, data: &Rc>>) { + async fn on_delegation(&self, data: &Rc>>) { self.push(Event::Delegated(data.dupe())) } #[instrument(skip(self))] - async fn on_revocation(&self, data: &Rc>>) { + async fn on_revocation(&self, data: &Rc>>) { self.push(Event::Revoked(data.dupe())) } } -impl CgkaListener for Deque { +impl CgkaListener for Deque { #[instrument(skip(self))] async fn on_cgka_op(&self, op: &Rc>) { self.push(Event::CgkaOperation(op.dupe())) diff --git a/keyhive_core/src/listener/log.rs b/keyhive_core/src/listener/log.rs index 9f5536cb..43b5a709 100644 --- a/keyhive_core/src/listener/log.rs +++ b/keyhive_core/src/listener/log.rs @@ -8,6 +8,7 @@ use crate::{ group::{delegation::Delegation, revocation::Revocation}, individual::op::{add_key::AddKeyOp, rotate_key::RotateKeyOp}, }, + store::secret_key::traits::ShareSecretStore, }; use derive_more::{From, Into}; use derive_where::derive_where; @@ -21,22 +22,22 @@ use tracing::instrument; #[derive(From, Into, PartialEq, Eq)] #[derive_where(Debug; T)] -pub struct Log( - #[allow(clippy::type_complexity)] pub Rc>>>>, +pub struct Log( + #[allow(clippy::type_complexity)] pub Rc>>>>, ); -impl Log { +impl Log { pub fn new() -> Self { Self(Rc::new(RefCell::new(vec![]))) } - pub fn push(&self, event: Event) { + pub fn push(&self, event: Event) { let rc = self.0.dupe(); let mut deq = (*rc).borrow_mut(); deq.push(event) } - pub fn pop(&self) -> Option> { + pub fn pop(&self) -> Option> { let rc = self.0.dupe(); let mut deq = (*rc).borrow_mut(); deq.pop() @@ -57,34 +58,34 @@ impl Log { } } -impl Clone for Log { +impl Clone for Log { fn clone(&self) -> Self { Self(self.0.dupe()) } } -impl Dupe for Log { +impl Dupe for Log { fn dupe(&self) -> Self { self.clone() } } -impl Hash for Log +impl Hash for Log where - Event>: Hash, + Event>: Hash, { fn hash(&self, state: &mut H) { self.0.borrow().hash(state) } } -impl Default for Log { +impl Default for Log { fn default() -> Self { Self::new() } } -impl PrekeyListener for Log { +impl PrekeyListener for Log { #[instrument(skip(self))] async fn on_prekeys_expanded(&self, new_prekey: &Rc>) { self.push(Event::PrekeysExpanded(new_prekey.dupe())) @@ -96,19 +97,21 @@ impl PrekeyListener for Log { } } -impl MembershipListener for Log { +impl MembershipListener + for Log +{ #[instrument(skip(self))] - async fn on_delegation(&self, data: &Rc>>) { + async fn on_delegation(&self, data: &Rc>>) { self.push(Event::Delegated(data.dupe())) } #[instrument(skip(self))] - async fn on_revocation(&self, data: &Rc>>) { + async fn on_revocation(&self, data: &Rc>>) { self.push(Event::Revoked(data.dupe())) } } -impl CgkaListener for Log { +impl CgkaListener for Log { #[instrument(skip(self))] async fn on_cgka_op(&self, data: &Rc>) { self.push(Event::CgkaOperation(data.dupe())) diff --git a/keyhive_core/src/listener/membership.rs b/keyhive_core/src/listener/membership.rs index cc746351..af3de699 100644 --- a/keyhive_core/src/listener/membership.rs +++ b/keyhive_core/src/listener/membership.rs @@ -5,6 +5,7 @@ use crate::{ content::reference::ContentRef, crypto::{signed::Signed, signer::async_signer::AsyncSigner}, principal::group::{delegation::Delegation, revocation::Revocation}, + store::secret_key::traits::ShareSecretStore, }; use std::rc::Rc; @@ -23,10 +24,12 @@ use std::rc::Rc; /// [`Group`]: crate::principal::group::Group /// [`Document`]: crate::principal::document::Document #[allow(async_fn_in_trait)] -pub trait MembershipListener: PrekeyListener + CgkaListener { +pub trait MembershipListener: + PrekeyListener + CgkaListener +{ /// React to new [`Delegation`]s. - async fn on_delegation(&self, data: &Rc>>); + async fn on_delegation(&self, data: &Rc>>); /// React to new [`Revocation`]s. - async fn on_revocation(&self, data: &Rc>>); + async fn on_revocation(&self, data: &Rc>>); } diff --git a/keyhive_core/src/listener/no_listener.rs b/keyhive_core/src/listener/no_listener.rs index b78acd12..46b56815 100644 --- a/keyhive_core/src/listener/no_listener.rs +++ b/keyhive_core/src/listener/no_listener.rs @@ -9,6 +9,7 @@ use crate::{ group::{delegation::Delegation, revocation::Revocation}, individual::op::{add_key::AddKeyOp, rotate_key::RotateKeyOp}, }, + store::secret_key::traits::ShareSecretStore, }; use derive_more::derive::Debug; use dupe::Dupe; @@ -26,9 +27,11 @@ impl PrekeyListener for NoListener { async fn on_prekey_rotated(&self, _e: &Rc>) {} } -impl MembershipListener for NoListener { - async fn on_delegation(&self, _data: &Rc>>) {} - async fn on_revocation(&self, _data: &Rc>>) {} +impl MembershipListener + for NoListener +{ + async fn on_delegation(&self, _data: &Rc>>) {} + async fn on_revocation(&self, _data: &Rc>>) {} } impl CgkaListener for NoListener { diff --git a/keyhive_core/src/principal/active.rs b/keyhive_core/src/principal/active.rs index 7148a668..b00c564c 100644 --- a/keyhive_core/src/principal/active.rs +++ b/keyhive_core/src/principal/active.rs @@ -1,8 +1,5 @@ //! The current user agent (which can sign and encrypt). -pub mod archive; - -use self::archive::ActiveArchive; use super::{ document::id::DocumentId, identifier::Identifier, @@ -17,41 +14,44 @@ use crate::{ access::Access, content::reference::ContentRef, crypto::{ - share_key::{ShareKey, ShareSecretKey}, + share_key::{AsyncSecretKey, ShareKey}, signed::{Signed, SigningError}, signer::async_signer::AsyncSigner, verifiable::Verifiable, }, - listener::{log::Log, no_listener::NoListener, prekey::PrekeyListener}, + listener::{ + log::Log, membership::MembershipListener, no_listener::NoListener, prekey::PrekeyListener, + }, principal::{ agent::id::AgentId, group::delegation::{Delegation, DelegationError}, membered::Membered, }, + store::secret_key::traits::ShareSecretStore, transact::{fork::Fork, merge::Merge}, }; use derivative::Derivative; use dupe::Dupe; use futures::prelude::*; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Debug, marker::PhantomData, rc::Rc}; +use std::{fmt::Debug, marker::PhantomData, rc::Rc}; use thiserror::Error; /// The current user agent (which can sign and encrypt). #[derive(Clone, Derivative, Serialize, Deserialize)] #[derivative(Debug, Hash, PartialEq)] -pub struct Active { +pub struct Active< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef = [u8; 32], + L: PrekeyListener = NoListener, +> { /// The signing key of the active agent. #[derivative(Debug = "ignore")] pub(crate) signer: S, - // TODO generalize to use e.g. KMS for X25519 secret keys - #[derivative( - Debug(format_with = "crate::util::debug::prekey_fmt"), - Hash(hash_with = "crate::util::hasher::keys"), - PartialEq(compare_with = "crate::util::partial_eq::prekey_partial_eq") - )] - pub(crate) prekey_pairs: BTreeMap, + #[derivative(Debug = "ignore", Hash = "ignore", PartialEq = "ignore")] + pub(crate) secret_store: K, /// The [`Individual`] representation (how others see this agent). pub(crate) individual: Individual, @@ -64,21 +64,23 @@ pub struct Active, } -impl Active { +impl Active { /// Generate a new active agent. /// /// # Arguments /// /// * `signer` - The signing key of the active agent. /// * `listener` - The listener for changes to this agent's prekeys. - /// * `csprng` - The cryptographically secure random number generator. - pub async fn generate( + pub async fn generate( signer: S, + mut secret_store: K, listener: L, - csprng: &mut R, - ) -> Result { - let init_sk = ShareSecretKey::generate(csprng); - let init_pk = init_sk.share_key(); + ) -> Result> { + let init_sk = secret_store + .generate_share_secret_key() + .await + .map_err(GenerateActiveError::GenerateSecretKeyError)?; + let init_pk = init_sk.to_share_key(); let init_op = Rc::new( signer .try_sign_async(AddKeyOp { share_key: init_pk }) @@ -87,21 +89,25 @@ impl Active { .into(); let mut prekey_state = PrekeyState::new(init_op); - let prekey_pairs = - (0..6).try_fold(BTreeMap::from_iter([(init_pk, init_sk)]), |mut acc, _| { - let sk = ShareSecretKey::generate(csprng); - let pk = sk.share_key(); - acc.insert(pk, sk); - Ok::<_, SigningError>(acc) - })?; + let mut local_store = vec![]; + for _ in 0..6 { + let sk = secret_store + .generate_share_secret_key() + .await + .map_err(GenerateActiveError::GenerateSecretKeyError)?; + + local_store.push(sk); + } let borrowed_signer = &signer; - let ops = stream::iter(prekey_pairs.keys().map(Ok::<_, SigningError>)) - .try_fold(vec![], |mut acc, pk| async move { + let ops = stream::iter(local_store.iter().map(|x| Ok::<_, SigningError>(x))) + .try_fold(vec![], |mut acc, sk| async move { acc.push( Rc::new( borrowed_signer - .try_sign_async(AddKeyOp { share_key: *pk }) + .try_sign_async(AddKeyOp { + share_key: sk.to_share_key(), + }) .await?, ) .into(), @@ -120,7 +126,7 @@ impl Active { prekeys: prekey_state.build(), prekey_state, }, - prekey_pairs, + secret_store, listener, signer, _phantom: PhantomData, @@ -143,13 +149,12 @@ impl Active { } /// Create a [`ShareKey`] that is not broadcast via the prekey state. - pub async fn generate_private_prekey( + pub async fn generate_private_prekey( &mut self, - csprng: &mut R, - ) -> Result>, SigningError> { + ) -> Result>, RotatePrekeyError> { let share_key = self.individual.pick_prekey(DocumentId(self.id().into())); // Hack - let contact_key = self.rotate_prekey(*share_key, csprng).await?; - self.rotate_prekey(contact_key.payload.new, csprng).await?; + let contact_key = self.rotate_prekey(*share_key).await?; + self.rotate_prekey(contact_key.payload.new).await?; Ok(contact_key) } @@ -160,13 +165,16 @@ impl Active { } /// Replace a particular prekey with a new one. - pub async fn rotate_prekey( + pub async fn rotate_prekey( &mut self, old_prekey: ShareKey, - csprng: &mut R, - ) -> Result>, SigningError> { - let new_secret = ShareSecretKey::generate(csprng); - let new_public = new_secret.share_key(); + ) -> Result>, RotatePrekeyError> { + let new_secret = self + .secret_store + .generate_share_secret_key() + .await + .map_err(RotatePrekeyError::GenerateShareSecretKeyError)?; + let new_public = new_secret.to_share_key(); let rot_op = Rc::new( self.try_sign_async(RotateKeyOp { @@ -176,8 +184,6 @@ impl Active { .await?, ); - self.prekey_pairs.insert(new_public, new_secret); - self.individual .prekey_state .insert_op(KeyOp::Rotate(rot_op.dupe())) @@ -191,12 +197,14 @@ impl Active { } /// Add a new prekey, expanding the number of currently available prekeys. - pub async fn expand_prekeys( - &mut self, - csprng: &mut R, - ) -> Result>, SigningError> { - let new_secret = ShareSecretKey::generate(csprng); - let new_public = new_secret.share_key(); + pub async fn expand_prekeys(&mut self) -> Result>, ExpandPrekeyError> { + let new_secret = self + .secret_store + .generate_share_secret_key() + .await + .map_err(ExpandPrekeyError::GenerateShareSecretKeyError)?; + + let new_public = new_secret.to_share_key(); let op = Rc::new( self.signer @@ -211,7 +219,6 @@ impl Active { .insert_op(KeyOp::Add(op.dupe())) .expect("the op we just signed to be valid"); self.individual.prekeys.insert(new_public); - self.prekey_pairs.insert(new_public, new_secret); self.listener.on_prekeys_expanded(&op).await; Ok(op) @@ -228,9 +235,12 @@ impl Active { /// Encrypt a payload for a member of some [`Group`] or [`Document`]. pub fn get_capability( &self, - subject: Membered, + subject: Membered, min: Access, - ) -> Option>>> { + ) -> Option>>> + where + L: MembershipListener, + { subject.get_capability(&self.id().into()).and_then(|cap| { if cap.payload().can >= min { Some(cap) @@ -241,22 +251,16 @@ impl Active { } /// Serialize for storage. - pub fn into_archive(&self) -> ActiveArchive { - ActiveArchive { - prekey_pairs: self.prekey_pairs.clone(), - individual: self.individual.clone(), - } + pub fn into_archive(&self) -> Individual { + self.individual.clone() } /// Deserialize from storage. - pub fn from_archive(archive: &ActiveArchive, signer: S, listener: L) -> Self { - tracing::trace!( - num_prekey_pairs = archive.prekey_pairs.len(), - "loaded from archive" - ); + pub fn from_archive(archive: &Individual, secret_store: K, signer: S, listener: L) -> Self { + tracing::trace!("loaded from archive"); Self { - prekey_pairs: archive.prekey_pairs.clone(), - individual: archive.individual.clone(), + individual: archive.clone(), + secret_store, signer, listener, _phantom: PhantomData, @@ -264,25 +268,31 @@ impl Active { } } -impl std::fmt::Display for Active { +impl std::fmt::Display + for Active +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.id(), f) } } -impl Verifiable for Active { +impl Verifiable + for Active +{ fn verifying_key(&self) -> ed25519_dalek::VerifyingKey { self.signer.verifying_key() } } -impl Fork for Active { - type Forked = Active>; +impl Fork + for Active +{ + type Forked = Active>; fn fork(&self) -> Self::Forked { Active { signer: self.signer.clone(), - prekey_pairs: self.prekey_pairs.clone(), + secret_store: self.secret_store.clone(), individual: self.individual.clone(), listener: Log::new(), _phantom: PhantomData, @@ -290,13 +300,23 @@ impl Fork for Active Merge for Active { +impl Merge + for Active +{ fn merge(&mut self, fork: Self::Forked) { - self.prekey_pairs.extend(fork.prekey_pairs); self.individual.merge(fork.individual); } } +#[derive(Debug, Error)] +pub enum GenerateActiveError { + #[error(transparent)] + SigningError(#[from] SigningError), + + #[error("Failed to generate a new share secret key: {0}")] + GenerateSecretKeyError(K::GenerateSecretError), +} + /// Errors when sharing encrypted content. #[derive(Debug, Error)] pub enum ShareError { @@ -333,19 +353,45 @@ pub enum ActiveDelegationError { DelegationError(#[from] DelegationError), } +#[derive(Debug, Error)] +pub enum RotatePrekeyError { + #[error(transparent)] + SigningError(#[from] SigningError), + + #[error("Failed to generate a new share secret key: {0}")] + GenerateShareSecretKeyError(K::GenerateSecretError), +} + +#[derive(Debug, Error)] +pub enum ExpandPrekeyError { + #[error(transparent)] + SigningError(#[from] SigningError), + + #[error("Failed to generate a new share secret key: {0}")] + GenerateShareSecretKeyError(K::GenerateSecretError), +} + #[cfg(test)] mod tests { + use rand::rngs::ThreadRng; + use super::*; - use crate::crypto::signer::memory::MemorySigner; + use crate::{ + crypto::signer::memory::MemorySigner, store::secret_key::memory::MemorySecretKeyStore, + }; #[tokio::test] async fn test_seal() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); let signer = MemorySigner::generate(&mut rand::thread_rng()); - let active: Active<_, [u8; 32], _> = - Active::generate(signer, NoListener, csprng).await.unwrap(); + let active: Active> = Active::generate( + signer, + MemorySecretKeyStore::new(rand::thread_rng()), + NoListener, + ) + .await + .unwrap(); let message = "hello world".as_bytes(); let signed = active.try_sign_async(message).await.unwrap(); diff --git a/keyhive_core/src/principal/agent.rs b/keyhive_core/src/principal/agent.rs index 1789d756..6b056447 100644 --- a/keyhive_core/src/principal/agent.rs +++ b/keyhive_core/src/principal/agent.rs @@ -12,6 +12,7 @@ use crate::{ content::reference::ContentRef, crypto::{share_key::ShareKey, signer::async_signer::AsyncSigner, verifiable::Verifiable}, listener::{membership::MembershipListener, no_listener::NoListener}, + store::secret_key::traits::ShareSecretStore, }; use derivative::Derivative; use derive_more::{From, TryInto}; @@ -30,14 +31,21 @@ use std::{ /// This type is very lightweight to clone, since it only contains immutable references to the actual agents. #[derive_where(Clone, Debug; T)] #[derive(From, TryInto, Derivative)] -pub enum Agent = NoListener> { - Active(Rc>>), +pub enum Agent< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef = [u8; 32], + L: MembershipListener = NoListener, +> { + Active(Rc>>), Individual(Rc>), - Group(Rc>>), - Document(Rc>>), + Group(Rc>>), + Document(Rc>>), } -impl> PartialEq for Agent { +impl> PartialEq + for Agent +{ fn eq(&self, other: &Self) -> bool { match (self, other) { (Agent::Active(a), Agent::Active(b)) => a.borrow().id() == b.borrow().id(), @@ -49,7 +57,9 @@ impl> PartialEq for A } } -impl> Agent { +impl> + Agent +{ pub fn id(&self) -> Identifier { match self { Agent::Active(a) => a.borrow().id().into(), @@ -120,34 +130,34 @@ impl> Agent } } -impl> From> - for Agent +impl> + From> for Agent { - fn from(a: Active) -> Self { + fn from(a: Active) -> Self { Agent::Active(Rc::new(RefCell::new(a))) } } -impl> From - for Agent +impl> + From for Agent { fn from(i: Individual) -> Self { Agent::Individual(Rc::new(RefCell::new(i))) } } -impl> From> - for Agent +impl> + From> for Agent { - fn from(g: Group) -> Self { + fn from(g: Group) -> Self { Agent::Group(Rc::new(RefCell::new(g))) } } -impl> From> - for Agent +impl> + From> for Agent { - fn from(m: Membered) -> Self { + fn from(m: Membered) -> Self { match m { Membered::Group(g) => g.into(), Membered::Document(d) => d.into(), @@ -155,14 +165,17 @@ impl> From> From> - for Agent +impl> + From> for Agent { - fn from(d: Document) -> Self { + fn from(d: Document) -> Self { Agent::Document(Rc::new(RefCell::new(d))) } } -impl> Verifiable for Agent { + +impl> Verifiable + for Agent +{ fn verifying_key(&self) -> VerifyingKey { match self { Agent::Active(a) => a.borrow().verifying_key(), @@ -173,13 +186,17 @@ impl> Verifiable for } } -impl> Display for Agent { +impl> Display + for Agent +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.id()) } } -impl> Dupe for Agent { +impl> Dupe + for Agent +{ fn dupe(&self) -> Self { match self { Agent::Active(a) => a.dupe().into(), diff --git a/keyhive_core/src/principal/document.rs b/keyhive_core/src/principal/document.rs index ae68792a..be5f54de 100644 --- a/keyhive_core/src/principal/document.rs +++ b/keyhive_core/src/principal/document.rs @@ -6,17 +6,17 @@ use super::{group::AddGroupMemberError, individual::id::IndividualId}; use crate::{ access::Access, cgka::{ + beekem::DecryptTreeSecretError, error::CgkaError, - keys::ShareKeyMap, operation::{CgkaEpoch, CgkaOperation}, - Cgka, + Cgka, NewAppSecretError, RemoveError, TryCgkaFromArchiveError, UpdateLeafError, }, content::reference::ContentRef, crypto::{ digest::Digest, encrypted::EncryptedContent, envelope::Envelope, - share_key::{ShareKey, ShareSecretKey}, + share_key::{AsyncSecretKey, ShareKey}, signed::{Signed, SigningError}, signer::{async_signer::AsyncSigner, ephemeral::EphemeralSigner}, symmetric_key::SymmetricKey, @@ -39,11 +39,11 @@ use crate::{ ciphertext::{CausalDecryptionError, CausalDecryptionState, CiphertextStore, ErrorReason}, delegation::DelegationStore, revocation::RevocationStore, + secret_key::traits::ShareSecretStore, }, util::content_addressed_map::CaMap, }; use derivative::Derivative; -use derive_where::derive_where; use dupe::Dupe; use ed25519_dalek::VerifyingKey; use id::DocumentId; @@ -52,6 +52,7 @@ use serde::{Deserialize, Serialize}; use std::{ cell::RefCell, collections::{BTreeMap, HashMap, HashSet}, + fmt::Debug, hash::{Hash, Hasher}, rc::Rc, }; @@ -59,32 +60,46 @@ use thiserror::Error; use tracing::instrument; #[derive(Clone, Derivative)] -#[derive_where(Debug, PartialEq; T)] pub struct Document< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub(crate) group: Group, + pub(crate) group: Group, pub(crate) content_heads: HashSet, pub(crate) content_state: HashSet, known_decryption_keys: HashMap, - cgka: Option, + pub(crate) cgka: Cgka, } -impl> Document { - // FIXME: We need a signing key for initializing Cgka and we need to share - // the init add op. +impl> + Document +{ // NOTE doesn't register into the top-level Keyhive context - #[instrument(skip(group, viewer), fields(group_id = %group.id(), viewer_id = %viewer.id()))] - pub fn from_group( - group: Group, - viewer: &Active, + #[instrument(skip_all, fields(group_id = %group.id(), viewer_id = %viewer.id()))] + pub async fn from_group( + group: Group, + viewer: &Active, + mut share_secret_store: K, content_heads: NonEmpty, - ) -> Result { + ) -> Result> { + let doc_id = DocumentId(group.id()); + let initial_secret_key = share_secret_store + .generate_share_secret_key() + .await + .map_err(DocFromGroupError::GenerateInitialKeyError)?; + let initial_pk = initial_secret_key.to_share_key(); let mut doc = Document { - cgka: None, + cgka: Cgka::new( + doc_id, + viewer.id(), + initial_pk, + share_secret_store, + &viewer.signer, + ) + .await?, group, content_heads: content_heads.iter().cloned().collect(), content_state: Default::default(), @@ -106,34 +121,20 @@ impl> Document Result<&Cgka, CgkaError> { - match &self.cgka { - Some(cgka) => Ok(cgka), - None => Err(CgkaError::NotInitialized), - } - } - - pub fn cgka_mut(&mut self) -> Result<&mut Cgka, CgkaError> { - match &mut self.cgka { - Some(cgka) => Ok(cgka), - None => Err(CgkaError::NotInitialized), - } - } - #[allow(clippy::type_complexity)] - pub fn members(&self) -> &HashMap>>>> { + pub fn members(&self) -> &HashMap>>>> { self.group.members() } - pub fn transitive_members(&self) -> HashMap, Access)> { + pub fn transitive_members(&self) -> HashMap, Access)> { self.group.transitive_members() } - pub fn delegation_heads(&self) -> &CaMap>> { + pub fn delegation_heads(&self) -> &CaMap>> { self.group.delegation_heads() } - pub fn revocation_heads(&self) -> &CaMap>> { + pub fn revocation_heads(&self) -> &CaMap>> { self.group.revocation_heads() } @@ -141,7 +142,7 @@ impl> Document Option<&Rc>>> { + ) -> Option<&Rc>>> { self.group.get_capability(member_id) } @@ -150,14 +151,15 @@ impl> Document>()) )] pub async fn generate( - parents: NonEmpty>, + parents: NonEmpty>, initial_content_heads: NonEmpty, - delegations: DelegationStore, - revocations: RevocationStore, + delegations: DelegationStore, + revocations: RevocationStore, listener: L, + mut owner_sks: K, signer: &S, csprng: &mut R, - ) -> Result { + ) -> Result> { let (group_result, group_vk) = EphemeralSigner::with_signer(csprng, |verifier, signer| { Group::generate_after_content( signer, @@ -176,27 +178,26 @@ impl> Document = group_members .iter() .filter(|(id, _sk)| **id != owner_id) .map(|(id, pk)| (*id, *pk)) .collect(); - let mut owner_sks = ShareKeyMap::new(); - owner_sks.insert(owner_share_key, owner_share_secret_key); - let mut cgka = Cgka::new(doc_id, owner_id, owner_share_key, signer) + let mut cgka = Cgka::new(doc_id, owner_id, owner_share_key, owner_sks.clone(), signer) .await? - .with_new_owner(owner_id, owner_sks)?; - let mut ops: Vec> = Vec::new(); + .with_new_owner(owner_id)?; + let mut ops: Vec> = vec![]; ops.push(cgka.init_add_op()); if let Some(others) = NonEmpty::from_vec(other_members) { ops.extend(cgka.add_multiple(others, signer).await?.iter().cloned()); } - let (_pcs_key, update_op) = cgka - .update(owner_share_key, owner_share_secret_key, signer, csprng) - .await?; + let (_pcs_key, update_op) = cgka.update(&owner_share_secret_key, signer, csprng).await?; ops.push(update_op); for op in ops { @@ -208,7 +209,7 @@ impl> Document> Document, + member_to_add: Agent, can: Access, signer: &S, - other_relevant_docs: &[Rc>>], - ) -> Result, AddMemberError> { + other_relevant_docs: &[Rc>>], + ) -> Result, AddMemberError> { let mut after_content: BTreeMap> = other_relevant_docs .iter() .map(|d| { @@ -258,9 +259,9 @@ impl> Document>, + delegation: &Signed>, signer: &S, - ) -> Result>, CgkaError> { + ) -> Result>, DecryptTreeSecretError> { let prekeys = delegation .payload .delegate @@ -268,7 +269,7 @@ impl> Document> Document>, - ) -> Result, RevokeMemberError> { + ) -> Result, RevokeMemberError> { let RevokeMemberUpdate { revocations, redelegations, @@ -311,7 +312,7 @@ impl> Document> Document Result>, CgkaError> { - self.cgka_mut()?.remove(id, signer).await + ) -> Result>, RemoveError> { + self.cgka.remove(id, signer).await } pub fn get_agent_revocations( &self, - agent: &Agent, - ) -> Vec>>> { + agent: &Agent, + ) -> Vec>>> { self.group.get_agent_revocations(agent) } @@ -346,73 +347,50 @@ impl> Document>>, - ) -> Result>>, AddError> { + delegation: Rc>>, + ) -> Result>>, AddError> { self.group.receive_delegation(delegation) } pub async fn receive_revocation( &mut self, - revocation: Rc>>, - ) -> Result>>, AddError> { + revocation: Rc>>, + ) -> Result>>, AddError> { self.group.receive_revocation(revocation).await } - pub fn merge_cgka_op(&mut self, op: Rc>) -> Result<(), CgkaError> { - match &mut self.cgka { - Some(cgka) => return cgka.merge_concurrent_operation(op), - None => match op.payload.clone() { - CgkaOperation::Add { - added_id, - pk, - ref predecessors, - .. - } => { - if !predecessors.is_empty() { - return Err(CgkaError::OutOfOrderOperation); - } - self.cgka = Some(Cgka::new_from_init_add( - self.doc_id(), - added_id, - pk, - (*op).clone(), - )?) - } - _ => return Err(CgkaError::UnexpectedInitialOperation), - }, - } - Ok(()) + pub async fn merge_cgka_op( + &mut self, + op: Rc>, + ) -> Result<(), DecryptTreeSecretError> { + self.cgka.merge_concurrent_operation(op).await } - #[instrument(skip(self, sk), fields(doc_id = ?self.doc_id()))] - pub fn merge_cgka_invite_op( + #[instrument(skip(self), fields(doc_id = ?self.doc_id()))] + pub async fn merge_cgka_invite_op( &mut self, op: Rc>, - sk: &ShareSecretKey, - ) -> Result<(), CgkaError> { + ) -> Result<(), DecryptTreeSecretError> { let CgkaOperation::Add { added_id, - pk, ref predecessors, .. } = op.payload else { - return Err(CgkaError::UnexpectedInviteOperation); + return Err(CgkaError::UnexpectedInviteOperation)?; }; if !self - .cgka()? + .cgka .contains_predecessors(&HashSet::from_iter(predecessors.iter().cloned())) { - return Err(CgkaError::OutOfOrderOperation); + return Err(CgkaError::OutOfOrderOperation)?; } - let mut owner_sks = self.cgka()?.owner_sks.clone(); - owner_sks.insert(pk, *sk); - self.cgka = Some(self.cgka()?.with_new_owner(added_id, owner_sks)?); - self.merge_cgka_op(op) + self.cgka = self.cgka.with_new_owner(added_id)?; + self.merge_cgka_op(op).await } pub fn cgka_ops(&self) -> Result, CgkaError> { - self.cgka()?.ops() + self.cgka.ops() } #[instrument(skip_all, fields(doc_id = ?self.doc_id()))] @@ -420,15 +398,17 @@ impl> Document Result, EncryptError> { - let new_share_secret_key = ShareSecretKey::generate(csprng); - let new_share_key = new_share_secret_key.share_key(); - let (_, op) = self - .cgka_mut() - .map_err(EncryptError::UnableToPcsUpdate)? - .update(new_share_key, new_share_secret_key, signer, csprng) + ) -> Result, PcsUpdateErrorError> { + let new_share_secret_key = self + .cgka + .store + .generate_share_secret_key() .await - .map_err(EncryptError::UnableToPcsUpdate)?; + .map_err(PcsUpdateErrorError::GenerateSecretError)?; + let (_, op) = self + .cgka + .update(&new_share_secret_key, signer, csprng) + .await?; Ok(op) } @@ -440,13 +420,11 @@ impl> Document, signer: &S, csprng: &mut R, - ) -> Result, EncryptError> { + ) -> Result, TryEncryptError> { let (app_secret, maybe_update_op) = self - .cgka_mut() - .map_err(EncryptError::FailedToMakeAppSecret)? + .cgka .new_app_secret_for(content_ref, content, pred_refs, signer, csprng) - .await - .map_err(EncryptError::FailedToMakeAppSecret)?; + .await?; self.known_decryption_keys .insert(content_ref.clone(), app_secret.key()); @@ -454,20 +432,20 @@ impl> Document Deserialize<'de>>( + pub async fn try_decrypt_content Deserialize<'de>>( &mut self, encrypted_content: &EncryptedContent, ) -> Result, DecryptError> { let decrypt_key = self - .cgka_mut() - .map_err(|_| DecryptError::KeyNotFound)? + .cgka .decryption_key_for(encrypted_content) + .await .map_err(|_| DecryptError::KeyNotFound)?; let mut plaintext = encrypted_content.ciphertext.clone(); @@ -503,7 +481,7 @@ impl> Document Deserialize<'de>, { - let raw_entrypoint = self.try_decrypt_content(encrypted_content)?; + let raw_entrypoint = self.try_decrypt_content(encrypted_content).await?; let mut acc = CausalDecryptionState::new(); @@ -538,18 +516,19 @@ impl> Document, - delegations: DelegationStore, - revocations: RevocationStore, + delegations: DelegationStore, + revocations: RevocationStore, + share_secret_store: K, listener: L, - ) -> Result { + ) -> Result> { Ok(Document { - group: Group::::dummy_from_archive( + group: Group::::dummy_from_archive( archive.group, delegations, revocations, @@ -558,18 +537,44 @@ impl> Document> Verifiable for Document { +impl> Debug + for Document +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Ensures that we don't skip fields by accident + let Document { + group, + content_heads, + content_state, + known_decryption_keys, + cgka, + } = self; + f.debug_struct("Document") + .field("group", group) + .field("content_heads", content_heads) + .field("content_state", content_state) + .field("known_decryption_keys", known_decryption_keys) + .field("cgka", cgka) + .finish() + } +} + +impl> Verifiable + for Document +{ fn verifying_key(&self) -> VerifyingKey { self.group.verifying_key() } } -impl> Hash for Document { +impl> Hash + for Document +{ fn hash(&self, state: &mut H) { self.group.hash(state); crate::util::hasher::hash_set(&self.content_heads, state); @@ -578,39 +583,77 @@ impl> Hash for Docume } } +impl> PartialEq + for Document +{ + fn eq(&self, other: &Self) -> bool { + let Document { + group, + content_heads, + content_state, + cgka, + known_decryption_keys, + } = self; + let round1 = *group == other.group + && *content_heads == other.content_heads + && *content_state == other.content_state + && *cgka == other.cgka; + + if !round1 { + return false; + } + + if known_decryption_keys.len() != other.known_decryption_keys.len() { + return false; + } + + for (k, v) in known_decryption_keys { + if let Some(other_v) = other.known_decryption_keys.get(k) { + if v != other_v { + return false; + } + } else { + return false; + } + } + + true + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct AddMemberUpdate< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub delegation: Rc>>, + pub delegation: Rc>>, pub cgka_ops: Vec>, } -#[derive(Debug, Clone, PartialEq, Eq, Error)] -#[error("Missing individual: {0}")] -pub struct MissingIndividualError(pub Box); - #[derive(Debug, Clone, PartialEq)] pub struct RevokeMemberUpdate< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub(crate) revocations: Vec>>>, - pub(crate) redelegations: Vec>>>, + pub(crate) revocations: Vec>>>, + pub(crate) redelegations: Vec>>>, pub(crate) cgka_ops: Vec>, } -impl> RevokeMemberUpdate { +impl> + RevokeMemberUpdate +{ #[allow(clippy::type_complexity)] - pub fn revocations(&self) -> &[Rc>>] { + pub fn revocations(&self) -> &[Rc>>] { &self.revocations } #[allow(clippy::type_complexity)] - pub fn redelegations(&self) -> &[Rc>>] { + pub fn redelegations(&self) -> &[Rc>>] { &self.redelegations } @@ -620,8 +663,8 @@ impl> RevokeMemberUpd } } -impl> Default - for RevokeMemberUpdate +impl> Default + for RevokeMemberUpdate { fn default() -> Self { Self { @@ -633,28 +676,58 @@ impl> Default } #[derive(Debug, Error)] -pub enum AddMemberError { +pub enum AddMemberError { #[error(transparent)] - AddMemberError(#[from] AddGroupMemberError), + AddMemberError(#[from] AddGroupMemberError), #[error(transparent)] CgkaError(#[from] CgkaError), + + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), +} + +#[derive(Debug, Error)] +pub enum PcsUpdateErrorError { + #[error("Encryption failed: {0}")] + EncryptionFailed(chacha20poly1305::Error), + + #[error(transparent)] + UpdateLeafError(#[from] UpdateLeafError), + + #[error("Generate share secret key error: {0}")] + GenerateSecretError(K::GenerateSecretError), } #[derive(Debug, Error)] -pub enum EncryptError { +pub enum TryEncryptError { #[error("Encryption failed: {0}")] EncryptionFailed(chacha20poly1305::Error), - #[error("Unable to PCS update: {0}")] - UnableToPcsUpdate(CgkaError), + #[error(transparent)] + NewAppSecretError(#[from] NewAppSecretError), +} + +#[derive(Error)] +pub enum DocFromGroupError { + #[error(transparent)] + CgkaError(#[from] CgkaError), + + #[error("Generate initial key error: {0}")] + GenerateInitialKeyError(K::GenerateSecretError), +} - #[error("Failed to make app secret: {0}")] - FailedToMakeAppSecret(CgkaError), +impl Debug for DocFromGroupError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DocFromGroupError::CgkaError(e) => e.fmt(f), + DocFromGroupError::GenerateInitialKeyError(e) => e.fmt(f), + } + } } #[derive(Debug, Error)] -pub enum GenerateDocError { +pub enum GenerateDocError { #[error(transparent)] DelegationError(#[from] DelegationError), @@ -663,6 +736,15 @@ pub enum GenerateDocError { #[error(transparent)] CgkaError(#[from] CgkaError), + + #[error(transparent)] + UpdateLeafError(#[from] UpdateLeafError), + + #[error("Generate share secret key error: {0}")] + GenerateSecretError(K::GenerateSecretError), + + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), } #[derive(Debug, Error)] @@ -709,21 +791,27 @@ pub enum DecryptError { } #[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum TryFromDocumentArchiveError { +pub enum TryFromDocumentArchiveError< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef, + L: MembershipListener, +> { #[error("Cannot find individual: {0}")] MissingIndividual(IndividualId), #[error("Cannot find delegation: {0}")] - MissingDelegation(Digest>>), + MissingDelegation(Digest>>), #[error("Cannot find revocation: {0}")] - MissingRevocation(Digest>>), + MissingRevocation(Digest>>), } -impl From>>>> - for TryFromDocumentArchiveError +impl> + From>>>> + for TryFromDocumentArchiveError { - fn from(e: MissingDependency>>>) -> Self { + fn from(e: MissingDependency>>>) -> Self { TryFromDocumentArchiveError::MissingDelegation(e.0) } } diff --git a/keyhive_core/src/principal/document/archive.rs b/keyhive_core/src/principal/document/archive.rs index 79b1ba5e..1a576196 100644 --- a/keyhive_core/src/principal/document/archive.rs +++ b/keyhive_core/src/principal/document/archive.rs @@ -1,4 +1,6 @@ -use crate::{cgka::Cgka, content::reference::ContentRef, principal::group::GroupArchive}; +use crate::{ + cgka::archive::CgkaArchive, content::reference::ContentRef, principal::group::GroupArchive, +}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -7,5 +9,5 @@ pub struct DocumentArchive { pub(crate) group: GroupArchive, pub(crate) content_heads: HashSet, pub(crate) content_state: HashSet, - pub(crate) cgka: Option, + pub(crate) cgka: CgkaArchive, } diff --git a/keyhive_core/src/principal/group.rs b/keyhive_core/src/principal/group.rs index b86aad42..16b3e34f 100644 --- a/keyhive_core/src/principal/group.rs +++ b/keyhive_core/src/principal/group.rs @@ -23,7 +23,10 @@ use super::{ }; use crate::{ access::Access, - cgka::{error::CgkaError, operation::CgkaOperation}, + cgka::{ + beekem::DecryptTreeSecretError, error::CgkaError, operation::CgkaOperation, + secret_store::DecryptSecretError, RemoveError, + }, content::reference::ContentRef, crypto::{ digest::Digest, @@ -37,7 +40,10 @@ use crate::{ verifiable::Verifiable, }, listener::{membership::MembershipListener, no_listener::NoListener}, - store::{delegation::DelegationStore, revocation::RevocationStore}, + store::{ + delegation::DelegationStore, revocation::RevocationStore, + secret_key::traits::ShareSecretStore, + }, util::{content_addressed_map::CaMap, hex::ToHexString}, }; use derivative::Derivative; @@ -64,35 +70,41 @@ use tracing::{debug, info, instrument}; /// through the network of [`Agent`]s. #[derive(Clone, Derivative)] #[derive_where(Debug, PartialEq; T)] -pub struct Group = NoListener> -{ +pub struct Group< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef = [u8; 32], + L: MembershipListener = NoListener, +> { pub(crate) id_or_indie: IdOrIndividual, /// The current view of members of a group. #[allow(clippy::type_complexity)] - pub(crate) members: HashMap>>>>, + pub(crate) members: HashMap>>>>, /// Current view of revocations #[allow(clippy::type_complexity)] - pub(crate) active_revocations: HashMap<[u8; 64], Rc>>>, + pub(crate) active_revocations: HashMap<[u8; 64], Rc>>>, /// The `Group`'s underlying (causal) delegation state. - pub(crate) state: GroupState, + pub(crate) state: GroupState, #[derive_where(skip)] pub(crate) listener: L, } -impl> Group { +impl> + Group +{ #[instrument( skip_all, fields(group_id = %group_id, head_sig = ?head.signature.to_bytes()) )] pub async fn new( group_id: GroupId, - head: Rc>>, - delegations: DelegationStore, - revocations: RevocationStore, + head: Rc>>, + delegations: DelegationStore, + revocations: RevocationStore, listener: L, ) -> Self { listener.on_delegation(&head).await; @@ -111,9 +123,9 @@ impl> Group #[instrument(skip(delegations, revocations, listener))] pub async fn from_individual( individual: Individual, - head: Rc>>, - delegations: DelegationStore, - revocations: RevocationStore, + head: Rc>>, + delegations: DelegationStore, + revocations: RevocationStore, listener: L, ) -> Self { listener.on_delegation(&head).await; @@ -130,12 +142,12 @@ impl> Group /// Generate a new `Group` with a unique [`Identifier`] and the given `parents`. pub async fn generate( - parents: NonEmpty>, - delegations: DelegationStore, - revocations: RevocationStore, + parents: NonEmpty>, + delegations: DelegationStore, + revocations: RevocationStore, listener: L, csprng: &mut R, - ) -> Result, SigningError> { + ) -> Result, SigningError> { let (group_result, _vk) = EphemeralSigner::with_signer(csprng, |verifier, signer| { Self::generate_after_content( signer, @@ -161,9 +173,9 @@ impl> Group pub(crate) async fn generate_after_content( signer: Box, verifier: ed25519_dalek::VerifyingKey, - parents: NonEmpty>, - delegations: DelegationStore, - revocations: RevocationStore, + parents: NonEmpty>, + delegations: DelegationStore, + revocations: RevocationStore, after_content: BTreeMap>, listener: L, ) -> Result { @@ -247,19 +259,24 @@ impl> Group } #[allow(clippy::type_complexity)] - pub fn members(&self) -> &HashMap>>>> { + pub fn members(&self) -> &HashMap>>>> { &self.members } #[instrument(skip(self), fields(group_id = %self.group_id()))] - pub fn transitive_members(&self) -> HashMap, Access)> { - struct GroupAccess> { - agent: Agent, + pub fn transitive_members(&self) -> HashMap, Access)> { + struct GroupAccess< + Z: AsyncSigner, + V: ShareSecretStore, + U: ContentRef, + M: MembershipListener, + > { + agent: Agent, agent_access: Access, parent_access: Access, } - let mut explore: Vec> = vec![]; + let mut explore: Vec> = vec![]; let mut seen: HashSet<([u8; 64], Access)> = HashSet::new(); for member in self.members.keys() { @@ -276,7 +293,7 @@ impl> Group }); } - let mut caps: HashMap, Access)> = HashMap::new(); + let mut caps: HashMap, Access)> = HashMap::new(); while let Some(GroupAccess { agent: member, @@ -298,7 +315,7 @@ impl> Group caps.insert(member.id(), (member.dupe(), current_path_access)); if let Some(membered) = match member { - Agent::Group(inner_group) => Some(Membered::::from(inner_group)), + Agent::Group(inner_group) => Some(Membered::::from(inner_group)), Agent::Document(doc) => Some(doc.into()), _ => None, } { @@ -327,11 +344,11 @@ impl> Group caps } - pub fn delegation_heads(&self) -> &CaMap>> { + pub fn delegation_heads(&self) -> &CaMap>> { &self.state.delegation_heads } - pub fn revocation_heads(&self) -> &CaMap>> { + pub fn revocation_heads(&self) -> &CaMap>> { &self.state.revocation_heads } @@ -340,7 +357,7 @@ impl> Group pub fn get_capability( &self, member_id: &Identifier, - ) -> Option<&Rc>>> { + ) -> Option<&Rc>>> { self.members.get(member_id).and_then(|delegations| { delegations .iter() @@ -351,8 +368,8 @@ impl> Group #[instrument(skip(self), fields(group_id = %self.group_id()))] pub fn get_agent_revocations( &self, - agent: &Agent, - ) -> Vec>>> { + agent: &Agent, + ) -> Vec>>> { self.state .revocations .borrow() @@ -371,8 +388,8 @@ impl> Group #[instrument(skip_all, fields(group_id = %self.group_id()))] pub fn receive_delegation( &mut self, - delegation: Rc>>, - ) -> Result>>, error::AddError> { + delegation: Rc>>, + ) -> Result>>, error::AddError> { let digest = self.state.add_delegation(delegation)?; info!("{:x?}", &digest); self.rebuild(); @@ -383,8 +400,8 @@ impl> Group #[instrument(skip(self), fields(group_id = %self.group_id()))] pub async fn receive_revocation( &mut self, - revocation: Rc>>, - ) -> Result>>, error::AddError> { + revocation: Rc>>, + ) -> Result>>, error::AddError> { self.listener.on_revocation(&revocation).await; let digest = self.state.add_revocation(revocation)?; self.rebuild(); @@ -403,11 +420,11 @@ impl> Group ] pub async fn add_member( &mut self, - member_to_add: Agent, + member_to_add: Agent, can: Access, signer: &S, - relevant_docs: &[Rc>>], - ) -> Result, AddGroupMemberError> { + relevant_docs: &[Rc>>], + ) -> Result, AddGroupMemberError> { let after_content = relevant_docs .iter() .map(|d| { @@ -424,11 +441,11 @@ impl> Group pub(crate) async fn add_member_with_manual_content( &mut self, - member_to_add: Agent, + member_to_add: Agent, can: Access, signer: &S, after_content: BTreeMap>, - ) -> Result, AddGroupMemberError> { + ) -> Result, AddGroupMemberError> { let proof = if self.verifying_key() == signer.verifying_key() { None } else { @@ -479,11 +496,11 @@ impl> Group )] pub(crate) async fn add_cgka_member( &mut self, - delegation: Rc>>, + delegation: Rc>>, signer: &S, - ) -> Result>, CgkaError> { + ) -> Result>, DecryptTreeSecretError> { let mut cgka_ops = Vec::new(); - let docs: Vec>>> = self + let docs: Vec>>> = self .transitive_members() .values() .filter_map(|(agent, _)| { @@ -521,12 +538,12 @@ impl> Group retain_all_other_members: bool, signer: &S, after_content: &BTreeMap>, - ) -> Result, RevokeMemberError> { + ) -> Result, RevokeMemberError> { let vk = signer.verifying_key(); let mut revocations = vec![]; let og_dlgs: Vec<_> = self.members.values().flatten().cloned().collect(); - let all_to_revoke: Vec>>> = self + let all_to_revoke: Vec>>> = self .members() .get(&member_to_remove) .map(|ne| Vec::<_>::from(ne.clone())) // Semi-inexpensive because `Vec>` @@ -625,7 +642,7 @@ impl> Group let mut cgka_ops = Vec::new(); let (individuals, docs): ( Vec>>, - Vec>>>, + Vec>>>, ) = self.transitive_members().values().fold( (vec![], vec![]), |(mut indies, mut docs), (agent, _)| { @@ -687,10 +704,10 @@ impl> Group async fn build_revocation( &mut self, signer: &S, - revoke: Rc>>, - proof: Option>>>, + revoke: Rc>>, + proof: Option>>>, after_content: BTreeMap>, - ) -> Result>>, SigningError> { + ) -> Result>>, SigningError> { let revocation = signer .try_sign_async(Revocation { revoke, @@ -708,7 +725,8 @@ impl> Group self.active_revocations.clear(); #[allow(clippy::type_complexity)] - let mut dlgs_in_play: HashMap<[u8; 64], Rc>>> = HashMap::new(); + let mut dlgs_in_play: HashMap<[u8; 64], Rc>>> = + HashMap::new(); let mut revoked_dlgs: HashSet<[u8; 64]> = HashSet::new(); // {dlg_dep => Set} @@ -835,8 +853,8 @@ impl> Group pub(crate) fn dummy_from_archive( archive: GroupArchive, - delegations: DelegationStore, - revocations: RevocationStore, + delegations: DelegationStore, + revocations: RevocationStore, listener: L, ) -> Self { Self { @@ -868,7 +886,9 @@ impl> Group } } -impl> Hash for Group { +impl> Hash + for Group +{ fn hash(&self, state: &mut H) { self.id_or_indie.hash(state); self.members.iter().collect::>().hash(state); @@ -876,7 +896,9 @@ impl> Hash for Group< } } -impl> Verifiable for Group { +impl> Verifiable + for Group +{ fn verifying_key(&self) -> ed25519_dalek::VerifyingKey { self.state.verifying_key() } @@ -896,7 +918,7 @@ pub struct GroupArchive { } #[derive(Debug, Error)] -pub enum AddGroupMemberError { +pub enum AddGroupMemberError { #[error(transparent)] SigningError(#[from] SigningError), @@ -911,10 +933,13 @@ pub enum AddGroupMemberError { #[error(transparent)] CgkaError(#[from] CgkaError), + + #[error(transparent)] + DecryptTreeSecretError(#[from] DecryptTreeSecretError), } #[derive(Debug, Error)] -pub enum RevokeMemberError { +pub enum RevokeMemberError { #[error(transparent)] AddError(#[from] error::AddError), @@ -928,7 +953,13 @@ pub enum RevokeMemberError { CgkaError(#[from] CgkaError), #[error("Redelagation error")] - RedelegationError(#[from] AddGroupMemberError), + RedelegationError(#[from] AddGroupMemberError), + + #[error("Revocation error")] + DecryptSecretError(#[from] DecryptSecretError), + + #[error(transparent)] + RemoveError(#[from] RemoveError), } #[cfg(test)] @@ -937,22 +968,31 @@ mod tests { use super::*; use crate::crypto::signer::memory::MemorySigner; use crate::principal::active::Active; + use crate::store::secret_key::memory::MemorySecretKeyStore; use nonempty::nonempty; use pretty_assertions::assert_eq; + use rand::rngs::ThreadRng; use std::cell::RefCell; - async fn setup_user( - csprng: &mut R, - ) -> Active { - let sk = MemorySigner::generate(csprng); - Active::generate(sk, NoListener, csprng).await.unwrap() + async fn setup_user() -> Active> { + let sk = MemorySigner::generate(&mut rand::thread_rng()); + Active::generate( + sk, + MemorySecretKeyStore { + csprng: rand::thread_rng(), + keys: HashMap::new(), + }, + NoListener, + ) + .await + .unwrap() } - async fn setup_groups( - alice: Rc>>, - bob: Rc>>, - csprng: &mut R, - ) -> [Rc>>; 4] { + async fn setup_groups( + alice: Rc, T>>>, + bob: Rc, T>>>, + csprng: &mut ThreadRng, + ) -> [Rc, T>>>; 4] { /* ┌───────────┐ ┌───────────┐ │ │ │ │ ╔══════════════▶│ Alice │ │ Bob │ @@ -976,7 +1016,7 @@ mod tests { │ │ └───────────┘ */ - let alice_agent: Agent = alice.into(); + let alice_agent: Agent, T, _> = alice.into(); let bob_agent = bob.into(); let dlg_store = DelegationStore::new(); @@ -1035,10 +1075,10 @@ mod tests { #[allow(clippy::await_holding_refcell_ref)] // FIXME async fn setup_cyclic_groups( - alice: Rc>>, - bob: Rc>>, + alice: Rc, T>>>, + bob: Rc, T>>>, csprng: &mut R, - ) -> [Rc>>; 10] { + ) -> [Rc, T>>>; 10] { let dlg_store = DelegationStore::new(); let rev_store = RevocationStore::new(); @@ -1196,13 +1236,13 @@ mod tests { test_utils::init_logging(); let csprng = &mut rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); let alice_id = alice_agent.id(); - let bob = Rc::new(RefCell::new(setup_user(csprng).await)); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); - let [g0, ..]: [Rc>>; 4] = + let [g0, ..]: [Rc>>>; 4] = setup_groups(alice.dupe(), bob, csprng).await; let g0_mems = g0.borrow().transitive_members(); @@ -1216,11 +1256,11 @@ mod tests { test_utils::init_logging(); let csprng = &mut rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); let alice_id = alice_agent.id(); - let bob = Rc::new(RefCell::new(setup_user(csprng).await)); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); let [g0, g1, ..] = setup_groups(alice.dupe(), bob, csprng).await; let g1_mems = g1.borrow().transitive_members(); @@ -1231,14 +1271,14 @@ mod tests { ( alice_id, ( - Agent::::from(alice.dupe()), + Agent::>::from(alice.dupe()), Access::Admin ) ), ( g0.borrow().id(), ( - Agent::::from(g0.dupe()), + Agent::>::from(g0.dupe()), Access::Admin ) ) @@ -1251,16 +1291,16 @@ mod tests { test_utils::init_logging(); let csprng = &mut rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); let alice_id = alice_agent.id(); - let bob = Rc::new(RefCell::new(setup_user(csprng).await)); - let bob_agent: Agent = bob.dupe().into(); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let bob_agent: Agent> = bob.dupe().into(); let bob_id = bob_agent.id(); - let [g0, g1, g2, _g3]: [Rc>>; 4] = - setup_groups(alice.dupe(), bob.dupe(), csprng).await; + let [g0, g1, g2, _g3]: [Rc>>>; + 4] = setup_groups(alice.dupe(), bob.dupe(), csprng).await; let g1_mems = g2.borrow().transitive_members(); assert_eq!( @@ -1279,16 +1319,16 @@ mod tests { test_utils::init_logging(); let csprng = &mut rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); let alice_id = alice_agent.id(); - let bob = Rc::new(RefCell::new(setup_user(csprng).await)); - let bob_agent: Agent = bob.dupe().into(); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let bob_agent: Agent> = bob.dupe().into(); let bob_id = bob_agent.id(); - let [g0, g1, g2, g3]: [Rc>>; 4] = - setup_groups(alice.dupe(), bob.dupe(), csprng).await; + let [g0, g1, g2, g3]: [Rc>>>; + 4] = setup_groups(alice.dupe(), bob.dupe(), csprng).await; let g3_mems = g3.borrow().transitive_members(); assert_eq!(g3_mems.len(), 5); @@ -1310,16 +1350,17 @@ mod tests { test_utils::init_logging(); let csprng = &mut rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); let alice_id = alice_agent.id(); - let bob = Rc::new(RefCell::new(setup_user(csprng).await)); - let bob_agent: Agent = bob.dupe().into(); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let bob_agent: Agent> = bob.dupe().into(); let bob_id = bob_agent.id(); - let [g0, g1, g2, g3, g4, g5, g6, g7, g8, g9]: [Rc>>; - 10] = setup_cyclic_groups(alice.dupe(), bob.dupe(), csprng).await; + let [g0, g1, g2, g3, g4, g5, g6, g7, g8, g9]: [Rc< + RefCell>>, + >; 10] = setup_cyclic_groups(alice.dupe(), bob.dupe(), csprng).await; let g0_mems = g0.borrow().transitive_members(); assert_eq!(g0_mems.len(), 11); @@ -1327,10 +1368,7 @@ mod tests { assert_eq!( g0_mems, HashMap::from_iter([ - ( - alice_id, - (Agent::<_, String, _>::from(alice), Access::Admin) - ), + (alice_id, (Agent::from(alice), Access::Admin)), (bob_id, (bob.into(), Access::Admin)), (g1.borrow().id(), (g1.dupe().into(), Access::Admin)), (g2.borrow().id(), (g2.dupe().into(), Access::Admin)), @@ -1351,20 +1389,24 @@ mod tests { test_utils::init_logging(); let mut csprng = rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); - let bob = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let bob_agent: Agent = bob.dupe().into(); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let bob_agent: Agent> = bob.dupe().into(); - let carol = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let carol_agent: Agent = carol.dupe().into(); + let carol = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let carol_agent: Agent> = carol.dupe().into(); let signer = MemorySigner::generate(&mut csprng); let active = Rc::new(RefCell::new( - Active::generate(signer, NoListener, &mut csprng) - .await - .unwrap(), + Active::generate( + signer, + MemorySecretKeyStore::new(rand::thread_rng()), + NoListener, + ) + .await + .unwrap(), )); let active_id = active.borrow().id(); @@ -1495,19 +1537,18 @@ mod tests { // └─────────┘ test_utils::init_logging(); - let mut csprng = rand::thread_rng(); - let alice = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let alice_agent: Agent = alice.dupe().into(); + let alice = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let alice_agent: Agent> = alice.dupe().into(); - let bob = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let bob_agent: Agent = bob.dupe().into(); + let bob = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let bob_agent: Agent> = bob.dupe().into(); - let carol = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let carol_agent: Agent = carol.dupe().into(); + let carol = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let carol_agent: Agent> = carol.dupe().into(); - let dan = Rc::new(RefCell::new(setup_user(&mut csprng).await)); - let dan_agent: Agent = dan.dupe().into(); + let dan = Rc::new(RefCell::new(setup_user::<[u8; 32]>().await)); + let dan_agent: Agent> = dan.dupe().into(); let alice_id = alice.borrow().id().into(); let alice_signer = alice.borrow().signer.dupe(); @@ -1528,7 +1569,7 @@ mod tests { dlg_store.dupe(), rev_store.dupe(), NoListener, - &mut csprng, + &mut rand::thread_rng(), ) .await .unwrap(); diff --git a/keyhive_core/src/principal/group/delegation.rs b/keyhive_core/src/principal/group/delegation.rs index 0f992f19..406d6ff6 100644 --- a/keyhive_core/src/principal/group/delegation.rs +++ b/keyhive_core/src/principal/group/delegation.rs @@ -16,6 +16,7 @@ use crate::{ document::id::DocumentId, identifier::Identifier, }, + store::secret_key::traits::ShareSecretStore, }; use derive_where::derive_where; use dupe::Dupe; @@ -26,20 +27,26 @@ use thiserror::Error; #[derive_where(Debug, Clone, PartialEq; T)] pub struct Delegation< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub(crate) delegate: Agent, + pub(crate) delegate: Agent, pub(crate) can: Access, - pub(crate) proof: Option>>>, - pub(crate) after_revocations: Vec>>>, + pub(crate) proof: Option>>>, + pub(crate) after_revocations: Vec>>>, pub(crate) after_content: BTreeMap>, } -impl> Eq for Delegation {} +impl> Eq + for Delegation +{ +} -impl> Delegation { +impl> + Delegation +{ pub fn subject_id(&self, issuer: AgentId) -> Identifier { if let Some(proof) = &self.proof { proof.subject_id() @@ -48,7 +55,7 @@ impl> Delegation &Agent { + pub fn delegate(&self) -> &Agent { &self.delegate } @@ -57,16 +64,16 @@ impl> Delegation Option<&Rc>>> { + pub fn proof(&self) -> Option<&Rc>>> { self.proof.as_ref() } #[allow(clippy::type_complexity)] - pub fn after_revocations(&self) -> &[Rc>>] { + pub fn after_revocations(&self) -> &[Rc>>] { &self.after_revocations } - pub fn after(&self) -> Dependencies { + pub fn after(&self) -> Dependencies { let AfterAuth { optional_delegation, revocations, @@ -81,7 +88,7 @@ impl> Delegation AfterAuth { + pub fn after_auth(&self) -> AfterAuth { AfterAuth { optional_delegation: self.proof.dupe(), revocations: &self.after_revocations, @@ -92,7 +99,7 @@ impl> Delegation Vec>>> { + pub fn proof_lineage(&self) -> Vec>>> { let mut lineage = vec![]; let mut head = self; @@ -104,7 +111,7 @@ impl> Delegation>) -> bool { + pub fn is_descendant_of(&self, maybe_ancestor: &Signed>) -> bool { let mut head = self; while let Some(proof) = &head.proof { @@ -118,7 +125,7 @@ impl> Delegation>) -> bool { + pub fn is_ancestor_of(&self, maybe_descendant: &Signed>) -> bool { let mut head = maybe_descendant.payload(); while let Some(proof) = &head.proof { @@ -133,7 +140,9 @@ impl> Delegation> Signed> { +impl> + Signed> +{ pub fn subject_id(&self) -> Identifier { let mut head = self; @@ -145,7 +154,9 @@ impl> Signed> Serialize for Delegation { +impl> Serialize + for Delegation +{ fn serialize(&self, serializer: Z) -> Result { StaticDelegation::from(self.clone()).serialize(serializer) } @@ -183,10 +194,10 @@ impl<'a, T: ContentRef + arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a> } } -impl> From> - for StaticDelegation +impl> + From> for StaticDelegation { - fn from(delegation: Delegation) -> Self { + fn from(delegation: Delegation) -> Self { Self { can: delegation.can, proof: delegation.proof.map(|p| Digest::hash(p.as_ref()).into()), @@ -205,14 +216,15 @@ impl> From = NoListener, + L: MembershipListener = NoListener, > { #[allow(clippy::type_complexity)] - pub(crate) optional_delegation: Option>>>, + pub(crate) optional_delegation: Option>>>, #[allow(clippy::type_complexity)] - pub(crate) revocations: &'a [Rc>>], + pub(crate) revocations: &'a [Rc>>], } /// Errors that can occur when using an active agent. diff --git a/keyhive_core/src/principal/group/dependencies.rs b/keyhive_core/src/principal/group/dependencies.rs index 688ea3fc..bd4a3aa1 100644 --- a/keyhive_core/src/principal/group/dependencies.rs +++ b/keyhive_core/src/principal/group/dependencies.rs @@ -4,6 +4,7 @@ use crate::{ crypto::{signed::Signed, signer::async_signer::AsyncSigner}, listener::{membership::MembershipListener, no_listener::NoListener}, principal::document::id::DocumentId, + store::secret_key::traits::ShareSecretStore, }; use std::{collections::BTreeMap, hash::Hash, rc::Rc}; @@ -11,10 +12,11 @@ use std::{collections::BTreeMap, hash::Hash, rc::Rc}; pub struct Dependencies< 'a, S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub delegations: Vec>>>, - pub revocations: Vec>>>, + pub delegations: Vec>>>, + pub revocations: Vec>>>, pub content: &'a BTreeMap>, } diff --git a/keyhive_core/src/principal/group/membership_operation.rs b/keyhive_core/src/principal/group/membership_operation.rs index 09d7a07c..752c4beb 100644 --- a/keyhive_core/src/principal/group/membership_operation.rs +++ b/keyhive_core/src/principal/group/membership_operation.rs @@ -10,33 +10,61 @@ use crate::{ }, listener::{membership::MembershipListener, no_listener::NoListener}, principal::{document::id::DocumentId, identifier::Identifier}, + store::secret_key::traits::ShareSecretStore, util::content_addressed_map::CaMap, }; use derive_more::{From, Into}; -use derive_where::derive_where; use dupe::Dupe; use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, collections::{BTreeMap, HashMap, HashSet}, + fmt::Debug, hash::Hash, rc::Rc, }; use topological_sort::TopologicalSort; use tracing::instrument; -#[derive_where(Debug, Clone, Eq; T)] pub enum MembershipOperation< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - Delegation(Rc>>), - Revocation(Rc>>), + Delegation(Rc>>), + Revocation(Rc>>), } -impl> std::hash::Hash - for MembershipOperation +impl> Debug + for MembershipOperation +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MembershipOperation::Delegation(delegation) => delegation.fmt(f), + MembershipOperation::Revocation(revocation) => revocation.fmt(f), + } + } +} + +impl> Clone + for MembershipOperation +{ + fn clone(&self) -> Self { + match self { + MembershipOperation::Delegation(rc) => MembershipOperation::Delegation(rc.dupe()), + MembershipOperation::Revocation(rc) => MembershipOperation::Revocation(rc.dupe()), + } + } +} + +impl> Eq + for MembershipOperation +{ +} + +impl> + std::hash::Hash for MembershipOperation { fn hash(&self, state: &mut H) { match self { @@ -50,8 +78,8 @@ impl> std::hash::Hash } } -impl> PartialEq - for MembershipOperation +impl> PartialEq + for MembershipOperation { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -62,8 +90,12 @@ impl> PartialEq } } -impl> Serialize - for MembershipOperation +impl< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef + Serialize, + L: MembershipListener, + > Serialize for MembershipOperation { fn serialize(&self, serializer: Z) -> Result { match self { @@ -73,7 +105,9 @@ impl> Ser } } -impl> MembershipOperation { +impl> + MembershipOperation +{ pub fn subject_id(&self) -> Identifier { match self { MembershipOperation::Delegation(delegation) => delegation.subject_id(), @@ -99,7 +133,7 @@ impl> MembershipOpera !self.is_delegation() } - pub fn after_auth(&self) -> Vec> { + pub fn after_auth(&self) -> Vec> { let deps = self.after(); deps.delegations .into_iter() @@ -108,7 +142,7 @@ impl> MembershipOpera .collect() } - pub fn after(&self) -> Dependencies { + pub fn after(&self) -> Dependencies { match self { MembershipOperation::Delegation(delegation) => delegation.payload.after(), MembershipOperation::Revocation(revocation) => revocation.payload.after(), @@ -129,7 +163,7 @@ impl> MembershipOpera } } - pub fn ancestors(&self) -> (CaMap>, usize) { + pub fn ancestors(&self) -> (CaMap>, usize) { if self.is_root() { return (CaMap::new(), 1); } @@ -174,18 +208,18 @@ impl> MembershipOpera #[allow(clippy::type_complexity)] // Clippy doens't like the returned pair #[instrument(skip_all)] pub fn topsort( - delegation_heads: &CaMap>>, - revocation_heads: &CaMap>>, + delegation_heads: &CaMap>>, + revocation_heads: &CaMap>>, ) -> Vec<( - Digest>, - MembershipOperation, + Digest>, + MembershipOperation, )> { // NOTE: BTreeMap to get deterministic order let mut ops_with_ancestors: BTreeMap< - Digest>, + Digest>, ( - MembershipOperation, - CaMap>, + MembershipOperation, + CaMap>, usize, ), > = BTreeMap::new(); @@ -205,17 +239,17 @@ impl> MembershipOpera } } - let mut leftovers: HashMap> = HashMap::new(); - let mut explore: Vec> = vec![]; + let mut leftovers: HashMap> = HashMap::new(); + let mut explore: Vec> = vec![]; for dlg in delegation_heads.values() { - let op: MembershipOperation = dlg.dupe().into(); + let op: MembershipOperation = dlg.dupe().into(); leftovers.insert(op.signature().into(), op.clone()); explore.push(op); } for rev in revocation_heads.values() { - let op: MembershipOperation = rev.dupe().into(); + let op: MembershipOperation = rev.dupe().into(); leftovers.insert(op.signature().into(), op.clone()); explore.push(op); } @@ -224,8 +258,8 @@ impl> MembershipOpera let mut revoked_dependencies: HashMap< Key, ( - Digest>, - MembershipOperation, + Digest>, + MembershipOperation, ), > = HashMap::new(); @@ -246,8 +280,8 @@ impl> MembershipOpera } let mut adjacencies: TopologicalSort<( - Digest>, - &MembershipOperation, + Digest>, + &MembershipOperation, )> = topological_sort::TopologicalSort::new(); for (digest, (op, op_ancestors, longest_path)) in ops_with_ancestors.iter() { @@ -267,11 +301,11 @@ impl> MembershipOpera .expect("values that we just put there to be there"); #[allow(clippy::mutable_key_type)] - let ancestor_set: HashSet<&MembershipOperation> = + let ancestor_set: HashSet<&MembershipOperation> = op_ancestors.values().map(|op| op.as_ref()).collect(); #[allow(clippy::mutable_key_type)] - let other_ancestor_set: HashSet<&MembershipOperation> = + let other_ancestor_set: HashSet<&MembershipOperation> = other_ancestors.values().map(|op| op.as_ref()).collect(); if other_ancestor_set.contains(op) || ancestor_set.is_subset(&other_ancestor_set) { @@ -329,8 +363,8 @@ impl> MembershipOpera } let mut history: Vec<( - Digest>, - MembershipOperation, + Digest>, + MembershipOperation, )> = leftovers .values() .map(|op| (Digest::hash(op), op.clone())) @@ -356,16 +390,16 @@ impl> MembershipOpera } } -impl> Dupe - for MembershipOperation +impl> Dupe + for MembershipOperation { fn dupe(&self) -> Self { self.clone() } } -impl> Verifiable - for MembershipOperation +impl> Verifiable + for MembershipOperation { fn verifying_key(&self) -> ed25519_dalek::VerifyingKey { match self { @@ -375,18 +409,18 @@ impl> Verifiable } } -impl> - From>>> for MembershipOperation +impl> + From>>> for MembershipOperation { - fn from(delegation: Rc>>) -> Self { + fn from(delegation: Rc>>) -> Self { MembershipOperation::Delegation(delegation) } } -impl> - From>>> for MembershipOperation +impl> + From>>> for MembershipOperation { - fn from(revocation: Rc>>) -> Self { + fn from(revocation: Rc>>) -> Self { MembershipOperation::Revocation(revocation) } } @@ -398,10 +432,10 @@ pub enum StaticMembershipOperation { Revocation(Signed>), } -impl> From> - for StaticMembershipOperation +impl> + From> for StaticMembershipOperation { - fn from(op: MembershipOperation) -> Self { + fn from(op: MembershipOperation) -> Self { match op { MembershipOperation::Delegation(d) => { StaticMembershipOperation::Delegation(Rc::unwrap_or_clone(d).map(Into::into)) @@ -419,10 +453,11 @@ mod tests { use crate::{ access::Access, crypto::signer::{memory::MemorySigner, sync_signer::SyncSigner}, - principal::agent::Agent, - principal::individual::Individual, + principal::{agent::Agent, individual::Individual}, + store::secret_key::memory::MemorySecretKeyStore, }; use dupe::Dupe; + use rand::rngs::ThreadRng; use std::cell::RefCell; use std::rc::Rc; @@ -477,10 +512,8 @@ mod tests { └───────┘ */ - async fn add_alice( - csprng: &mut R, - ) -> Rc>> { - let alice = Individual::generate(fixture(&ALICE_SIGNER), csprng) + async fn add_alice() -> Rc>>> { + let alice = Individual::generate(fixture(&ALICE_SIGNER), &mut rand::thread_rng()) .await .unwrap(); let group_sk = LazyLock::force(&GROUP_SIGNER).clone(); @@ -499,10 +532,8 @@ mod tests { .dupe() } - async fn add_bob( - csprng: &mut R, - ) -> Rc>> { - let bob = Individual::generate(fixture(&BOB_SIGNER), csprng) + async fn add_bob() -> Rc>>> { + let bob = Individual::generate(fixture(&BOB_SIGNER), &mut rand::thread_rng()) .await .unwrap(); @@ -511,7 +542,7 @@ mod tests { .try_sign_sync(Delegation { delegate: Agent::Individual(Rc::new(RefCell::new(bob))), can: Access::Write, - proof: Some(add_alice(csprng).await), + proof: Some(add_alice().await), after_content: BTreeMap::new(), after_revocations: vec![], }) @@ -519,10 +550,8 @@ mod tests { ) } - async fn add_carol( - csprng: &mut R, - ) -> Rc>> { - let carol = Individual::generate(fixture(&CAROL_SIGNER), csprng) + async fn add_carol() -> Rc>>> { + let carol = Individual::generate(fixture(&CAROL_SIGNER), &mut rand::thread_rng()) .await .unwrap(); @@ -531,7 +560,7 @@ mod tests { .try_sign_sync(Delegation { delegate: carol.into(), can: Access::Write, - proof: Some(add_alice(csprng).await), + proof: Some(add_alice().await), after_content: BTreeMap::new(), after_revocations: vec![], }) @@ -539,10 +568,8 @@ mod tests { ) } - async fn add_dan( - csprng: &mut R, - ) -> Rc>> { - let dan = Individual::generate(fixture(&DAN_SIGNER), csprng) + async fn add_dan() -> Rc>>> { + let dan = Individual::generate(fixture(&DAN_SIGNER), &mut rand::thread_rng()) .await .unwrap(); @@ -551,7 +578,7 @@ mod tests { .try_sign_sync(Delegation { delegate: dan.into(), can: Access::Write, - proof: Some(add_carol(csprng).await), + proof: Some(add_carol().await), after_content: BTreeMap::new(), after_revocations: vec![], }) @@ -559,10 +586,8 @@ mod tests { ) } - async fn add_erin( - csprng: &mut R, - ) -> Rc>> { - let erin = Individual::generate(fixture(&ERIN_SIGNER), csprng) + async fn add_erin() -> Rc>>> { + let erin = Individual::generate(fixture(&ERIN_SIGNER), &mut rand::thread_rng()) .await .unwrap(); @@ -571,7 +596,7 @@ mod tests { .try_sign_sync(Delegation { delegate: erin.into(), can: Access::Write, - proof: Some(add_bob(csprng).await), + proof: Some(add_bob().await), after_content: BTreeMap::new(), after_revocations: vec![], }) @@ -579,28 +604,25 @@ mod tests { ) } - async fn remove_carol( - csprng: &mut R, - ) -> Rc>> { + async fn remove_carol() -> Rc>>> + { Rc::new( fixture(&ALICE_SIGNER) .try_sign_sync(Revocation { - revoke: add_carol(csprng).await, - proof: Some(add_alice(csprng).await), + revoke: add_carol().await, + proof: Some(add_alice().await), after_content: BTreeMap::new(), }) .unwrap(), ) } - async fn remove_dan( - csprng: &mut R, - ) -> Rc>> { + async fn remove_dan() -> Rc>>> { Rc::new( fixture(&BOB_SIGNER) .try_sign_sync(Revocation { - revoke: add_dan(csprng).await, - proof: Some(add_bob(csprng).await), + revoke: add_dan().await, + proof: Some(add_bob().await), after_content: BTreeMap::new(), }) .unwrap(), @@ -617,8 +639,7 @@ mod tests { #[tokio::test] async fn test_singleton() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let alice_dlg = add_alice(csprng).await; + let alice_dlg = add_alice().await; let (ancestors, longest) = MembershipOperation::from(alice_dlg).ancestors(); assert!(ancestors.is_empty()); assert_eq!(longest, 1); @@ -627,8 +648,7 @@ mod tests { #[tokio::test] async fn test_two_direct() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let bob_dlg = add_bob(csprng).await; + let bob_dlg = add_bob().await; let (ancestors, longest) = MembershipOperation::from(bob_dlg).ancestors(); assert_eq!(ancestors.len(), 1); assert_eq!(longest, 2); @@ -637,9 +657,8 @@ mod tests { #[tokio::test] async fn test_concurrent() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let bob_dlg = add_bob(csprng).await; - let carol_dlg = add_carol(csprng).await; + let bob_dlg = add_bob().await; + let carol_dlg = add_carol().await; let (bob_ancestors, bob_longest) = MembershipOperation::from(bob_dlg).ancestors(); let (carol_ancestors, carol_longest) = MembershipOperation::from(carol_dlg).ancestors(); @@ -651,8 +670,7 @@ mod tests { #[tokio::test] async fn test_longer() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let erin_dlg = add_erin(csprng).await; + let erin_dlg = add_erin().await; let (ancestors, longest) = MembershipOperation::from(erin_dlg).ancestors(); assert_eq!(ancestors.len(), 2); assert_eq!(longest, 2); @@ -661,8 +679,7 @@ mod tests { #[tokio::test] async fn test_revocation() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let rev = remove_carol(csprng).await; + let rev = remove_carol().await; let (ancestors, longest) = MembershipOperation::from(rev).ancestors(); assert_eq!(ancestors.len(), 2); assert_eq!(longest, 2); @@ -670,9 +687,9 @@ mod tests { } mod topsort { - use crate::principal::active::Active; - use super::*; + use crate::{principal::active::Active, store::secret_key::memory::MemorySecretKeyStore}; + use rand::rngs::ThreadRng; #[test] fn test_empty() { @@ -681,16 +698,18 @@ mod tests { let dlgs = CaMap::new(); let revs = CaMap::new(); - let observed = MembershipOperation::::topsort(&dlgs, &revs); + let observed = + MembershipOperation::>::topsort( + &dlgs, &revs, + ); assert_eq!(observed, vec![]); } #[tokio::test] async fn test_one_delegation() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let dlg = add_alice(csprng).await; + let dlg = add_alice().await; let dlgs = CaMap::from_iter_direct([dlg.dupe()]); let revs = CaMap::new(); @@ -704,10 +723,9 @@ mod tests { #[tokio::test] async fn test_delegation_sequence() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let alice_dlg = add_alice(csprng).await; - let bob_dlg = add_bob(csprng).await; + let alice_dlg = add_alice().await; + let bob_dlg = add_bob().await; let dlg_heads = CaMap::from_iter_direct([bob_dlg.dupe()]); let rev_heads = CaMap::new(); @@ -729,24 +747,26 @@ mod tests { #[tokio::test] async fn test_longer_delegation_chain() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let alice_dlg = add_alice(csprng).await; - let carol_dlg = add_carol(csprng).await; - let dan_dlg = add_dan(csprng).await; + let alice_dlg = add_alice().await; + let carol_dlg = add_carol().await; + let dan_dlg = add_dan().await; let dlg_heads = CaMap::from_iter_direct([dan_dlg.dupe()]); let rev_heads = CaMap::new(); let observed = MembershipOperation::topsort(&dlg_heads, &rev_heads); - let alice_op: MembershipOperation = alice_dlg.into(); + let alice_op: MembershipOperation> = + alice_dlg.into(); let alice_hash = Digest::hash(&alice_op); - let carol_op: MembershipOperation = carol_dlg.into(); + let carol_op: MembershipOperation> = + carol_dlg.into(); let carol_hash = Digest::hash(&carol_op); - let dan_op: MembershipOperation = dan_dlg.into(); + let dan_op: MembershipOperation> = + dan_dlg.into(); let dan_hash = Digest::hash(&dan_op); let a = (alice_hash, alice_op.clone()); @@ -777,33 +797,54 @@ mod tests { // │ Carol │ // └─────────┘ test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); let alice_sk = fixture(&ALICE_SIGNER).clone(); let alice = Rc::new(RefCell::new( - Active::<_, [u8; 32], _>::generate(alice_sk, NoListener, csprng) - .await - .unwrap(), + Active::>::generate( + alice_sk, + MemorySecretKeyStore::new(rand::thread_rng()), + NoListener, + ) + .await + .unwrap(), )); let bob_sk = fixture(&BOB_SIGNER).clone(); let bob = Rc::new(RefCell::new( - Active::generate(bob_sk, NoListener, csprng).await.unwrap(), + Active::generate( + bob_sk, + MemorySecretKeyStore::new(rand::thread_rng()), + NoListener, + ) + .await + .unwrap(), )); let carol_sk = fixture(&CAROL_SIGNER).clone(); let carol = Rc::new(RefCell::new( - Active::generate(carol_sk, NoListener, csprng) - .await - .unwrap(), + Active::generate( + carol_sk, + MemorySecretKeyStore::new(rand::thread_rng()), + NoListener, + ) + .await + .unwrap(), )); let dan_sk = fixture(&DAN_SIGNER).clone(); let dan = Rc::new(RefCell::new( - Active::generate(dan_sk, NoListener, csprng).await.unwrap(), + Active::generate( + dan_sk, + MemorySecretKeyStore::new(rand::thread_rng()), + NoListener, + ) + .await + .unwrap(), )); - let alice_to_bob: Rc>> = Rc::new( + let alice_to_bob: Rc< + Signed>>, + > = Rc::new( alice .borrow() .signer @@ -873,10 +914,9 @@ mod tests { async fn test_one_revocation() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); let alice_sk = fixture(&ALICE_SIGNER).clone(); - let alice_dlg = add_alice(csprng).await; - let bob_dlg = add_bob(csprng).await; + let alice_dlg = add_alice().await; + let bob_dlg = add_bob().await; let alice_revokes_bob = Rc::new( alice_sk @@ -887,7 +927,8 @@ mod tests { }) .unwrap(), ); - let rev_op: MembershipOperation = alice_revokes_bob.dupe().into(); + let rev_op: MembershipOperation> = + alice_revokes_bob.dupe().into(); let rev_hash = Digest::hash(&rev_op); let dlgs = CaMap::new(); @@ -895,10 +936,12 @@ mod tests { let mut observed = MembershipOperation::topsort(&dlgs, &revs); - let alice_op: MembershipOperation = alice_dlg.into(); + let alice_op: MembershipOperation> = + alice_dlg.into(); let alice_hash = Digest::hash(&alice_op); - let bob_op: MembershipOperation = bob_dlg.into(); + let bob_op: MembershipOperation> = + bob_dlg.into(); let bob_hash = Digest::hash(&bob_op); let a = (alice_hash, alice_op.clone()); @@ -916,23 +959,22 @@ mod tests { #[tokio::test] async fn test_many_revocations() { test_utils::init_logging(); - let csprng = &mut rand::thread_rng(); - let alice_dlg = add_alice(csprng).await; - let bob_dlg = add_bob(csprng).await; + let alice_dlg = add_alice().await; + let bob_dlg = add_bob().await; - let carol_dlg = add_carol(csprng).await; - let dan_dlg = add_dan(csprng).await; - let erin_dlg = add_erin(csprng).await; + let carol_dlg = add_carol().await; + let dan_dlg = add_dan().await; + let erin_dlg = add_erin().await; - let alice_revokes_carol = remove_carol(csprng).await; - let bob_revokes_dan = remove_dan(csprng).await; + let alice_revokes_carol = remove_carol().await; + let bob_revokes_dan = remove_dan().await; - let rev_carol_op: MembershipOperation = + let rev_carol_op: MembershipOperation> = alice_revokes_carol.dupe().into(); let rev_carol_hash = Digest::hash(&rev_carol_op); - let rev_dan_op: MembershipOperation = + let rev_dan_op: MembershipOperation> = bob_revokes_dan.dupe().into(); let rev_dan_hash = Digest::hash(&rev_dan_op); @@ -942,19 +984,24 @@ mod tests { let observed = MembershipOperation::topsort(&dlg_heads, &rev_heads); - let alice_op: MembershipOperation = alice_dlg.clone().into(); + let alice_op: MembershipOperation> = + alice_dlg.clone().into(); let alice_hash = Digest::hash(&alice_op); - let bob_op: MembershipOperation = bob_dlg.clone().into(); + let bob_op: MembershipOperation> = + bob_dlg.clone().into(); let bob_hash = Digest::hash(&bob_op); - let carol_op: MembershipOperation = carol_dlg.clone().into(); + let carol_op: MembershipOperation> = + carol_dlg.clone().into(); let carol_hash = Digest::hash(&carol_op); - let dan_op: MembershipOperation = dan_dlg.clone().into(); + let dan_op: MembershipOperation> = + dan_dlg.clone().into(); let dan_hash = Digest::hash(&dan_op); - let erin_op: MembershipOperation = erin_dlg.clone().into(); + let erin_op: MembershipOperation> = + erin_dlg.clone().into(); let erin_hash = Digest::hash(&erin_op); let mut bob_and_revoke_carol = [ diff --git a/keyhive_core/src/principal/group/revocation.rs b/keyhive_core/src/principal/group/revocation.rs index 14ffbc4c..7704ed27 100644 --- a/keyhive_core/src/principal/group/revocation.rs +++ b/keyhive_core/src/principal/group/revocation.rs @@ -7,6 +7,7 @@ use crate::{ crypto::{digest::Digest, signed::Signed, signer::async_signer::AsyncSigner}, listener::{membership::MembershipListener, no_listener::NoListener}, principal::{agent::id::AgentId, document::id::DocumentId, identifier::Identifier}, + store::secret_key::traits::ShareSecretStore, }; use derive_where::derive_where; use dupe::Dupe; @@ -17,20 +18,23 @@ use std::{collections::BTreeMap, rc::Rc}; #[derive_where(Debug, Clone; T)] pub struct Revocation< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - pub(crate) revoke: Rc>>, - pub(crate) proof: Option>>>, + pub(crate) revoke: Rc>>, + pub(crate) proof: Option>>>, pub(crate) after_content: BTreeMap>, } -impl> Revocation { +impl> + Revocation +{ pub fn subject_id(&self) -> Identifier { self.revoke.subject_id() } - pub fn revoked(&self) -> &Rc>> { + pub fn revoked(&self) -> &Rc>> { &self.revoke } @@ -38,11 +42,11 @@ impl> Revocation Option>>> { + pub fn proof(&self) -> Option>>> { self.proof.dupe() } - pub fn after(&self) -> Dependencies { + pub fn after(&self) -> Dependencies { let mut delegations = vec![self.revoke.dupe()]; if let Some(dlg) = &self.proof { delegations.push(dlg.clone()); @@ -56,14 +60,16 @@ impl> Revocation> Signed> { +impl> + Signed> +{ pub fn subject_id(&self) -> Identifier { self.payload.subject_id() } } -impl> std::hash::Hash - for Revocation +impl> + std::hash::Hash for Revocation { fn hash(&self, state: &mut H) { self.revoke.hash(state); @@ -75,7 +81,9 @@ impl> std::hash::Hash } } -impl> Serialize for Revocation { +impl> Serialize + for Revocation +{ fn serialize(&self, serializer: Z) -> Result { StaticRevocation::from(self.clone()).serialize(serializer) } @@ -94,10 +102,10 @@ pub struct StaticRevocation { pub after_content: BTreeMap>, } -impl> From> - for StaticRevocation +impl> + From> for StaticRevocation { - fn from(revocation: Revocation) -> Self { + fn from(revocation: Revocation) -> Self { Self { revoke: Digest::hash(revocation.revoke.as_ref()).into(), proof: revocation.proof.map(|p| Digest::hash(p.as_ref()).into()), diff --git a/keyhive_core/src/principal/group/state.rs b/keyhive_core/src/principal/group/state.rs index b2718200..c6840ce7 100644 --- a/keyhive_core/src/principal/group/state.rs +++ b/keyhive_core/src/principal/group/state.rs @@ -13,7 +13,10 @@ use crate::{ }, listener::{membership::MembershipListener, no_listener::NoListener}, principal::{agent::Agent, group::delegation::DelegationError, identifier::Identifier}, - store::{delegation::DelegationStore, revocation::RevocationStore}, + store::{ + delegation::DelegationStore, revocation::RevocationStore, + secret_key::traits::ShareSecretStore, + }, util::content_addressed_map::CaMap, }; use derive_where::derive_where; @@ -24,25 +27,28 @@ use std::{cmp::Ordering, collections::BTreeMap, rc::Rc}; #[derive_where(Debug, PartialEq, Hash; T)] pub struct GroupState< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { pub(crate) id: GroupId, #[derive_where(skip)] - pub(crate) delegations: DelegationStore, - pub(crate) delegation_heads: CaMap>>, + pub(crate) delegations: DelegationStore, + pub(crate) delegation_heads: CaMap>>, #[derive_where(skip)] - pub(crate) revocations: RevocationStore, - pub(crate) revocation_heads: CaMap>>, + pub(crate) revocations: RevocationStore, + pub(crate) revocation_heads: CaMap>>, } -impl> GroupState { +impl> + GroupState +{ pub fn new( - delegation_head: Rc>>, - delegations: DelegationStore, - revocations: RevocationStore, + delegation_head: Rc>>, + delegations: DelegationStore, + revocations: RevocationStore, ) -> Self { let id = GroupId(delegation_head.verifying_key().into()); let mut heads = vec![delegation_head.dupe()]; @@ -84,9 +90,9 @@ impl> GroupState( - parents: Vec>, - delegations: DelegationStore, - revocations: RevocationStore, + parents: Vec>, + delegations: DelegationStore, + revocations: RevocationStore, csprng: &mut R, ) -> Result { let signer = MemorySigner::generate(csprng); @@ -125,19 +131,19 @@ impl> GroupState &CaMap>> { + pub fn delegation_heads(&self) -> &CaMap>> { &self.delegation_heads } - pub fn revocation_heads(&self) -> &CaMap>> { + pub fn revocation_heads(&self) -> &CaMap>> { &self.revocation_heads } #[allow(clippy::type_complexity)] pub fn add_delegation( &mut self, - delegation: Rc>>, - ) -> Result>>, AddError> { + delegation: Rc>>, + ) -> Result>>, AddError> { if delegation.subject_id() != self.id.into() { return Err(AddError::InvalidSubject(Box::new(delegation.subject_id()))); } @@ -187,8 +193,8 @@ impl> GroupState>>, - ) -> Result>>, AddError> { + revocation: Rc>>, + ) -> Result>>, AddError> { if revocation.subject_id() != self.id.into() { return Err(AddError::InvalidSubject(Box::new(revocation.subject_id()))); } @@ -228,7 +234,10 @@ impl> GroupState) -> Vec>>> { + pub fn delegations_for( + &self, + agent: Agent, + ) -> Vec>>> { self.delegations .borrow() .values() @@ -244,8 +253,8 @@ impl> GroupState, - delegations: DelegationStore, - revocations: RevocationStore, + delegations: DelegationStore, + revocations: RevocationStore, ) -> Self { Self { id: archive.id, @@ -267,8 +276,8 @@ impl> GroupState> Verifiable - for GroupState +impl> Verifiable + for GroupState { fn verifying_key(&self) -> ed25519_dalek::VerifyingKey { self.id.0.verifying_key() diff --git a/keyhive_core/src/principal/membered.rs b/keyhive_core/src/principal/membered.rs index bcf3e2c1..712987b8 100644 --- a/keyhive_core/src/principal/membered.rs +++ b/keyhive_core/src/principal/membered.rs @@ -15,6 +15,7 @@ use crate::{ digest::Digest, signed::Signed, signer::async_signer::AsyncSigner, verifiable::Verifiable, }, listener::{membership::MembershipListener, no_listener::NoListener}, + store::secret_key::traits::ShareSecretStore, util::content_addressed_map::CaMap, }; use derive_where::derive_where; @@ -32,15 +33,21 @@ use std::{ #[derive_where(Debug, PartialEq; T)] pub enum Membered< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, + L: MembershipListener = NoListener, > { - Group(Rc>>), - Document(Rc>>), + Group(Rc>>), + Document(Rc>>), } -impl> Membered { - pub fn get_capability(&self, agent_id: &Identifier) -> Option>>> { +impl> + Membered +{ + pub fn get_capability( + &self, + agent_id: &Identifier, + ) -> Option>>> { match self { Membered::Group(group) => group.borrow().get_capability(agent_id).duped(), Membered::Document(doc) => doc.borrow().get_capability(agent_id).duped(), @@ -61,14 +68,14 @@ impl> Membered CaMap>> { + pub fn delegation_heads(&self) -> CaMap>> { match self { Membered::Group(group) => group.borrow().delegation_heads().clone(), Membered::Document(document) => document.borrow().delegation_heads().clone(), } } - pub fn revocation_heads(&self) -> CaMap>> { + pub fn revocation_heads(&self) -> CaMap>> { match self { Membered::Group(group) => group.borrow().revocation_heads().clone(), Membered::Document(document) => document.borrow().revocation_heads().clone(), @@ -76,7 +83,7 @@ impl> Membered HashMap>>>> { + pub fn members(&self) -> HashMap>>>> { match self { Membered::Group(group) => group.borrow().members().clone(), Membered::Document(document) => document.borrow().members().clone(), @@ -87,11 +94,11 @@ impl> Membered, + member_to_add: Agent, can: Access, signer: &S, - other_relevant_docs: &[Rc>>], - ) -> Result, AddMemberError> { + other_relevant_docs: &[Rc>>], + ) -> Result, AddMemberError> { match self { Membered::Group(group) => Ok(group .borrow_mut() @@ -114,7 +121,7 @@ impl> Membered>, - ) -> Result, RevokeMemberError> { + ) -> Result, RevokeMemberError> { match self { Membered::Group(group) => { group @@ -133,8 +140,8 @@ impl> Membered, - ) -> Vec>>> { + agent: &Agent, + ) -> Vec>>> { match self { Membered::Group(group) => group.borrow().get_agent_revocations(agent), Membered::Document(document) => document.borrow().get_agent_revocations(agent), @@ -144,8 +151,8 @@ impl> Membered>>, - ) -> Result>>, AddError> { + delegation: Rc>>, + ) -> Result>>, AddError> { match self { Membered::Group(group) => Ok(group.borrow_mut().receive_delegation(delegation)?), Membered::Document(document) => { @@ -155,23 +162,25 @@ impl> Membered> From>>> - for Membered +impl> + From>>> for Membered { - fn from(group: Rc>>) -> Self { + fn from(group: Rc>>) -> Self { Membered::Group(group) } } -impl> - From>>> for Membered +impl> + From>>> for Membered { - fn from(document: Rc>>) -> Self { + fn from(document: Rc>>) -> Self { Membered::Document(document) } } -impl> Verifiable for Membered { +impl> Verifiable + for Membered +{ fn verifying_key(&self) -> ed25519_dalek::VerifyingKey { match self { Membered::Group(group) => group.borrow().verifying_key(), diff --git a/keyhive_core/src/principal/peer.rs b/keyhive_core/src/principal/peer.rs index 0eea692d..13fa2f3c 100644 --- a/keyhive_core/src/principal/peer.rs +++ b/keyhive_core/src/principal/peer.rs @@ -9,6 +9,7 @@ use crate::{ content::reference::ContentRef, crypto::{share_key::ShareKey, signer::async_signer::AsyncSigner}, listener::{membership::MembershipListener, no_listener::NoListener}, + store::secret_key::traits::ShareSecretStore, }; use derive_more::{From, TryInto}; use derive_where::derive_where; @@ -24,13 +25,20 @@ use thiserror::Error; /// An [`Agent`] minus the current user. #[derive(From, TryInto)] #[derive_where(PartialEq, Debug; T)] -pub enum Peer = NoListener> { +pub enum Peer< + S: AsyncSigner, + K: ShareSecretStore, + T: ContentRef = [u8; 32], + L: MembershipListener = NoListener, +> { Individual(Rc>), - Group(Rc>>), - Document(Rc>>), + Group(Rc>>), + Document(Rc>>), } -impl> Peer { +impl> + Peer +{ pub fn id(&self) -> Identifier { match self { Peer::Individual(i) => i.borrow().id().into(), @@ -67,13 +75,17 @@ impl> Peer { } } -impl> Dupe for Peer { +impl> Dupe + for Peer +{ fn dupe(&self) -> Self { self.clone() } } -impl> Clone for Peer { +impl> Clone + for Peer +{ fn clone(&self) -> Self { match self { Peer::Individual(i) => Peer::Individual(i.dupe()), @@ -83,16 +95,18 @@ impl> Clone for Peer< } } -impl> Display for Peer { +impl> Display + for Peer +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.id().fmt(f) } } -impl> From> - for Agent +impl> + From> for Agent { - fn from(peer: Peer) -> Self { + fn from(peer: Peer) -> Self { match peer { Peer::Individual(individual) => Agent::Individual(individual), Peer::Group(group) => Agent::Group(group), @@ -101,12 +115,12 @@ impl> From> TryFrom> - for Peer +impl> + TryFrom> for Peer { type Error = ActiveUserIsNotAPeer; - fn try_from(agent: Agent) -> Result { + fn try_from(agent: Agent) -> Result { match agent { Agent::Individual(individual) => Ok(Peer::Individual(individual)), Agent::Group(group) => Ok(Peer::Group(group)), diff --git a/keyhive_core/src/principal/public.rs b/keyhive_core/src/principal/public.rs index b2dd7297..c3c8faa6 100644 --- a/keyhive_core/src/principal/public.rs +++ b/keyhive_core/src/principal/public.rs @@ -11,9 +11,10 @@ use crate::{ verifiable::Verifiable, }, listener::prekey::PrekeyListener, + store::secret_key::traits::ShareSecretStore, }; use dupe::Dupe; -use std::{collections::BTreeMap, rc::Rc}; +use std::rc::Rc; /// A well-known agent that can be used by anyone. ⚠ USE WITH CAUTION ⚠ /// @@ -64,13 +65,14 @@ impl Public { } } - pub fn active( + pub fn active( &self, + secret_store: K, listener: L, - ) -> Active { + ) -> Active { Active { signer: self.signer(), - prekey_pairs: BTreeMap::from_iter([(self.share_key(), self.share_secret_key())]), + secret_store, individual: self.individual(), listener, _phantom: std::marker::PhantomData, diff --git a/keyhive_core/src/store.rs b/keyhive_core/src/store.rs index 202dcf8d..8b331630 100644 --- a/keyhive_core/src/store.rs +++ b/keyhive_core/src/store.rs @@ -3,3 +3,4 @@ pub mod ciphertext; pub mod delegation; pub mod revocation; +pub mod secret_key; diff --git a/keyhive_core/src/store/ciphertext.rs b/keyhive_core/src/store/ciphertext.rs index ab8c7e7b..bee4f366 100644 --- a/keyhive_core/src/store/ciphertext.rs +++ b/keyhive_core/src/store/ciphertext.rs @@ -362,8 +362,7 @@ mod tests { use crate::{ cgka::operation::CgkaOperation, crypto::{ - application_secret::PcsKey, digest::Digest, envelope::Envelope, - share_key::ShareSecretKey, signed::Signed, siv::Siv, + digest::Digest, envelope::Envelope, share_key::ShareSecretKey, signed::Signed, siv::Siv, }, principal::document::id::DocumentId, }; @@ -379,9 +378,7 @@ mod tests { doc_id: DocumentId, csprng: &mut ThreadRng, ) -> (Rc>, SymmetricKey) { - let pcs_key: PcsKey = ShareSecretKey::generate(csprng).into(); - let pcs_key_hash = Digest::hash(&pcs_key); - + let secret_key = ShareSecretKey::generate(csprng); let key = SymmetricKey::generate(csprng); let envelope = Envelope { plaintext, @@ -396,7 +393,7 @@ mod tests { nonce, bytes, // - pcs_key_hash, + secret_key.share_key(), pcs_update_op_hash, // cref, diff --git a/keyhive_core/src/store/delegation.rs b/keyhive_core/src/store/delegation.rs index a33482e5..ed8c0852 100644 --- a/keyhive_core/src/store/delegation.rs +++ b/keyhive_core/src/store/delegation.rs @@ -1,5 +1,6 @@ //! [`Delegation`] storage. +use super::secret_key::traits::ShareSecretStore; use crate::{ content::reference::ContentRef, crypto::{digest::Digest, signed::Signed, signer::async_signer::AsyncSigner}, @@ -17,11 +18,14 @@ use std::{cell::Ref, cell::RefCell, rc::Rc}; #[derive_where(Clone, Debug; T)] pub struct DelegationStore< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, ->(pub(crate) Rc>>>>); + L: MembershipListener = NoListener, +>(pub(crate) Rc>>>>); -impl> DelegationStore { +impl> + DelegationStore +{ /// Create a new delegation store. pub fn new() -> Self { Self(Rc::new(RefCell::new(CaMap::new()))) @@ -30,22 +34,22 @@ impl> DelegationStore /// Retrieve a [`Delegation`] by its [`Digest`]. pub fn get( &self, - key: &Digest>>, - ) -> Option>>> { + key: &Digest>>, + ) -> Option>>> { let rc = self.0.dupe(); let borrowed = RefCell::borrow(&rc); borrowed.get(key).cloned() } /// Check if a [`Digest`] is present in the store. - pub fn contains_key(&self, key: &Digest>>) -> bool { + pub fn contains_key(&self, key: &Digest>>) -> bool { let rc = self.0.dupe(); let borrowed = RefCell::borrow(&rc); borrowed.contains_key(key) } /// Check if a [`Delegation`] is present in the store. - pub fn contains_value(&self, value: &Signed>) -> bool { + pub fn contains_value(&self, value: &Signed>) -> bool { let rc = self.0.dupe(); let borrowed = RefCell::borrow(&rc); borrowed.contains_value(value) @@ -54,35 +58,40 @@ impl> DelegationStore /// Insert a [`Delegation`] into the store. pub fn insert( &self, - delegation: Rc>>, - ) -> Digest>> { + delegation: Rc>>, + ) -> Digest>> { self.0.borrow_mut().insert(delegation) } - pub fn borrow(&self) -> Ref>>> { + pub fn borrow(&self) -> Ref>>> { self.0.borrow() } } -impl> PartialEq - for DelegationStore +impl> PartialEq + for DelegationStore { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -impl> Eq for DelegationStore {} +impl> Eq + for DelegationStore +{ +} -impl> std::hash::Hash - for DelegationStore +impl> + std::hash::Hash for DelegationStore { fn hash(&self, state: &mut H) { self.0.borrow().hash(state); } } -impl> Dupe for DelegationStore { +impl> Dupe + for DelegationStore +{ fn dupe(&self) -> Self { Self(self.0.dupe()) } diff --git a/keyhive_core/src/store/revocation.rs b/keyhive_core/src/store/revocation.rs index b10befd0..b99182e7 100644 --- a/keyhive_core/src/store/revocation.rs +++ b/keyhive_core/src/store/revocation.rs @@ -1,5 +1,6 @@ //! [`Revocation`] storage. +use super::secret_key::traits::ShareSecretStore; use crate::{ content::reference::ContentRef, crypto::{digest::Digest, signed::Signed, signer::async_signer::AsyncSigner}, @@ -20,11 +21,14 @@ use std::{ #[derive_where(Debug, Clone; T)] pub struct RevocationStore< S: AsyncSigner, + K: ShareSecretStore, T: ContentRef = [u8; 32], - L: MembershipListener = NoListener, ->(pub Rc>>>>); + L: MembershipListener = NoListener, +>(pub Rc>>>>); -impl> RevocationStore { +impl> + RevocationStore +{ /// Create a new revocation store. pub fn new() -> Self { Self(Rc::new(RefCell::new(CaMap::new()))) @@ -33,18 +37,18 @@ impl> RevocationStore /// Retrieve a [`Revocation`] by its [`Digest`]. pub fn get( &self, - key: &Digest>>, - ) -> Option>>> { + key: &Digest>>, + ) -> Option>>> { self.borrow().get(key).cloned() } /// Check if a [`Digest`] is present in the store. - pub fn contains_key(&self, key: &Digest>>) -> bool { + pub fn contains_key(&self, key: &Digest>>) -> bool { self.borrow().contains_key(key) } /// Check if a [`Revocation`] is present in the store. - pub fn contains_value(&self, value: &Signed>) -> bool { + pub fn contains_value(&self, value: &Signed>) -> bool { let rc = self.0.dupe(); let borrowed = RefCell::borrow(&rc); borrowed.contains_value(value) @@ -53,40 +57,45 @@ impl> RevocationStore /// Insert a [`Revocation`] into the store. pub fn insert( &self, - revocation: Rc>>, - ) -> Digest>> { + revocation: Rc>>, + ) -> Digest>> { self.0.borrow_mut().insert(revocation) } /// Get an immutable reference to the underlying [`CaMap`]. - pub fn borrow(&self) -> Ref>>> { + pub fn borrow(&self) -> Ref>>> { self.0.borrow() } /// Get a mutable reference to the underlying [`CaMap`]. - pub fn borrow_mut(&self) -> RefMut>>> { + pub fn borrow_mut(&self) -> RefMut>>> { self.0.borrow_mut() } } -impl> Dupe for RevocationStore { +impl> Dupe + for RevocationStore +{ fn dupe(&self) -> Self { Self(self.0.dupe()) } } -impl> PartialEq - for RevocationStore +impl> PartialEq + for RevocationStore { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -impl> Eq for RevocationStore {} +impl> Eq + for RevocationStore +{ +} -impl> std::hash::Hash - for RevocationStore +impl> + std::hash::Hash for RevocationStore { fn hash(&self, state: &mut H) { self.0.borrow().hash(state); diff --git a/keyhive_core/src/store/secret_key.rs b/keyhive_core/src/store/secret_key.rs new file mode 100644 index 00000000..a4e69c7d --- /dev/null +++ b/keyhive_core/src/store/secret_key.rs @@ -0,0 +1,2 @@ +pub mod memory; +pub mod traits; diff --git a/keyhive_core/src/store/secret_key/memory.rs b/keyhive_core/src/store/secret_key/memory.rs new file mode 100644 index 00000000..450fc73a --- /dev/null +++ b/keyhive_core/src/store/secret_key/memory.rs @@ -0,0 +1,92 @@ +use std::{ + collections::{BTreeSet, HashMap}, + convert::Infallible, + hash::Hash, + rc::Rc, +}; + +use dupe::Dupe; + +use crate::crypto::share_key::{AsyncSecretKey, ShareKey, ShareSecretKey}; + +use super::traits::ShareSecretStore; + +#[derive(Debug, Clone, Default)] +pub struct MemorySecretKeyStore { + pub csprng: R, + pub keys: HashMap>, +} + +impl MemorySecretKeyStore { + pub fn new(csprng: R) -> Self { + Self { + csprng, + keys: HashMap::new(), + } + } + + pub fn len(&self) -> usize { + self.keys.len() + } +} + +impl PartialEq for MemorySecretKeyStore { + fn eq(&self, other: &Self) -> bool { + self.keys == other.keys + } +} + +impl Hash for MemorySecretKeyStore { + fn hash(&self, state: &mut H) { + self.keys.keys().collect::>().hash(state); + } +} + +impl Eq for MemorySecretKeyStore {} + +impl ShareSecretStore for MemorySecretKeyStore { + type SecretKey = Rc; + + type GetSecretError = Infallible; + type GetIndexError = Infallible; + type ImportKeyError = Infallible; + type GenerateSecretError = Infallible; + + async fn get_index(&self) -> Result, Self::GetIndexError> { + Ok(self.keys.clone()) + } + + async fn get_secret_key( + &self, + public_key: &ShareKey, + ) -> Result, Self::GetSecretError> { + Ok(self.keys.get(public_key).cloned()) + } + + async fn import_secret_key( + &mut self, + secret_key: ShareSecretKey, + ) -> Result { + let rc = Rc::new(secret_key); + self.keys.insert(secret_key.share_key(), rc.dupe()); + Ok(rc) + } + + async fn import_secret_key_directly( + &mut self, + secret_key: Self::SecretKey, + ) -> Result { + self.keys + .insert(secret_key.to_share_key(), secret_key.dupe()); + Ok(secret_key) + } + + async fn generate_share_secret_key( + &mut self, + ) -> Result { + let sk = Rc::new(ShareSecretKey::generate(&mut self.csprng)); + let pk = sk.share_key(); + self.keys.insert(pk, sk.dupe()); + Ok(sk) + } +} diff --git a/keyhive_core/src/store/secret_key/traits.rs b/keyhive_core/src/store/secret_key/traits.rs new file mode 100644 index 00000000..ea5cf081 --- /dev/null +++ b/keyhive_core/src/store/secret_key/traits.rs @@ -0,0 +1,84 @@ +use crate::crypto::{ + encrypted::EncryptedSecret, + share_key::{AsyncSecretKey, ShareKey, ShareSecretKey}, +}; +use derive_where::derive_where; +use std::{ + collections::HashMap, + fmt::{Debug, Display}, + future::Future, +}; +use thiserror::Error; + +pub trait ShareSecretStore: Clone { + type SecretKey: AsyncSecretKey + Debug + Clone; + + type GetSecretError: Debug + Display; + type GetIndexError: Debug + Display; + type ImportKeyError: Debug + Display; + type GenerateSecretError: Debug + Display; + + fn get_index( + &self, + ) -> impl Future, Self::GetIndexError>>; + + fn get_secret_key( + &self, + public_key: &ShareKey, + ) -> impl Future, Self::GetSecretError>>; + + fn import_secret_key( + &mut self, + secret_key: ShareSecretKey, + ) -> impl Future>; + + fn import_secret_key_directly( + &mut self, + secret_key: Self::SecretKey, + ) -> impl Future>; + + fn generate_share_secret_key( + &mut self, + ) -> impl Future>; + + fn try_decrypt_encryption( + &self, + encrypter_pk: ShareKey, + encrypted: &EncryptedSecret, + ) -> impl Future, DecryptionError>> { + async move { + let sk = self + .get_secret_key(&encrypted.paired_pk) + .await + .map_err(DecryptionError::GetSecretError)? + .ok_or(DecryptionError::CannotFindKey(encrypted.paired_pk))?; + + let key = sk + .derive_symmetric_key(encrypter_pk) + .await + .map_err(DecryptionError::EcdhError)?; + + let mut buf = encrypted.ciphertext.clone(); + key.try_decrypt(encrypted.nonce, &mut buf) + .map_err(DecryptionError::DecryptionError)?; + + Ok(buf) + } + } +} + +#[derive(Error)] +#[derive_where(Debug)] +pub enum DecryptionError { + #[error("Failed to decrypt the ciphertext: {0}")] + DecryptionError(chacha20poly1305::Error), + + #[error("Failed to find the secret key for the given public key: {0}")] + CannotFindKey(ShareKey), + + #[error("Failed to get the secret key: {0}")] + GetSecretError(K::GetSecretError), + + #[error("ECDH error: {0}")] + EcdhError(::EcdhError), +} diff --git a/keyhive_core/src/test_utils.rs b/keyhive_core/src/test_utils.rs index a9ab5cde..98cf2b84 100644 --- a/keyhive_core/src/test_utils.rs +++ b/keyhive_core/src/test_utils.rs @@ -1,23 +1,36 @@ use crate::{ - crypto::{signed::SigningError, signer::memory::MemorySigner}, + crypto::signer::memory::MemorySigner, keyhive::Keyhive, listener::no_listener::NoListener, - store::ciphertext::memory::MemoryCiphertextStore, + principal::active::GenerateActiveError, + store::{ciphertext::memory::MemoryCiphertextStore, secret_key::memory::MemorySecretKeyStore}, }; use rand::rngs::ThreadRng; +use std::collections::HashMap; pub async fn make_simple_keyhive() -> Result< Keyhive< MemorySigner, + MemorySecretKeyStore, [u8; 32], Vec, MemoryCiphertextStore<[u8; 32], Vec>, NoListener, ThreadRng, >, - SigningError, + GenerateActiveError>, > { let mut csprng = rand::thread_rng(); - let sk = MemorySigner::generate(&mut csprng); - Keyhive::generate(sk, MemoryCiphertextStore::new(), NoListener, csprng).await + + Keyhive::generate( + MemorySigner::generate(&mut csprng), + MemorySecretKeyStore { + csprng: rand::thread_rng(), + keys: HashMap::new(), + }, + MemoryCiphertextStore::new(), + NoListener, + csprng, + ) + .await } diff --git a/keyhive_core/src/util.rs b/keyhive_core/src/util.rs index 69aba684..c1607608 100644 --- a/keyhive_core/src/util.rs +++ b/keyhive_core/src/util.rs @@ -2,7 +2,6 @@ pub mod content_addressed_map; -pub(crate) mod debug; pub(crate) mod hasher; pub(crate) mod hex; pub(crate) mod partial_eq; diff --git a/keyhive_core/src/util/debug.rs b/keyhive_core/src/util/debug.rs deleted file mode 100644 index f555cb0c..00000000 --- a/keyhive_core/src/util/debug.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::crypto::share_key::{ShareKey, ShareSecretKey}; -use std::{collections::BTreeMap, fmt::Debug}; - -pub(crate) fn prekey_fmt( - prekey_pairs: &BTreeMap, - f: &mut std::fmt::Formatter, -) -> Result<(), std::fmt::Error> { - Debug::fmt(&prekey_pairs.keys(), f) -} diff --git a/keyhive_core/src/util/hasher.rs b/keyhive_core/src/util/hasher.rs index e73362b0..b7410482 100644 --- a/keyhive_core/src/util/hasher.rs +++ b/keyhive_core/src/util/hasher.rs @@ -1,14 +1,8 @@ use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, + collections::{BTreeSet, HashSet}, hash::{DefaultHasher, Hash, Hasher}, }; -pub(crate) fn keys(tree: &BTreeMap, state: &mut H) { - for k in tree.keys() { - std::hash::Hash::hash(k, state); - } -} - pub(crate) fn hash_set(tree: &HashSet, state: &mut H) { tree.iter() .map(|k| { diff --git a/keyhive_core/src/util/partial_eq.rs b/keyhive_core/src/util/partial_eq.rs index 9598d498..cb52cef8 100644 --- a/keyhive_core/src/util/partial_eq.rs +++ b/keyhive_core/src/util/partial_eq.rs @@ -1,18 +1,31 @@ -use crate::crypto::share_key::{ShareKey, ShareSecretKey}; use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeSet, HashMap}, hash::{DefaultHasher, Hash, Hasher}, }; -pub(crate) fn prekey_partial_eq( - xs: &BTreeMap, - ys: &BTreeMap, +pub(crate) fn hash_map_key_partial_eq( + map1: &HashMap, + map2: &HashMap, ) -> bool { - xs.len() == ys.len() - && xs - .iter() - .zip(ys.iter()) - .all(|((xk, xv), (yk, yv))| xk == yk && xv.to_bytes() == yv.to_bytes()) + let ordered1: BTreeSet<_> = map1 + .keys() + .map(|k| { + let mut hasher = DefaultHasher::new(); + (*k).hash(&mut hasher); + hasher.finish() + }) + .collect(); + + let ordered2: BTreeSet<_> = map2 + .keys() + .map(|k| { + let mut hasher = DefaultHasher::new(); + (*k).hash(&mut hasher); + hasher.finish() + }) + .collect(); + + ordered1 == ordered2 } #[allow(dead_code)] // Not dead code; just used in a macro diff --git a/keyhive_core/tests/encrypt.rs b/keyhive_core/tests/encrypt.rs index d348ccee..c48e3484 100644 --- a/keyhive_core/tests/encrypt.rs +++ b/keyhive_core/tests/encrypt.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{collections::HashMap, rc::Rc}; use keyhive_core::{ access::Access, @@ -6,30 +6,36 @@ use keyhive_core::{ event::static_event::StaticEvent, keyhive::Keyhive, listener::{log::Log, no_listener::NoListener}, - store::ciphertext::memory::MemoryCiphertextStore, + store::{ciphertext::memory::MemoryCiphertextStore, secret_key::memory::MemorySecretKeyStore}, }; use nonempty::nonempty; +use rand::rngs::ThreadRng; use testresult::TestResult; #[allow(clippy::type_complexity)] struct NewKeyhive { signer: MemorySigner, - log: Log, + log: Log>, keyhive: Keyhive< MemorySigner, + MemorySecretKeyStore, [u8; 32], Vec, MemoryCiphertextStore<[u8; 32], Vec>, - Log, - rand::rngs::ThreadRng, + Log>, + ThreadRng, >, } async fn make_keyhive() -> NewKeyhive { let sk = MemorySigner::generate(&mut rand::thread_rng()); let store: MemoryCiphertextStore<[u8; 32], Vec> = MemoryCiphertextStore::new(); + let keystore = MemorySecretKeyStore { + csprng: rand::thread_rng(), + keys: HashMap::new(), + }; let log = Log::new(); - let keyhive = Keyhive::generate(sk.clone(), store, log.clone(), rand::thread_rng()) + let keyhive = Keyhive::generate(sk.clone(), keystore, store, log.clone(), rand::thread_rng()) .await .unwrap(); NewKeyhive { @@ -78,7 +84,9 @@ async fn test_encrypt_to_added_member() -> TestResult { // Now attempt to decrypt on bob let doc_on_bob = bob.get_document(doc.borrow().doc_id()).unwrap(); - let decrypted = bob.try_decrypt_content(doc_on_bob.clone(), encrypted.encrypted_content())?; + let decrypted = bob + .try_decrypt_content(doc_on_bob.clone(), encrypted.encrypted_content()) + .await?; assert_eq!(decrypted, init_content); Ok(()) @@ -111,10 +119,15 @@ async fn test_decrypt_after_to_from_archive() { let mut alice = Keyhive::try_from_archive( &archive, sk, + MemorySecretKeyStore { + csprng: rand::thread_rng(), + keys: HashMap::new(), + }, MemoryCiphertextStore::new(), NoListener, rand::thread_rng(), ) + .await .unwrap(); let mut events = Vec::new(); while let Some(evt) = log.pop() { @@ -126,6 +139,7 @@ async fn test_decrypt_after_to_from_archive() { let decrypted = alice .try_decrypt_content(doc.clone(), encrypted.encrypted_content()) + .await .unwrap(); assert_eq!(decrypted, init_content); @@ -176,10 +190,15 @@ async fn test_decrypt_after_fork_and_merge() { let mut keyhive = Keyhive::try_from_archive( &archive1, sk.clone(), + MemorySecretKeyStore { + csprng: rand::thread_rng(), + keys: HashMap::new(), + }, MemoryCiphertextStore::<[u8; 32], Vec>::new(), Log::new(), rand::thread_rng(), ) + .await .unwrap(); keyhive.ingest_archive(archive2).await.unwrap(); @@ -192,6 +211,7 @@ async fn test_decrypt_after_fork_and_merge() { let decrypted = reloaded .try_decrypt_content(doc.clone(), encrypted.encrypted_content()) + .await .unwrap(); assert_eq!(decrypted, init_content); diff --git a/keyhive_wasm/Cargo.toml b/keyhive_wasm/Cargo.toml index 46fdff03..3e3ace54 100644 --- a/keyhive_wasm/Cargo.toml +++ b/keyhive_wasm/Cargo.toml @@ -46,8 +46,9 @@ features = ["Crypto", "CryptoKey", "CryptoKeyPair", "Storage", "SubtleCrypto", " optional = false [profile.release] -wasm-opt = ["-Oz"] +opt-level = "z" lto = true +codegen-units = 1 [features] default = ["console_error_panic_hook", "web-sys"]