Skip to main content

yb_core/errors/
mod.rs

1// SPDX-FileCopyrightText: 2026 Frederic Ruget <fred@atlant.is> (GitHub: @douzebis)
2//
3// SPDX-License-Identifier: MIT
4
5//! Typed errors and their human-readable rendering (spec 0025).
6//!
7//! - [`CardError`]: a failure reported by the card (status word) or by
8//!   PC/SC, tagged with the operation yb was performing.
9//! - [`YbError`]: one of yb's own errors, already phrased as what / why /
10//!   fix.
11//!
12//! Both travel through `anyhow` unchanged; [`render`] turns an error chain
13//! into the text shown to the user.
14
15mod catalog;
16
17pub use catalog::{explain_pcsc, render, Explanation, RenderEnv};
18
19use crate::piv::MgmtAlgo;
20use std::fmt;
21
22/// The card operation that failed, in yb terms.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CardOp {
25    SelectPiv,
26    VerifyPin,
27    /// CHANGE REFERENCE DATA for the PIN.
28    ChangePin,
29    /// CHANGE REFERENCE DATA for the PUK.
30    ChangePuk,
31    MgmtAuth,
32    SetMgmtKey,
33    ReadObject(u32),
34    /// Reading the certificate of a key slot.
35    ReadCertificate(u8),
36    WriteObject(u32),
37    GenerateKey(u8),
38    Sign(u8),
39    Ecdh(u8),
40    GetMetadata(u8),
41    /// Any other card command (e.g. a raw `send_apdu`).
42    Command,
43}
44
45impl CardOp {
46    /// Short description, used on the details line.
47    pub fn describe(&self) -> String {
48        match self {
49            Self::SelectPiv => "selecting the PIV application".to_owned(),
50            Self::VerifyPin => "PIN verification".to_owned(),
51            Self::ChangePin => "PIN change".to_owned(),
52            Self::ChangePuk => "PUK change".to_owned(),
53            Self::MgmtAuth => "management key authentication".to_owned(),
54            Self::SetMgmtKey => "changing the management key".to_owned(),
55            Self::ReadObject(id) => format!("read object 0x{id:06X}"),
56            Self::ReadCertificate(slot) => format!("read certificate of slot 0x{slot:02x}"),
57            Self::WriteObject(id) => format!("write object 0x{id:06X}"),
58            Self::GenerateKey(slot) => format!("key generation in slot 0x{slot:02x}"),
59            Self::Sign(slot) => format!("signature with slot 0x{slot:02x}"),
60            Self::Ecdh(slot) => format!("key agreement with slot 0x{slot:02x}"),
61            Self::GetMetadata(slot) => format!("metadata of slot 0x{slot:02x}"),
62            Self::Command => "card command".to_owned(),
63        }
64    }
65}
66
67/// What the failing step knew when it failed (spec 0025 §1).
68#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
69pub struct ErrCtx {
70    /// The management key algorithm yb used, when known.
71    pub algo: Option<MgmtAlgo>,
72}
73
74/// The PC/SC step that failed.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum PcscOp {
77    Establish,
78    ListReaders,
79    Connect,
80    Transmit,
81}
82
83impl PcscOp {
84    fn describe(self) -> &'static str {
85        match self {
86            Self::Establish => "PC/SC context",
87            Self::ListReaders => "listing PC/SC readers",
88            Self::Connect => "connecting to the YubiKey",
89            Self::Transmit => "exchanging data with the YubiKey",
90        }
91    }
92}
93
94/// A PC/SC failure, reduced to the cases the catalog explains.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum PcscCode {
97    NoService,
98    NoReaders,
99    SharingViolation,
100    RemovedCard,
101    ResetCard,
102    /// Anything else, with the PC/SC library's description.
103    Other(String),
104}
105
106impl From<pcsc::Error> for PcscCode {
107    fn from(e: pcsc::Error) -> Self {
108        match e {
109            pcsc::Error::NoService | pcsc::Error::ServiceStopped => Self::NoService,
110            pcsc::Error::NoReadersAvailable | pcsc::Error::UnknownReader => Self::NoReaders,
111            pcsc::Error::SharingViolation => Self::SharingViolation,
112            pcsc::Error::RemovedCard | pcsc::Error::NoSmartcard => Self::RemovedCard,
113            pcsc::Error::ResetCard => Self::ResetCard,
114            other => Self::Other(format!("{other:?}")),
115        }
116    }
117}
118
119impl fmt::Display for PcscCode {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            Self::NoService => f.write_str("SCARD_E_NO_SERVICE"),
123            Self::NoReaders => f.write_str("SCARD_E_NO_READERS_AVAILABLE"),
124            Self::SharingViolation => f.write_str("SCARD_E_SHARING_VIOLATION"),
125            Self::RemovedCard => f.write_str("SCARD_W_REMOVED_CARD"),
126            Self::ResetCard => f.write_str("SCARD_W_RESET_CARD"),
127            Self::Other(name) => write!(f, "PC/SC {name}"),
128        }
129    }
130}
131
132/// A failure reported by the card or by PC/SC.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum CardError {
135    /// The card answered with a status word other than 9000.
136    Status { op: CardOp, sw: u16, ctx: ErrCtx },
137    /// PC/SC failed.
138    Pcsc { op: PcscOp, code: PcscCode },
139    /// The card's answer made no sense (too short, malformed, a
140    /// cryptographic check failed).
141    Protocol { op: CardOp, what: String },
142}
143
144impl CardError {
145    pub fn status(op: CardOp, sw1: u8, sw2: u8) -> Self {
146        Self::Status {
147            op,
148            sw: u16::from_be_bytes([sw1, sw2]),
149            ctx: ErrCtx::default(),
150        }
151    }
152
153    pub fn with_ctx(mut self, new_ctx: ErrCtx) -> Self {
154        if let Self::Status { ref mut ctx, .. } = self {
155            *ctx = new_ctx;
156        }
157        self
158    }
159
160    pub fn pcsc(op: PcscOp, e: pcsc::Error) -> Self {
161        Self::Pcsc { op, code: e.into() }
162    }
163
164    pub fn protocol(op: CardOp, what: impl Into<String>) -> Self {
165        Self::Protocol {
166            op,
167            what: what.into(),
168        }
169    }
170
171    /// The details line content: operation and code, no identifying data.
172    pub fn details(&self) -> String {
173        match self {
174            Self::Status { op, sw, .. } => format!("{} → SW {sw:04X}", op.describe()),
175            Self::Pcsc { op, code } => format!("{} → {code}", op.describe()),
176            Self::Protocol { op, what } => format!("{} → {what}", op.describe()),
177        }
178    }
179}
180
181impl fmt::Display for CardError {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        f.write_str(&self.details())
184    }
185}
186
187impl std::error::Error for CardError {}
188
189/// One of yb's own errors (not from the card), phrased for the user.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct YbError {
192    pub what: String,
193    pub why: Option<String>,
194    pub fix: Option<String>,
195}
196
197impl YbError {
198    pub fn new(what: impl Into<String>) -> Self {
199        Self {
200            what: what.into(),
201            why: None,
202            fix: None,
203        }
204    }
205
206    pub fn why(mut self, why: impl Into<String>) -> Self {
207        self.why = Some(why.into());
208        self
209    }
210
211    pub fn fix(mut self, fix: impl Into<String>) -> Self {
212        self.fix = Some(fix.into());
213        self
214    }
215}
216
217impl fmt::Display for YbError {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        f.write_str(&self.what)?;
220        if let Some(ref why) = self.why {
221            write!(f, " ({why})")?;
222        }
223        if let Some(ref fix) = self.fix {
224            write!(f, "; {fix}")?;
225        }
226        Ok(())
227    }
228}
229
230impl std::error::Error for YbError {}