use crate::builder::TctSigningView;
use crate::types::Tct;
use crate::TctError;
use aitp_core::{base64url, jcs, Aid, Timestamp};
use aitp_crypto::{AitpVerifyingKey, Signature};
use sha2::{Digest, Sha256};
use uuid::Uuid;
pub struct TctVerifyContext<'a> {
pub expected_audience: &'a Aid,
pub issuer_pubkey: &'a AitpVerifyingKey,
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_pubkey: &'a AitpVerifyingKey) -> Self {
Self {
expected_audience,
issuer_pubkey,
now: Timestamp::now(),
issuer_manifest_expires_at: None,
revocation_check: None,
}
}
}
pub fn verify_tct<'a>(tct: &'a Tct, ctx: &TctVerifyContext<'_>) -> Result<&'a Tct, TctError> {
if tct.version != "aitp/0.1" {
return Err(TctError::VersionUnknown);
}
if &tct.audience != ctx.expected_audience {
return Err(TctError::AudienceMismatch);
}
if tct.audience != tct.subject {
return Err(TctError::AudienceMismatch);
}
if tct.expires_at.is_in_the_past(ctx.now) {
return Err(TctError::Expired);
}
if tct.issued_at.is_in_the_future(ctx.now) {
return Err(TctError::Expired);
}
if let Some(manifest_expires_at) = ctx.issuer_manifest_expires_at {
if tct.expires_at.0 > manifest_expires_at.0 {
return Err(TctError::ExpiresAfterManifest);
}
}
if tct.grants.is_empty() {
return Err(TctError::EmptyGrants);
}
let cnf_bytes =
base64url::decode_strict(&tct.binding.cnf).map_err(|_| TctError::CnfMalformed)?;
if cnf_bytes != tct.subject.pubkey_compressed_bytes() {
return Err(TctError::CnfMalformed);
}
if ctx.issuer_pubkey.to_compressed() != tct.issuer.pubkey_compressed_bytes() {
return Err(TctError::IssuerMismatch);
}
let view = TctSigningView {
version: &tct.version,
jti: &tct.jti,
issuer: &tct.issuer,
subject: &tct.subject,
audience: &tct.audience,
issued_at: &tct.issued_at,
expires_at: &tct.expires_at,
grants: &tct.grants,
binding: &tct.binding,
};
let canonical = jcs::canonicalize_serializable(&view)
.map_err(|e| TctError::Canonicalization(e.to_string()))?;
let digest = Sha256::digest(&canonical);
let sig = Signature::parse(&tct.signature).map_err(|_| TctError::SignatureInvalid)?;
ctx.issuer_pubkey
.verify(&digest, &sig)
.map_err(|_| TctError::SignatureInvalid)?;
if let Some(check) = ctx.revocation_check {
if check(&tct.jti) {
return Err(TctError::Revoked);
}
}
Ok(tct)
}