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
15use 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
37struct 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 #[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}