use core::marker::PhantomData;
use crate::freshness::{FreshnessStore, FreshnessToken, InMemoryFreshness};
use crate::grant::{Grant, RedeemedGrant};
use crate::operation::Operation;
use crate::phases::{
consumption::{
add_credential_after_lifecycle, execute_export, execute_lifecycle, execute_use, open,
remove_credential_after_lifecycle, ExportArtifact, LifecycleOutput, Mutation, OpenedState,
},
grant::{redeem, RedeemInputs, RedeemerPolicy},
setup::{run as run_setup, SetupInputs, SetupOutputs},
};
use crate::primitives::{Authenticator, PrimitiveSuite};
use crate::state::{ProtectedState, SealedState};
use crate::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConveyancePayload {
pub o: Operation,
#[serde(with = "crate::wire::b64bytes")]
pub r: Vec<u8>,
pub credentials: Vec<ConveyanceCredential>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConveyanceCredential {
#[serde(with = "crate::wire::b64bytes")]
pub credential_id: Vec<u8>,
#[serde(with = "crate::wire::b64bytes")]
pub prf_salt: Vec<u8>,
}
pub struct Custodian<S, A, F = InMemoryFreshness<<S as PrimitiveSuite>::Csprng>>
where
S: PrimitiveSuite,
A: Authenticator,
F: FreshnessStore,
{
pub identity: Option<String>,
pub iat_skew_secs: u64,
pub freshness: F,
_marker: PhantomData<(S, A)>,
}
#[cfg(feature = "std-primitives")]
impl<S, A> Custodian<S, A>
where
S: PrimitiveSuite<Csprng = crate::primitives::OsCsprng>,
A: Authenticator,
{
pub fn new(identity: impl Into<String>) -> Self {
Self {
identity: Some(identity.into()),
iat_skew_secs: 300,
freshness: InMemoryFreshness::default(),
_marker: PhantomData,
}
}
}
impl<S, A, F> Custodian<S, A, F>
where
S: PrimitiveSuite,
A: Authenticator,
F: FreshnessStore,
{
pub fn with_freshness(identity: impl Into<String>, freshness: F) -> Self {
Self {
identity: Some(identity.into()),
iat_skew_secs: 300,
freshness,
_marker: PhantomData,
}
}
pub fn without_redeemer_check(mut self) -> Self {
self.identity = None;
self
}
pub fn setup(
&self,
protected: ProtectedState,
enrollment: A::Enrollment,
prf_salt: Vec<u8>,
wrapping_key: crate::grant::WrappingKey,
auth_context: &A::Context,
) -> Result<SealedState> {
let out: SetupOutputs = run_setup::<S, A>(
SetupInputs {
protected,
enrollment,
prf_salt,
wrapping_key,
},
auth_context,
)?;
Ok(out.sealed)
}
pub fn issue_freshness(&mut self) -> FreshnessToken {
self.freshness.issue()
}
pub fn build_conveyance(&mut self, o: Operation, sealed: &SealedState) -> ConveyancePayload {
let r = self.freshness.issue();
let credentials = sealed
.credential_iter()
.map(|(cid, salt)| ConveyanceCredential {
credential_id: cid.to_vec(),
prf_salt: salt.to_vec(),
})
.collect();
ConveyancePayload {
o,
r: r.to_vec(),
credentials,
}
}
pub fn redeem_grant(
&mut self,
grant: Grant<A>,
auth_context: &A::Context,
sealed: &SealedState,
now_unix: u64,
) -> Result<RedeemedGrant> {
let redeemer = match &self.identity {
Some(id) => RedeemerPolicy::Equals(id.as_str()),
None => RedeemerPolicy::AnyAccepted,
};
redeem::<S, A, F>(
RedeemInputs {
grant,
auth_context,
redeemer,
iat_skew_secs: self.iat_skew_secs,
now_unix,
},
&mut self.freshness,
sealed,
)
}
pub fn open(&self, redeemed: &RedeemedGrant, sealed: &SealedState) -> Result<OpenedState> {
open::<S>(redeemed, sealed)
}
pub fn execute_use<R, H>(
&self,
redeemed: RedeemedGrant,
sealed: &SealedState,
handler: H,
) -> Result<R>
where
H: FnOnce(&str, &[u8]) -> Result<R>,
{
execute_use::<S, H, R>(redeemed, sealed, handler)
}
pub fn execute_export<H>(
&self,
redeemed: RedeemedGrant,
sealed: &SealedState,
seal_for_recipient: H,
) -> Result<ExportArtifact>
where
H: FnOnce(&[u8; 32], &[u8]) -> Result<ExportArtifact>,
{
execute_export::<S, H>(redeemed, sealed, seal_for_recipient)
}
pub fn execute_lifecycle(
&self,
redeemed: RedeemedGrant,
sealed: &SealedState,
next_prf_salt: &[u8],
mutation: Box<Mutation>,
) -> Result<SealedState> {
Ok(execute_lifecycle::<S>(redeemed, sealed, next_prf_salt, mutation)?.sealed_state)
}
#[allow(clippy::too_many_arguments)]
pub fn execute_enroll(
&self,
redeemed: RedeemedGrant,
sealed: &SealedState,
next_prf_salt: &[u8],
new_enrollment: A::Enrollment,
new_prf_salt: Vec<u8>,
new_wrapping_key: crate::grant::WrappingKey,
auth_context: &A::Context,
) -> Result<SealedState> {
let new_cred = A::verify_enrollment(&new_enrollment, auth_context)?;
let new_credential_id = new_cred.credential_id;
let new_public_key = new_cred.public_key;
let new_wrapping_key_for_peer = new_wrapping_key.clone();
let new_credential_id_for_peer = new_credential_id.clone();
let LifecycleOutput {
sealed_state,
k_prime,
} = execute_lifecycle::<S>(
redeemed,
sealed,
next_prf_salt,
Box::new(move |m: &mut ProtectedState| {
let cid_b64 =
base64::engine::general_purpose::STANDARD.encode(&new_credential_id_for_peer);
m.peers.insert(cid_b64, new_wrapping_key_for_peer);
Ok(())
}),
)?;
add_credential_after_lifecycle::<S, A>(
sealed_state,
new_credential_id,
new_public_key,
new_prf_salt,
new_wrapping_key,
&k_prime,
)
}
pub fn execute_revoke(
&self,
redeemed: RedeemedGrant,
sealed: &SealedState,
next_prf_salt: &[u8],
revoked_credential_id: Vec<u8>,
) -> Result<SealedState> {
if revoked_credential_id == redeemed.credential_id {
return Err(crate::Error::CannotRevokeSelf);
}
let survivors = sealed
.credentials
.iter()
.filter(|c| c.credential_id != revoked_credential_id)
.count();
if survivors == 0 {
return Err(crate::Error::WouldOrphanState);
}
let revoked_for_peer = revoked_credential_id.clone();
let LifecycleOutput { sealed_state, .. } = execute_lifecycle::<S>(
redeemed,
sealed,
next_prf_salt,
Box::new(move |m: &mut ProtectedState| {
let cid_b64 = base64::engine::general_purpose::STANDARD.encode(&revoked_for_peer);
m.peers.remove(&cid_b64);
Ok(())
}),
)?;
Ok(remove_credential_after_lifecycle(
sealed_state,
&revoked_credential_id,
))
}
}
use base64::Engine;