use crate::types::{GrantVoucherClaims, TctClaims, VerifiedTct};
use crate::TctError;
use aitp_core::{Aid, Timestamp, PROTOCOL_VERSION};
use aitp_crypto::{jws, AitpVerifyingKey};
use uuid::Uuid;
pub struct TctVerifyContext<'a> {
pub expected_audience: &'a Aid,
pub issuer: &'a Aid,
pub now: Timestamp,
pub issuer_manifest_expires_at: Option<Timestamp>,
pub revocation_check: Option<&'a dyn Fn(&Uuid) -> bool>,
}
impl<'a> TctVerifyContext<'a> {
pub fn now(expected_audience: &'a Aid, issuer: &'a Aid) -> Self {
Self {
expected_audience,
issuer,
now: Timestamp::now(),
issuer_manifest_expires_at: None,
revocation_check: None,
}
}
}
pub fn verify_tct(token: &str, ctx: &TctVerifyContext<'_>) -> Result<VerifiedTct, TctError> {
let payload = jws::verify_compact(ctx.issuer, jws::TYP_TCT, token).map_err(TctError::Crypto)?;
let claims: TctClaims =
serde_json::from_slice(&payload).map_err(|e| TctError::ClaimsMalformed(e.to_string()))?;
if claims.ver != PROTOCOL_VERSION {
return Err(TctError::VersionUnknown);
}
if &claims.iss != ctx.issuer {
return Err(TctError::IssuerMismatch);
}
if &claims.aud != ctx.expected_audience {
return Err(TctError::AudienceMismatch);
}
if claims.aud != claims.sub {
return Err(TctError::AudienceMismatch);
}
if claims.exp.is_in_the_past(ctx.now) {
return Err(TctError::Expired);
}
if claims.iat.is_in_the_future(ctx.now) {
return Err(TctError::Expired);
}
if let Some(manifest_expires_at) = ctx.issuer_manifest_expires_at {
if claims.exp.0 > manifest_expires_at.0 {
return Err(TctError::ExpiresAfterManifest);
}
}
if claims.grants.is_empty() {
return Err(TctError::EmptyGrants);
}
let subject_key = AitpVerifyingKey::from_aid(&claims.sub).map_err(TctError::Crypto)?;
let expected_jkt = subject_key.to_jwk_thumbprint().map_err(TctError::Crypto)?;
if claims.cnf.jkt != expected_jkt {
return Err(TctError::CnfMalformed);
}
if let Some(check) = ctx.revocation_check {
if check(&claims.jti) {
return Err(TctError::Revoked);
}
}
Ok(VerifiedTct {
token: token.to_string(),
claims,
})
}
pub fn verify_voucher(token: &str, issuer: &Aid) -> Result<GrantVoucherClaims, TctError> {
let payload =
jws::verify_compact(issuer, jws::TYP_GRANT_VOUCHER, token).map_err(TctError::Crypto)?;
let claims: GrantVoucherClaims =
serde_json::from_slice(&payload).map_err(|e| TctError::ClaimsMalformed(e.to_string()))?;
if claims.ver != PROTOCOL_VERSION {
return Err(TctError::VersionUnknown);
}
if &claims.iss != issuer {
return Err(TctError::IssuerMismatch);
}
if claims.grants.is_empty() {
return Err(TctError::EmptyGrants);
}
Ok(claims)
}