use std::{
cell::RefCell,
collections::{BTreeSet, HashMap},
rc::Rc,
};
use litl::{impl_debug_as_litl};
use mofo::Mofo;
use ridl::{
asymm_encr::RecipientSecret,
signing::{Signed, SignerSecret},
symm_encr::{ KeySecret},
};
use caro::{Remote, SetItem, SetItemID};
use serde_derive::{Deserialize, Serialize};
mod claims_and_permissions_v1;
mod credential_source;
mod scope;
use claims_and_permissions_v1::CredoV1Claim;
pub use claims_and_permissions_v1::{ClaimBody, PermissionKind, WRITE_TO_SCOPE};
use scope::{
ScopeSetHeader,
};
pub use scope::{
scope_state::{ScopeSecretRecipientState, ScopeState, ValidClaims},
Scope, ScopeID,
MakeClaimError
};
pub use credential_source::{CredentialSource, SimpleCredentialSource, CredentialSourceHash};
struct CredoInner {
name: String,
telepathy: caro::Node,
scopes: HashMap<(ScopeID, CredentialSourceHash), Scope>,
background: Mofo,
}
#[derive(Clone)]
pub struct Credo(Rc<RefCell<CredoInner>>);
impl Credo {
pub fn new(name: String, background: Mofo) -> Credo {
let telepathy = caro::Node::new(background.clone());
Self::new_with_telepathy(name, telepathy, background)
}
pub fn new_with_telepathy(name: String, telepathy: caro::Node, background: Mofo) -> Credo {
Credo(Rc::new(RefCell::new(CredoInner {
name,
telepathy,
scopes: HashMap::new(),
background,
})))
}
fn name(&self) -> String {
self.0.borrow().name.clone()
}
pub async fn create_scope(
&self,
initial_recipient: Credential,
initial_permissions: Vec<PermissionKind>,
parent_scope: Option<ScopeID>,
credential_source: Box<dyn CredentialSource>,
) -> Scope {
let mut bootstrap_claims = Vec::<SetItem>::new();
for initial_permission in initial_permissions.into_iter().chain([
PermissionKind::AddSharedSecretRecipient {
secret_kind: WRITE_TO_SCOPE.to_string(),
},
PermissionKind::RevealSharedSecret {
secret_kind: WRITE_TO_SCOPE.to_string(),
},
PermissionKind::EntrustToSharedSecret {
secret_kind: WRITE_TO_SCOPE.to_string(),
},
]) {
bootstrap_claims.push(SetItem::new(
&Claim::new_v1(
&initial_recipient.for_making_claims,
ClaimBody::Permission {
to: initial_recipient.for_making_claims.pub_id(),
permitted: initial_permission,
as_of: ti64::now(),
},
),
bootstrap_claims.last().map(|item| item.id()),
));
}
bootstrap_claims.push(SetItem::new(
&Claim::new_v1(
&initial_recipient.for_making_claims,
ClaimBody::AddSharedSecretRecipient {
secret_kind: WRITE_TO_SCOPE.to_string(),
recipient: initial_recipient.for_accepting_secrets.pub_id(),
},
),
bootstrap_claims.last().map(|item| item.id()),
));
if let Some(parent_scope) = parent_scope {
bootstrap_claims.push(SetItem::new(
&Claim::new_v1(
&initial_recipient.for_making_claims,
ClaimBody::InheritFrom {
parent: parent_scope,
},
),
bootstrap_claims.last().map(|item| item.id()),
));
}
let write_to_scope_shared_secret = KeySecret::new_random();
let initial_secret_recipient = initial_recipient.for_accepting_secrets.pub_id();
bootstrap_claims.push(SetItem::new(
&Claim::new_v1(
&initial_recipient.for_making_claims,
ClaimBody::AddSharedSecretRecipient {
secret_kind: WRITE_TO_SCOPE.to_string(),
recipient: initial_secret_recipient.clone(),
},
),
bootstrap_claims.last().map(|item| item.id()),
));
bootstrap_claims.push(SetItem::new(
&Claim::new_v1(
&initial_recipient.for_making_claims,
ClaimBody::RevealSharedSecret {
secret_kind: WRITE_TO_SCOPE.to_string(),
key_id: write_to_scope_shared_secret.id,
encrypted_per_recipient: [(
initial_secret_recipient.clone(),
initial_secret_recipient.encrypt_from_anon(&write_to_scope_shared_secret),
)]
.into_iter()
.collect(),
},
),
bootstrap_claims.last().map(|item| item.id()),
));
let telepathy = self
.0
.borrow()
.telepathy.clone();
let (set_id, set_access) = telepathy
.create_set(Some(ScopeSetHeader {
bootstrap_claims: bootstrap_claims
.iter()
.map(|claim_item| claim_item.id())
.collect::<BTreeSet<_>>(),
}))
.await;
let mut last_claim_id = bootstrap_claims
.last()
.as_ref()
.expect("Expected at least one bootstrap claim")
.id();
let scope = self.get_scope(&ScopeID(set_id), credential_source.clone_ref());
for claim_item in bootstrap_claims {
scope.add_claim_raw(&set_access, claim_item).await.unwrap();
}
credential_source
.add_credential(scope.id(), initial_recipient.clone())
.await;
let mut revealed_set_access_to_at_least_one_key = false;
for (_, maybe_key_secret) in scope
.current_shared_secrets_for(WRITE_TO_SCOPE)
.await
.unwrap()
{
if let Ok(key_secret) = maybe_key_secret {
last_claim_id = scope
.add_claim(
&set_access,
Claim::new_v1(
&initial_recipient.for_making_claims,
ClaimBody::EntrustToSharedSecret {
secret_kind: WRITE_TO_SCOPE.to_string(),
entrusted_secret_name: ScopeID(set_id).to_string(),
for_key_id: key_secret.id,
encrypted: key_secret.encrypt(&set_access).as_encrypted_value(),
},
),
Some(last_claim_id),
)
.await
.unwrap();
revealed_set_access_to_at_least_one_key = true;
}
}
if !revealed_set_access_to_at_least_one_key {
panic!("Expected to reveal set access to at least one key");
}
scope
}
pub fn get_scope(
&self,
scope_id: &ScopeID,
credential_source: Box<dyn CredentialSource>,
) -> Scope {
let scope_key = (*scope_id, credential_source.id_hash());
if let Some(scope) = self.0.borrow().scopes.get(&scope_key) {
return scope.clone();
}
let telepathy = (*self.0).borrow().telepathy.clone();
let background = (*self.0).borrow().background.clone();
let scope = Scope::new(
self.clone(),
*scope_id,
telepathy,
credential_source,
background,
);
self.0.borrow_mut().scopes.insert(scope_key, scope.clone());
scope
}
pub async fn add_remote(&self, remote: Remote) {
let telepathy = (*self.0).borrow_mut().telepathy.clone();
telepathy.add_remote(remote).await
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Credential {
#[serde(with = "ser_rc")]
pub for_accepting_secrets: Rc<RecipientSecret>,
#[serde(with = "ser_rc")]
pub for_making_claims: Rc<SignerSecret>,
}
mod ser_rc {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::rc::Rc;
pub fn serialize<S, T>(value: &Rc<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
value.as_ref().serialize(serializer)
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Rc<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Ok(Rc::new(T::deserialize(deserializer)?))
}
}
impl Credential {
pub fn new_random() -> Self {
Self {
for_accepting_secrets: Rc::new(RecipientSecret::new_random()),
for_making_claims: Rc::new(SignerSecret::new_random()),
}
}
}
pub type ClaimID = SetItemID;
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "version")]
pub enum Claim {
#[serde(rename = "v1")]
CredoV1(Signed<CredoV1Claim>),
}
impl_debug_as_litl!(Claim);
impl Claim {
pub fn expect_v1(&self) -> &Signed<CredoV1Claim> {
match self {
Claim::CredoV1(claim) => claim,
}
}
pub fn new_v1(signer_secret: &SignerSecret, kind: ClaimBody) -> Self {
Claim::CredoV1(signer_secret.sign(CredoV1Claim {
by: signer_secret.pub_id(),
made_at: ti64::now(),
body: kind,
}))
}
}