1use crate::error::AcdpError;
15use serde::{Deserialize, Serialize};
16
17const PREFIX: &str = "keyless:";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24#[non_exhaustive]
25pub enum KeylessReason {
26 IssuerNotAccepted,
29 TokenInvalid,
31 AudienceMismatch,
33 ConfirmationMismatch,
35 TokenReplayed,
37 IdentityNotAuthorized,
40 PopInvalid,
42 AttestationMissing,
44 AttestationForeign,
46 AttestationNotFound,
48 AttestationMismatch,
51 AttestationWithdrawn,
53 OutsideAttestationWindow,
55 KeylessIdentityMismatch,
57 AttestationInvalid,
59 UntrustedAttester,
61 Unplaceable,
63 InvalidSignature,
65 Revoked,
67 ControllerUnreachable,
69}
70
71impl KeylessReason {
72 pub const ALL: [KeylessReason; 20] = [
74 Self::IssuerNotAccepted,
75 Self::TokenInvalid,
76 Self::AudienceMismatch,
77 Self::ConfirmationMismatch,
78 Self::TokenReplayed,
79 Self::IdentityNotAuthorized,
80 Self::PopInvalid,
81 Self::AttestationMissing,
82 Self::AttestationForeign,
83 Self::AttestationNotFound,
84 Self::AttestationMismatch,
85 Self::AttestationWithdrawn,
86 Self::OutsideAttestationWindow,
87 Self::KeylessIdentityMismatch,
88 Self::AttestationInvalid,
89 Self::UntrustedAttester,
90 Self::Unplaceable,
91 Self::InvalidSignature,
92 Self::Revoked,
93 Self::ControllerUnreachable,
94 ];
95
96 pub fn as_str(self) -> &'static str {
98 match self {
99 Self::IssuerNotAccepted => "issuer_not_accepted",
100 Self::TokenInvalid => "token_invalid",
101 Self::AudienceMismatch => "audience_mismatch",
102 Self::ConfirmationMismatch => "confirmation_mismatch",
103 Self::TokenReplayed => "token_replayed",
104 Self::IdentityNotAuthorized => "identity_not_authorized",
105 Self::PopInvalid => "pop_invalid",
106 Self::AttestationMissing => "attestation_missing",
107 Self::AttestationForeign => "attestation_foreign",
108 Self::AttestationNotFound => "attestation_not_found",
109 Self::AttestationMismatch => "attestation_mismatch",
110 Self::AttestationWithdrawn => "attestation_withdrawn",
111 Self::OutsideAttestationWindow => "outside_attestation_window",
112 Self::KeylessIdentityMismatch => "keyless_identity_mismatch",
113 Self::AttestationInvalid => "attestation_invalid",
114 Self::UntrustedAttester => "untrusted_attester",
115 Self::Unplaceable => "unplaceable",
116 Self::InvalidSignature => "invalid_signature",
117 Self::Revoked => "revoked",
118 Self::ControllerUnreachable => "controller_unreachable",
119 }
120 }
121
122 pub fn parse(token: &str) -> Option<Self> {
124 Self::ALL.into_iter().find(|r| r.as_str() == token)
125 }
126
127 pub fn is_registry_token(self) -> bool {
130 !matches!(
131 self,
132 Self::AttestationInvalid
133 | Self::UntrustedAttester
134 | Self::Unplaceable
135 | Self::InvalidSignature
136 | Self::Revoked
137 | Self::ControllerUnreachable
138 )
139 }
140
141 pub fn tag(self, message: impl std::fmt::Display) -> String {
143 format!("{PREFIX}{}: {message}", self.as_str())
144 }
145
146 pub fn error(self, message: impl std::fmt::Display) -> AcdpError {
150 let msg = self.tag(message);
151 match self {
152 Self::InvalidSignature | Self::PopInvalid => AcdpError::InvalidSignature(msg),
153 Self::ControllerUnreachable => AcdpError::KeyResolutionUnreachable(msg),
154 Self::AttestationInvalid
155 | Self::AttestationMissing
156 | Self::AttestationForeign
157 | Self::AttestationNotFound => AcdpError::KeyResolution(msg),
158 Self::KeylessIdentityMismatch => AcdpError::NotAuthorized(msg),
159 _ => AcdpError::KeyNotAuthorized(msg),
160 }
161 }
162
163 pub fn of(error: &AcdpError) -> Option<Self> {
167 let msg = match error {
168 AcdpError::KeyRevoked(_) => return Some(Self::Revoked),
170 AcdpError::InvalidSignature(m)
171 | AcdpError::KeyResolution(m)
172 | AcdpError::KeyResolutionUnreachable(m)
173 | AcdpError::KeyNotAuthorized(m)
174 | AcdpError::NotAuthorized(m)
175 | AcdpError::SchemaViolation(m) => m,
176 _ => return None,
177 };
178 let (token, _) = msg.strip_prefix(PREFIX)?.split_once(": ")?;
179 Self::parse(token)
180 }
181}
182
183impl std::fmt::Display for KeylessReason {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 f.write_str(self.as_str())
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn tokens_round_trip_and_are_unique() {
195 let mut seen = std::collections::BTreeSet::new();
196 for r in KeylessReason::ALL {
197 assert!(seen.insert(r.as_str()), "duplicate token {r}");
198 assert_eq!(KeylessReason::parse(r.as_str()), Some(r));
199 let json = serde_json::to_value(r).unwrap();
200 assert_eq!(json, r.as_str());
201 assert_eq!(KeylessReason::of(&r.error("x")), Some(r));
202 }
203 }
204
205 #[test]
206 fn untagged_errors_carry_no_reason() {
207 assert_eq!(
208 KeylessReason::of(&AcdpError::KeyNotAuthorized("plain".into())),
209 None
210 );
211 assert_eq!(
212 KeylessReason::of(&AcdpError::KeyNotAuthorized("keyless:nope: x".into())),
213 None
214 );
215 }
216}