use crate::beta::{compute_beta_for_op, constant_time_eq, DS_BIND};
use crate::freshness::FreshnessStore;
use crate::grant::{Grant, RedeemedGrant};
use crate::operation::Operation;
use crate::primitives::{Authenticator, Hash, PrimitiveSuite};
use crate::state::SealedState;
use crate::Result;
pub enum RedeemerPolicy<'a> {
Equals(&'a str),
AnyAccepted,
}
pub struct OpValidationCtx<'a> {
pub redeemer: &'a RedeemerPolicy<'a>,
pub iat_skew_secs: u64,
pub now_unix: u64,
}
pub fn validate_op_against(o: &Operation, ctx: &OpValidationCtx<'_>) -> Result<()> {
o.check_validity(ctx.now_unix, ctx.iat_skew_secs)?;
if o.valid.multiplicity != crate::operation::Multiplicity::One {
return Err(crate::Error::MultiplicityNotImplemented);
}
if let RedeemerPolicy::Equals(expected) = ctx.redeemer {
if !constant_time_eq(o.bind.redeemer.as_bytes(), expected.as_bytes()) {
return Err(crate::Error::RedeemerMismatch);
}
}
if o.act.kind == crate::operation::ActType::Export && o.bind.recipient.is_none() {
return Err(crate::Error::MissingRecipient);
}
Ok(())
}
pub fn validate_batch_ops(ops: &[Operation], ctx: &OpValidationCtx<'_>) -> Result<()> {
if ops.is_empty() {
return Err(crate::Error::Malformed("batch: empty ops"));
}
let rotation_count = ops
.iter()
.filter(|o| o.act.kind.is_rotation_class())
.count();
if rotation_count > 1 {
return Err(crate::Error::BatchMultipleRotationOps);
}
for o in ops {
validate_op_against(o, ctx)?;
}
Ok(())
}
pub struct RedeemInputs<'a, A: Authenticator> {
pub grant: Grant<A>,
pub auth_context: &'a A::Context,
pub redeemer: RedeemerPolicy<'a>,
pub iat_skew_secs: u64,
pub now_unix: u64,
}
pub fn redeem<S, A, F>(
inputs: RedeemInputs<'_, A>,
freshness: &mut F,
sealed: &SealedState,
) -> Result<RedeemedGrant>
where
S: PrimitiveSuite,
A: Authenticator,
F: FreshnessStore,
{
let RedeemInputs {
grant,
auth_context,
redeemer,
iat_skew_secs,
now_unix,
} = inputs;
if !freshness.consume(&grant.r) {
return Err(crate::Error::FreshnessRejected);
}
let val_ctx = OpValidationCtx {
redeemer: &redeemer,
iat_skew_secs,
now_unix,
};
validate_op_against(&grant.o, &val_ctx)?;
if grant.o.act.kind.is_rotation_class() && grant.opt.wrapping_key_next.is_none() {
return Err(crate::Error::MissingRotationKey);
}
let pk = sealed
.registry
.get::<A>(&grant.credential_id)?
.ok_or(crate::Error::UnknownCredential)?;
let beta = compute_beta_for_op::<S::Hash>(DS_BIND, &grant.r, &grant.o)?;
A::check_credential_binding(&grant.credential_id, &grant.assertion)?;
A::verify_assertion(&pk, &beta, &grant.assertion, auth_context)
.map_err(|_| crate::Error::AuthorizationInvalid)?;
let _ = S::Hash::OUTPUT_LEN;
Ok(RedeemedGrant {
o: grant.o,
credential_id: grant.credential_id,
wrapping_key: grant.wrapping_key,
opt: grant.opt,
})
}