use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::beta::{compute_beta_from_canonical, DS_BIND};
use crate::freshness::FreshnessStore;
use crate::grant::{GrantOpt, RedeemedGrant, WrappingKey};
use crate::operation::Operation;
use crate::phases::grant::RedeemerPolicy;
use crate::primitives::{Authenticator, Hash, PrimitiveSuite};
use crate::state::SealedState;
use crate::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct BatchOperations(pub Vec<Operation>);
impl BatchOperations {
pub fn new(ops: Vec<Operation>) -> Self {
Self(ops)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn canonical_bytes(&self) -> Result<Vec<u8>> {
let v = serde_json::to_value(self)
.map_err(|_| crate::Error::Encoding("BatchOperations→Value"))?;
crate::canonical::canonicalize_strict(&v)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BatchGrant<A: Authenticator> {
pub ops: BatchOperations,
#[serde(with = "crate::wire::b64bytes")]
pub r: Vec<u8>,
#[serde(with = "crate::wire::b64bytes")]
pub credential_id: Vec<u8>,
pub wrapping_key: WrappingKey,
pub assertion: A::Assertion,
#[serde(default)]
pub opt: GrantOpt,
}
impl<A: Authenticator> Clone for BatchGrant<A>
where
A::Assertion: Clone,
{
fn clone(&self) -> Self {
Self {
ops: self.ops.clone(),
r: self.r.clone(),
credential_id: self.credential_id.clone(),
wrapping_key: self.wrapping_key.clone(),
assertion: self.assertion.clone(),
opt: self.opt.clone(),
}
}
}
#[derive(Debug)]
pub struct RedeemedBatch {
pub ops: BatchOperations,
pub credential_id: Vec<u8>,
pub wrapping_key: WrappingKey,
pub opt: GrantOpt,
}
impl RedeemedBatch {
pub fn per_op(&self) -> impl Iterator<Item = RedeemedGrant> + '_ {
self.ops.0.iter().cloned().map(move |o| RedeemedGrant {
o,
credential_id: self.credential_id.clone(),
wrapping_key: self.wrapping_key.clone(),
opt: GrantOpt {
wrapping_key_next: self.opt.wrapping_key_next.clone(),
},
})
}
}
pub struct RedeemBatchInputs<'a, A: Authenticator> {
pub grant: BatchGrant<A>,
pub auth_context: &'a A::Context,
pub redeemer: RedeemerPolicy<'a>,
pub iat_skew_secs: u64,
pub now_unix: u64,
}
pub fn redeem_batch<S, A, F>(
inputs: RedeemBatchInputs<'_, A>,
freshness: &mut F,
sealed: &SealedState,
) -> Result<RedeemedBatch>
where
S: PrimitiveSuite,
A: Authenticator,
F: FreshnessStore,
{
let RedeemBatchInputs {
grant,
auth_context,
redeemer,
iat_skew_secs,
now_unix,
} = inputs;
if !freshness.consume(&grant.r) {
return Err(crate::Error::FreshnessRejected);
}
let val_ctx = crate::phases::grant::OpValidationCtx {
redeemer: &redeemer,
iat_skew_secs,
now_unix,
};
crate::phases::grant::validate_batch_ops(&grant.ops.0, &val_ctx)?;
if grant.ops.0.iter().any(|o| 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 ops_canonical = grant.ops.canonical_bytes()?;
let beta = compute_beta_from_canonical::<S::Hash>(DS_BIND, &grant.r, &ops_canonical);
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(RedeemedBatch {
ops: grant.ops,
credential_id: grant.credential_id,
wrapping_key: grant.wrapping_key,
opt: grant.opt,
})
}
#[allow(dead_code)]
trait _BatchMarker: Zeroize + ZeroizeOnDrop {}