use crate::error::AcdpError;
use serde::{Deserialize, Serialize};
const PREFIX: &str = "keyless:";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum KeylessReason {
IssuerNotAccepted,
TokenInvalid,
AudienceMismatch,
ConfirmationMismatch,
TokenReplayed,
IdentityNotAuthorized,
PopInvalid,
AttestationMissing,
AttestationForeign,
AttestationNotFound,
AttestationMismatch,
AttestationWithdrawn,
OutsideAttestationWindow,
KeylessIdentityMismatch,
AttestationInvalid,
UntrustedAttester,
Unplaceable,
InvalidSignature,
Revoked,
ControllerUnreachable,
}
impl KeylessReason {
pub const ALL: [KeylessReason; 20] = [
Self::IssuerNotAccepted,
Self::TokenInvalid,
Self::AudienceMismatch,
Self::ConfirmationMismatch,
Self::TokenReplayed,
Self::IdentityNotAuthorized,
Self::PopInvalid,
Self::AttestationMissing,
Self::AttestationForeign,
Self::AttestationNotFound,
Self::AttestationMismatch,
Self::AttestationWithdrawn,
Self::OutsideAttestationWindow,
Self::KeylessIdentityMismatch,
Self::AttestationInvalid,
Self::UntrustedAttester,
Self::Unplaceable,
Self::InvalidSignature,
Self::Revoked,
Self::ControllerUnreachable,
];
pub fn as_str(self) -> &'static str {
match self {
Self::IssuerNotAccepted => "issuer_not_accepted",
Self::TokenInvalid => "token_invalid",
Self::AudienceMismatch => "audience_mismatch",
Self::ConfirmationMismatch => "confirmation_mismatch",
Self::TokenReplayed => "token_replayed",
Self::IdentityNotAuthorized => "identity_not_authorized",
Self::PopInvalid => "pop_invalid",
Self::AttestationMissing => "attestation_missing",
Self::AttestationForeign => "attestation_foreign",
Self::AttestationNotFound => "attestation_not_found",
Self::AttestationMismatch => "attestation_mismatch",
Self::AttestationWithdrawn => "attestation_withdrawn",
Self::OutsideAttestationWindow => "outside_attestation_window",
Self::KeylessIdentityMismatch => "keyless_identity_mismatch",
Self::AttestationInvalid => "attestation_invalid",
Self::UntrustedAttester => "untrusted_attester",
Self::Unplaceable => "unplaceable",
Self::InvalidSignature => "invalid_signature",
Self::Revoked => "revoked",
Self::ControllerUnreachable => "controller_unreachable",
}
}
pub fn parse(token: &str) -> Option<Self> {
Self::ALL.into_iter().find(|r| r.as_str() == token)
}
pub fn is_registry_token(self) -> bool {
!matches!(
self,
Self::AttestationInvalid
| Self::UntrustedAttester
| Self::Unplaceable
| Self::InvalidSignature
| Self::Revoked
| Self::ControllerUnreachable
)
}
pub fn tag(self, message: impl std::fmt::Display) -> String {
format!("{PREFIX}{}: {message}", self.as_str())
}
pub fn error(self, message: impl std::fmt::Display) -> AcdpError {
let msg = self.tag(message);
match self {
Self::InvalidSignature | Self::PopInvalid => AcdpError::InvalidSignature(msg),
Self::ControllerUnreachable => AcdpError::KeyResolutionUnreachable(msg),
Self::AttestationInvalid
| Self::AttestationMissing
| Self::AttestationForeign
| Self::AttestationNotFound => AcdpError::KeyResolution(msg),
Self::KeylessIdentityMismatch => AcdpError::NotAuthorized(msg),
_ => AcdpError::KeyNotAuthorized(msg),
}
}
pub fn of(error: &AcdpError) -> Option<Self> {
let msg = match error {
AcdpError::KeyRevoked(_) => return Some(Self::Revoked),
AcdpError::InvalidSignature(m)
| AcdpError::KeyResolution(m)
| AcdpError::KeyResolutionUnreachable(m)
| AcdpError::KeyNotAuthorized(m)
| AcdpError::NotAuthorized(m)
| AcdpError::SchemaViolation(m) => m,
_ => return None,
};
let (token, _) = msg.strip_prefix(PREFIX)?.split_once(": ")?;
Self::parse(token)
}
}
impl std::fmt::Display for KeylessReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tokens_round_trip_and_are_unique() {
let mut seen = std::collections::BTreeSet::new();
for r in KeylessReason::ALL {
assert!(seen.insert(r.as_str()), "duplicate token {r}");
assert_eq!(KeylessReason::parse(r.as_str()), Some(r));
let json = serde_json::to_value(r).unwrap();
assert_eq!(json, r.as_str());
assert_eq!(KeylessReason::of(&r.error("x")), Some(r));
}
}
#[test]
fn untagged_errors_carry_no_reason() {
assert_eq!(
KeylessReason::of(&AcdpError::KeyNotAuthorized("plain".into())),
None
);
assert_eq!(
KeylessReason::of(&AcdpError::KeyNotAuthorized("keyless:nope: x".into())),
None
);
}
}