credo/scope/
scope_state.rs

1use std::collections::{BTreeSet, HashMap};
2
3use litl::impl_debug_as_litl;
4use ridl::{
5    asymm_encr::{AsymmDecryptionError, RecipientID},
6    hashing::HashOf,
7    symm_encr::{KeyID, KeySecret},
8};
9use serde_derive::{Deserialize, Serialize};
10use thiserror::Error;
11use ti64::MsSinceEpoch;
12
13use crate::{claims_and_permissions_v1::CredoV1Claim, ClaimID, ClaimBody, Credential, ScopeID};
14
15use super::combined_claim_set::ClaimInvalidReason;
16
17pub type ValidClaims = HashMap<ClaimID, (CredoV1Claim, ScopeID)>;
18pub type InvalidClaims = HashMap<ClaimID, ClaimInvalidReason>;
19pub type SecretRecipients = HashMap<String, BTreeSet<RecipientID>>;
20pub type ScopeSecretRecipientState = HashOf<SecretRecipients>;
21
22#[derive(Clone, Serialize, Deserialize)]
23pub struct ScopeState {
24    pub valid_claims: ValidClaims,
25    pub invalid_claims: InvalidClaims,
26    pub secret_recipients: SecretRecipients,
27}
28
29impl_debug_as_litl!(ScopeState);
30
31impl ScopeState {
32    pub fn secret_state(&self) -> ScopeSecretRecipientState {
33        HashOf::hash(&self.secret_recipients)
34    }
35
36    pub fn newest_shared_secret_ids(&self, secret_kind: &str) -> HashMap<ScopeID, KeyID> {
37        let mut newest_shared_secret_per_scope = HashMap::<ScopeID, (KeyID, MsSinceEpoch)>::new();
38
39        for (claim, source_scope) in self.valid_claims.values() {
40            if let ClaimBody::RevealSharedSecret {
41                key_id,
42                secret_kind: revealed_secret_kind,
43                ..
44            } = &claim.body
45            {
46                if secret_kind == revealed_secret_kind {
47                    match newest_shared_secret_per_scope.entry(*source_scope) {
48                        std::collections::hash_map::Entry::Occupied(mut existing) => {
49                            if existing.get().1 < claim.made_at {
50                                existing.insert((*key_id, claim.made_at));
51                            }
52                        }
53                        std::collections::hash_map::Entry::Vacant(vacant) => {
54                            vacant.insert((*key_id, claim.made_at));
55                        }
56                    }
57                }
58            }
59        }
60
61        newest_shared_secret_per_scope
62            .into_iter()
63            .map(|(scope, (key_id, _))| (scope, key_id))
64            .collect()
65    }
66
67    pub fn get_current_shared_secrets(
68        &self,
69        secret_kind: &str,
70        credentials_to_try: &[Credential],
71    ) -> Result<HashMap<ScopeID, Result<KeySecret, GetSharedSecretError>>, GetSharedSecretError>
72    {
73        let newest_key_ids = self.newest_shared_secret_ids(secret_kind);
74
75        if newest_key_ids.is_empty() {
76            return Err(GetSharedSecretError::NoNewestKeyId);
77        }
78
79        Ok(newest_key_ids
80            .into_iter()
81            .map(|(scope, key_id)| {
82                (
83                    scope,
84                    self.get_shared_secret(secret_kind, key_id, credentials_to_try),
85                )
86            })
87            .collect())
88    }
89
90    pub fn get_shared_secret(
91        &self,
92        shared_secret_kind: &str,
93        shared_secret_key_id: KeyID,
94        credentials_to_try: &[Credential],
95    ) -> Result<KeySecret, GetSharedSecretError> {
96        let mut encrypted_per_recipient_entries = self
97            .valid_claims
98            .iter()
99            .filter_map(|(_, (claim, _))| match &claim.body {
100                ClaimBody::RevealSharedSecret {
101                    key_id,
102                    encrypted_per_recipient,
103                    secret_kind,
104                } if key_id == &shared_secret_key_id && secret_kind == shared_secret_kind => {
105                    Some(encrypted_per_recipient)
106                }
107                _ => None,
108            })
109            .flatten()
110            .peekable();
111
112        if encrypted_per_recipient_entries.peek().is_none() {
113            Err(GetSharedSecretError::NoRevelationFound(
114                shared_secret_kind.to_string(),
115                shared_secret_key_id,
116            ))
117        } else if let Some((credential, matching_encrypted_key)) = encrypted_per_recipient_entries
118            .find_map(|(recipent, encrypted_key)| {
119                credentials_to_try.iter().find_map(|credential| {
120                    if recipent == &credential.for_accepting_secrets.pub_id() {
121                        Some((credential, encrypted_key))
122                    } else {
123                        None
124                    }
125                })
126            })
127        {
128            Ok(credential
129                .for_accepting_secrets
130                .decrypt(matching_encrypted_key)?)
131        } else {
132            Err(GetSharedSecretError::NoRevelationFoundForCredentials(
133                shared_secret_kind.to_string(),
134                shared_secret_key_id,
135                credentials_to_try
136                    .iter()
137                    .map(|c| c.for_accepting_secrets.pub_id())
138                    .collect(),
139            ))
140        }
141    }
142}
143
144#[derive(Error, Debug)]
145pub enum GetSharedSecretError {
146    #[error("Could not find newest key id")]
147    NoNewestKeyId,
148    #[error("Could not find revelation of shared secret of kind {0}, id {1:?}")]
149    NoRevelationFound(String, KeyID),
150    #[error("Could not find revelation of shared secret of kind {0}, id {1:?} for available credentials {2:?}")]
151    NoRevelationFoundForCredentials(String, KeyID, Vec<RecipientID>),
152    #[error("No credentials for scope")]
153    NoCredentialsForScope,
154    #[error(transparent)]
155    DecryptionError(#[from] AsymmDecryptionError),
156}