canokey_protocol/error.rs
1//! Structured protocol failures, independent of transport and binding errors.
2use crate::StatusWord;
3/// Stable semantic categories for protocol failures. Transport errors stay outside
4/// the core; callers must allow future variants of this non-exhaustive enum.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum ErrorKind {
8 /// A caller value or options combination is invalid.
9 InvalidArgument,
10 /// PIN/PUK bytes violate applet input rules.
11 InvalidPin,
12 /// Response framing or semantic content is malformed.
13 InvalidResponse,
14 /// The exchange sequence violates its conversation rules.
15 ProtocolViolation,
16 /// A frame, input, response, nesting, or exchange budget was exceeded.
17 LimitExceeded,
18 /// Explicit credential verification failed; retry information may be present.
19 AuthenticationFailed,
20 /// The card's mutual-authentication cryptogram did not match the host challenge.
21 DeviceAuthenticationFailed,
22 /// A credential reference is blocked.
23 PinBlocked,
24 /// Required authentication/security state is absent.
25 SecurityStatusNotSatisfied,
26 /// The card reports unmet execution conditions.
27 ConditionsNotSatisfied,
28 /// A requested applet object/reference was not found.
29 NotFound,
30 /// The required applet/device could not be selected.
31 UnsupportedDevice,
32 /// A capability or instruction is known to be unavailable.
33 UnsupportedFeature,
34 /// The requested algorithm cannot be used for this operation.
35 UnsupportedAlgorithm,
36 /// Available evidence cannot establish required capability support.
37 CapabilityUnknown,
38 /// An observed format/version is not understood (including certificate encoding).
39 UnsupportedProtocolVersion,
40 /// An unmapped status was returned; inspect `Error::status_word` or
41 /// `Error::application_status`.
42 UnexpectedStatusWord,
43 /// A local lifecycle method was called in an invalid state.
44 OperationStateError,
45}
46/// Context in which a protocol error was classified.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum Phase {
49 /// Input/configuration validation or an error without a more specific phase.
50 Construction,
51 /// Applet selection.
52 Select,
53 /// Target applet command.
54 Command,
55 /// Credential verification or authentication exchange.
56 Authentication,
57 /// Response or structured-byte decoding.
58 Parsing,
59 /// Physical continuation, correction, or segmentation.
60 Conversation,
61}
62/// Credential involved in an authentication error; never contains its bytes.
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum SecretReference {
65 /// PIV user PIN.
66 Pin,
67 /// PIV PIN unblocking key.
68 Puk,
69 /// PIV management key.
70 ManagementKey,
71 /// Admin applet PIN.
72 AdminPin,
73 /// OATH access-code key, distinct from a PIN retry counter.
74 OathAccess,
75 /// OpenPGP PW1 for signatures (reference 81).
76 Pw1Sign,
77 /// OpenPGP PW1 for decipher/authentication (reference 82).
78 Pw1Other,
79 /// OpenPGP administrative password (reference 83).
80 Pw3,
81 /// OpenPGP resetting code.
82 ResetCode,
83}
84/// Owned, cloneable protocol failure with no secret payload.
85/// Display is diagnostic English; applications own localization and transport errors.
86#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
87#[error("{kind:?} during {phase:?}")]
88pub struct Error {
89 /// Semantic failure category.
90 pub kind: ErrorKind,
91 /// Command/parser context used to classify the error.
92 pub phase: Phase,
93 /// Original status when the failure came from a card status word.
94 pub status_word: Option<StatusWord>,
95 /// Applet-level non-ISO status byte reported in the response payload
96 /// (e.g. the CTAP status byte), when the failure came from such a status.
97 /// Distinct from `status_word`, which is always an ISO 7816 SW1-SW2.
98 pub application_status: Option<u8>,
99 /// Credential reference when known; absent for non-authentication failures.
100 pub reference: Option<SecretReference>,
101 /// Retry count from an authentication 63Cx status; absent when not reported.
102 pub retries_remaining: Option<u8>,
103}
104impl Error {
105 /// Construct a failure in Construction phase with no card status or credential.
106 pub fn new(kind: ErrorKind) -> Self {
107 Self {
108 kind,
109 phase: Phase::Construction,
110 status_word: None,
111 application_status: None,
112 reference: None,
113 retries_remaining: None,
114 }
115 }
116 /// Replace the phase while retaining all other error details.
117 pub fn at(mut self, phase: Phase) -> Self {
118 self.phase = phase;
119 self
120 }
121 /// Classify a card status using its phase and optional credential reference.
122 ///
123 /// SELECT 6A82/6A88 maps to UnsupportedDevice; elsewhere it maps to NotFound.
124 /// 63Cx is AuthenticationFailed only with a credential reference. Unknown
125 /// statuses remain UnexpectedStatusWord with their raw value. This function
126 /// is for failure paths: even 9000 maps to UnexpectedStatusWord here.
127 pub fn status(sw: StatusWord, phase: Phase, reference: Option<SecretReference>) -> Self {
128 let pin_reference = matches!(
129 reference,
130 Some(
131 SecretReference::Pin
132 | SecretReference::Puk
133 | SecretReference::AdminPin
134 | SecretReference::Pw1Sign
135 | SecretReference::Pw1Other
136 | SecretReference::Pw3
137 | SecretReference::ResetCode
138 )
139 );
140 let kind = match sw.raw() {
141 0x6983 if pin_reference => ErrorKind::PinBlocked,
142 0x6982 => ErrorKind::SecurityStatusNotSatisfied,
143 0x6985 => ErrorKind::ConditionsNotSatisfied,
144 0x6a82 | 0x6a88 if phase == Phase::Select => ErrorKind::UnsupportedDevice,
145 0x6a82 | 0x6a88 => ErrorKind::NotFound,
146 0x6d00 | 0x6e00 => ErrorKind::UnsupportedFeature,
147 n if n & 0xfff0 == 0x63c0 && reference.is_some() => ErrorKind::AuthenticationFailed,
148 _ => ErrorKind::UnexpectedStatusWord,
149 };
150 Self {
151 kind,
152 phase,
153 status_word: Some(sw),
154 application_status: None,
155 reference,
156 retries_remaining: if pin_reference && sw.raw() & 0xfff0 == 0x63c0 {
157 Some((sw.raw() & 15) as u8)
158 } else {
159 None
160 },
161 }
162 }
163}