use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::attestation;
use crate::config::VerifierConfig;
use crate::error::VerifyError;
use crate::identity::DeviceIdentity;
use crate::jwks::IssuerJwks;
use crate::replay::ReplayStore;
use crate::request::SignedRequest;
use crate::signature;
pub async fn verify(
request: &SignedRequest<'_>,
config: &VerifierConfig,
jwks: &IssuerJwks,
replay_store: &dyn ReplayStore,
) -> Result<DeviceIdentity, VerifyError> {
let (sig_token, att_token) = match (request.signature, request.attestation) {
(Some(s), Some(a)) => (s, a),
_ => {
tracing::debug!(target: "authkestra_devsig", "rejecting request: missing_credential");
return Err(VerifyError::MissingCredential);
}
};
let now = current_unix_time();
let parsed_att = attestation::parse(att_token, &config.allowed_algs)?;
let parsed_sig = signature::parse(sig_token, &config.allowed_algs)?;
let att = attestation::verify_trust(&parsed_att, config, jwks, now).await?;
let sig = signature::verify_bound_and_signed(&parsed_sig, request, config, &att.jkt, now)?;
let ttl = Duration::from_secs((sig.exp - now).max(0) as u64);
match replay_store.put_if_absent(&sig.jti, ttl).await {
Ok(true) => {}
Ok(false) => {
tracing::warn!(target: "authkestra_devsig", jti = %sig.jti, "rejecting request: jti already seen (replay_detected)");
return Err(VerifyError::ReplayDetected);
}
Err(store_err) => {
tracing::error!(target: "authkestra_devsig", error = %store_err, "rejecting request: replay store unreachable, failing closed");
return Err(VerifyError::ReplayDetected);
}
}
tracing::debug!(
target: "authkestra_devsig",
subject = %att.sub,
device = %att.did,
"device-signature request accepted"
);
Ok(DeviceIdentity {
subject: att.sub,
device: att.did,
key_thumbprint: sig.jwk_thumbprint,
attributes: att.att,
})
}
fn current_unix_time() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before 1970-01-01")
.as_secs() as i64
}