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 IssuerUnreachable,
74 KeylessEventUnsupported,
77}
78
79impl KeylessReason {
80 pub const ALL: &'static [KeylessReason] = &[
83 Self::IssuerNotAccepted,
84 Self::TokenInvalid,
85 Self::AudienceMismatch,
86 Self::ConfirmationMismatch,
87 Self::TokenReplayed,
88 Self::IdentityNotAuthorized,
89 Self::PopInvalid,
90 Self::AttestationMissing,
91 Self::AttestationForeign,
92 Self::AttestationNotFound,
93 Self::AttestationMismatch,
94 Self::AttestationWithdrawn,
95 Self::OutsideAttestationWindow,
96 Self::KeylessIdentityMismatch,
97 Self::AttestationInvalid,
98 Self::UntrustedAttester,
99 Self::Unplaceable,
100 Self::InvalidSignature,
101 Self::Revoked,
102 Self::ControllerUnreachable,
103 Self::IssuerUnreachable,
104 Self::KeylessEventUnsupported,
105 ];
106
107 pub fn as_str(self) -> &'static str {
109 match self {
110 Self::IssuerNotAccepted => "issuer_not_accepted",
111 Self::TokenInvalid => "token_invalid",
112 Self::IssuerUnreachable => "issuer_unreachable",
113 Self::AudienceMismatch => "audience_mismatch",
114 Self::ConfirmationMismatch => "confirmation_mismatch",
115 Self::TokenReplayed => "token_replayed",
116 Self::IdentityNotAuthorized => "identity_not_authorized",
117 Self::PopInvalid => "pop_invalid",
118 Self::AttestationMissing => "attestation_missing",
119 Self::KeylessEventUnsupported => "keyless_event_unsupported",
120 Self::AttestationForeign => "attestation_foreign",
121 Self::AttestationNotFound => "attestation_not_found",
122 Self::AttestationMismatch => "attestation_mismatch",
123 Self::AttestationWithdrawn => "attestation_withdrawn",
124 Self::OutsideAttestationWindow => "outside_attestation_window",
125 Self::KeylessIdentityMismatch => "keyless_identity_mismatch",
126 Self::AttestationInvalid => "attestation_invalid",
127 Self::UntrustedAttester => "untrusted_attester",
128 Self::Unplaceable => "unplaceable",
129 Self::InvalidSignature => "invalid_signature",
130 Self::Revoked => "revoked",
131 Self::ControllerUnreachable => "controller_unreachable",
132 }
133 }
134
135 pub fn parse(token: &str) -> Option<Self> {
137 Self::ALL.iter().copied().find(|r| r.as_str() == token)
138 }
139
140 pub fn is_registry_token(self) -> bool {
143 !matches!(
144 self,
145 Self::AttestationInvalid
146 | Self::UntrustedAttester
147 | Self::Unplaceable
148 | Self::InvalidSignature
149 | Self::Revoked
150 | Self::ControllerUnreachable
151 )
152 }
153
154 pub fn tag(self, message: impl std::fmt::Display) -> String {
156 format!("{PREFIX}{}: {message}", self.as_str())
157 }
158
159 pub fn error(self, message: impl std::fmt::Display) -> AcdpError {
164 let msg = self.tag(message);
165 match self {
166 Self::InvalidSignature | Self::PopInvalid => AcdpError::InvalidSignature(msg),
167 Self::ControllerUnreachable | Self::IssuerUnreachable => {
168 AcdpError::KeyResolutionUnreachable(msg)
169 }
170 Self::AttestationInvalid
171 | Self::AttestationMissing
172 | Self::KeylessEventUnsupported
173 | Self::AttestationForeign
174 | Self::AttestationNotFound => AcdpError::KeyResolution(msg),
175 Self::KeylessIdentityMismatch => AcdpError::NotAuthorized(msg),
176 _ => AcdpError::KeyNotAuthorized(msg),
177 }
178 }
179
180 pub fn of(error: &AcdpError) -> Option<Self> {
184 let msg = match error {
185 AcdpError::KeyRevoked(_) => return Some(Self::Revoked),
187 AcdpError::InvalidSignature(m)
188 | AcdpError::KeyResolution(m)
189 | AcdpError::KeyResolutionUnreachable(m)
190 | AcdpError::KeyNotAuthorized(m)
191 | AcdpError::NotAuthorized(m)
192 | AcdpError::SchemaViolation(m) => m,
193 _ => return None,
194 };
195 let (token, _) = msg.strip_prefix(PREFIX)?.split_once(": ")?;
196 Self::parse(token)
197 }
198}
199
200impl std::fmt::Display for KeylessReason {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 f.write_str(self.as_str())
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn tokens_round_trip_and_are_unique() {
212 let mut seen = std::collections::BTreeSet::new();
213 for &r in KeylessReason::ALL {
214 assert!(seen.insert(r.as_str()), "duplicate token {r}");
215 assert_eq!(KeylessReason::parse(r.as_str()), Some(r));
216 let json = serde_json::to_value(r).unwrap();
217 assert_eq!(json, r.as_str());
218 assert_eq!(KeylessReason::of(&r.error("x")), Some(r));
219 }
220 }
221
222 #[test]
223 fn untagged_errors_carry_no_reason() {
224 assert_eq!(
225 KeylessReason::of(&AcdpError::KeyNotAuthorized("plain".into())),
226 None
227 );
228 assert_eq!(
229 KeylessReason::of(&AcdpError::KeyNotAuthorized("keyless:nope: x".into())),
230 None
231 );
232 }
233}