credo/
lib.rs

1use std::{
2    cell::RefCell,
3    collections::{BTreeSet, HashMap},
4    rc::Rc,
5};
6
7use litl::{impl_debug_as_litl};
8use mofo::Mofo;
9use ridl::{
10    asymm_encr::RecipientSecret,
11    signing::{Signed, SignerSecret},
12    symm_encr::{ KeySecret},
13};
14
15// TODO(refactor): #35 re-export Remote, RemoteMode
16use caro::{Remote, SetItem, SetItemID};
17
18use serde_derive::{Deserialize, Serialize};
19
20mod claims_and_permissions_v1;
21mod credential_source;
22mod scope;
23
24use claims_and_permissions_v1::CredoV1Claim;
25pub use claims_and_permissions_v1::{ClaimBody, PermissionKind, WRITE_TO_SCOPE};
26use scope::{
27    ScopeSetHeader,
28};
29pub use scope::{
30    scope_state::{ScopeSecretRecipientState, ScopeState, ValidClaims},
31    Scope, ScopeID,
32    MakeClaimError
33};
34
35pub use credential_source::{CredentialSource, SimpleCredentialSource, CredentialSourceHash};
36
37// TODO(design): #38 build this as a peer-to-peer prototcol, with explicit rights for seeing scopes and time witnessing and then just make the credo backend a default trusted one (configurable)
38
39// TODO(design): #39 listeners are only interested in valid claims changing, which happens on: new claims & timestamps reached
40
41struct CredoInner {
42    name: String,
43    telepathy: caro::Node,
44    scopes: HashMap<(ScopeID, CredentialSourceHash), Scope>,
45    background: Mofo,
46}
47
48#[derive(Clone)]
49pub struct Credo(Rc<RefCell<CredoInner>>);
50
51impl Credo {
52    pub fn new(name: String, background: Mofo) -> Credo {
53        let telepathy = caro::Node::new(background.clone());
54
55        Self::new_with_telepathy(name, telepathy, background)
56    }
57
58    pub fn new_with_telepathy(name: String, telepathy: caro::Node, background: Mofo) -> Credo {
59        Credo(Rc::new(RefCell::new(CredoInner {
60            name,
61            telepathy,
62            scopes: HashMap::new(),
63            background,
64        })))
65    }
66
67    fn name(&self) -> String {
68        self.0.borrow().name.clone()
69    }
70
71    pub async fn create_scope(
72        &self,
73        initial_recipient: Credential,
74        initial_permissions: Vec<PermissionKind>,
75        parent_scope: Option<ScopeID>,
76        credential_source: Box<dyn CredentialSource>,
77    ) -> Scope {
78        let mut bootstrap_claims = Vec::<SetItem>::new();
79
80        for initial_permission in initial_permissions.into_iter().chain([
81            PermissionKind::AddSharedSecretRecipient {
82                secret_kind: WRITE_TO_SCOPE.to_string(),
83            },
84            PermissionKind::RevealSharedSecret {
85                secret_kind: WRITE_TO_SCOPE.to_string(),
86            },
87            PermissionKind::EntrustToSharedSecret {
88                secret_kind: WRITE_TO_SCOPE.to_string(),
89            },
90        ]) {
91            bootstrap_claims.push(SetItem::new(
92                &Claim::new_v1(
93                    &initial_recipient.for_making_claims,
94                    ClaimBody::Permission {
95                        to: initial_recipient.for_making_claims.pub_id(),
96                        permitted: initial_permission,
97                        as_of: ti64::now(),
98                    },
99                ),
100                bootstrap_claims.last().map(|item| item.id()),
101            ));
102        }
103
104        bootstrap_claims.push(SetItem::new(
105            &Claim::new_v1(
106                &initial_recipient.for_making_claims,
107                ClaimBody::AddSharedSecretRecipient {
108                    secret_kind: WRITE_TO_SCOPE.to_string(),
109                    recipient: initial_recipient.for_accepting_secrets.pub_id(),
110                },
111            ),
112            bootstrap_claims.last().map(|item| item.id()),
113        ));
114
115        if let Some(parent_scope) = parent_scope {
116            bootstrap_claims.push(SetItem::new(
117                &Claim::new_v1(
118                    &initial_recipient.for_making_claims,
119                    ClaimBody::InheritFrom {
120                        parent: parent_scope,
121                    },
122                ),
123                bootstrap_claims.last().map(|item| item.id()),
124            ));
125        }
126
127        let write_to_scope_shared_secret = KeySecret::new_random();
128        let initial_secret_recipient = initial_recipient.for_accepting_secrets.pub_id();
129
130        bootstrap_claims.push(SetItem::new(
131            &Claim::new_v1(
132                &initial_recipient.for_making_claims,
133                ClaimBody::AddSharedSecretRecipient {
134                    secret_kind: WRITE_TO_SCOPE.to_string(),
135                    recipient: initial_secret_recipient.clone(),
136                },
137            ),
138            bootstrap_claims.last().map(|item| item.id()),
139        ));
140
141        bootstrap_claims.push(SetItem::new(
142            &Claim::new_v1(
143                &initial_recipient.for_making_claims,
144                ClaimBody::RevealSharedSecret {
145                    secret_kind: WRITE_TO_SCOPE.to_string(),
146                    key_id: write_to_scope_shared_secret.id,
147                    encrypted_per_recipient: [(
148                        initial_secret_recipient.clone(),
149                        initial_secret_recipient.encrypt_from_anon(&write_to_scope_shared_secret),
150                    )]
151                    .into_iter()
152                    .collect(),
153                },
154            ),
155            bootstrap_claims.last().map(|item| item.id()),
156        ));
157
158        let telepathy = self
159        .0
160        .borrow()
161        .telepathy.clone();
162
163        let (set_id, set_access) = telepathy
164            .create_set(Some(ScopeSetHeader {
165                bootstrap_claims: bootstrap_claims
166                    .iter()
167                    .map(|claim_item| claim_item.id())
168                    .collect::<BTreeSet<_>>(),
169            }))
170            .await;
171
172        let mut last_claim_id = bootstrap_claims
173            .last()
174            .as_ref()
175            .expect("Expected at least one bootstrap claim")
176            .id();
177
178        let scope = self.get_scope(&ScopeID(set_id), credential_source.clone_ref());
179
180        for claim_item in bootstrap_claims {
181            scope.add_claim_raw(&set_access, claim_item).await.unwrap();
182        }
183
184        credential_source
185            .add_credential(scope.id(), initial_recipient.clone())
186            .await;
187
188        let mut revealed_set_access_to_at_least_one_key = false;
189        for (_, maybe_key_secret) in scope
190            .current_shared_secrets_for(WRITE_TO_SCOPE)
191            .await
192            .unwrap()
193        {
194            if let Ok(key_secret) = maybe_key_secret {
195                last_claim_id = scope
196                    .add_claim(
197                        &set_access,
198                        Claim::new_v1(
199                            &initial_recipient.for_making_claims,
200                            ClaimBody::EntrustToSharedSecret {
201                                secret_kind: WRITE_TO_SCOPE.to_string(),
202                                entrusted_secret_name: ScopeID(set_id).to_string(),
203                                for_key_id: key_secret.id,
204                                encrypted: key_secret.encrypt(&set_access).as_encrypted_value(),
205                            },
206                        ),
207                        Some(last_claim_id),
208                    )
209                    .await
210                    .unwrap();
211                revealed_set_access_to_at_least_one_key = true;
212            }
213        }
214
215        if !revealed_set_access_to_at_least_one_key {
216            panic!("Expected to reveal set access to at least one key");
217        }
218
219        scope
220    }
221
222    pub fn get_scope(
223        &self,
224        scope_id: &ScopeID,
225        credential_source: Box<dyn CredentialSource>,
226    ) -> Scope {
227        let scope_key = (*scope_id, credential_source.id_hash());
228
229        if let Some(scope) = self.0.borrow().scopes.get(&scope_key) {
230            return scope.clone();
231        }
232
233        let telepathy = (*self.0).borrow().telepathy.clone();
234        let background = (*self.0).borrow().background.clone();
235        let scope = Scope::new(
236            self.clone(),
237            *scope_id,
238            telepathy,
239            credential_source,
240            background,
241        );
242        self.0.borrow_mut().scopes.insert(scope_key, scope.clone());
243
244        scope
245    }
246
247    pub async fn add_remote(&self, remote: Remote) {
248        let telepathy = (*self.0).borrow_mut().telepathy.clone();
249        telepathy.add_remote(remote).await
250    }
251}
252
253#[derive(Clone, Serialize, Deserialize)]
254pub struct Credential {
255    #[serde(with = "ser_rc")]
256    pub for_accepting_secrets: Rc<RecipientSecret>,
257    #[serde(with = "ser_rc")]
258    pub for_making_claims: Rc<SignerSecret>,
259}
260
261mod ser_rc {
262    use serde::{Deserialize, Deserializer, Serialize, Serializer};
263    use std::rc::Rc;
264
265    pub fn serialize<S, T>(value: &Rc<T>, serializer: S) -> Result<S::Ok, S::Error>
266    where
267        S: Serializer,
268        T: Serialize,
269    {
270        value.as_ref().serialize(serializer)
271    }
272
273    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Rc<T>, D::Error>
274    where
275        D: Deserializer<'de>,
276        T: Deserialize<'de>,
277    {
278        Ok(Rc::new(T::deserialize(deserializer)?))
279    }
280}
281
282impl Credential {
283    pub fn new_random() -> Self {
284        Self {
285            for_accepting_secrets: Rc::new(RecipientSecret::new_random()),
286            for_making_claims: Rc::new(SignerSecret::new_random()),
287        }
288    }
289}
290
291pub type ClaimID = SetItemID;
292
293#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
294#[serde(tag = "version")]
295pub enum Claim {
296    //TODO-V1(optimization): Vec<CredoV1Claim> to save on signatures
297    #[serde(rename = "v1")]
298    CredoV1(Signed<CredoV1Claim>),
299}
300
301impl_debug_as_litl!(Claim);
302
303impl Claim {
304    pub fn expect_v1(&self) -> &Signed<CredoV1Claim> {
305        match self {
306            Claim::CredoV1(claim) => claim,
307        }
308    }
309
310    pub fn new_v1(signer_secret: &SignerSecret, kind: ClaimBody) -> Self {
311        Claim::CredoV1(signer_secret.sign(CredoV1Claim {
312            by: signer_secret.pub_id(),
313            made_at: ti64::now(),
314            body: kind,
315        }))
316    }
317}