Skip to main content

myna_card/
error.rs

1//! Error types.
2
3use crate::apdu::StatusWord;
4
5/// Alias for [`std::result::Result`] with this crate's error type.
6pub type Result<T, E = Error> = std::result::Result<T, E>;
7
8/// Anything that can go wrong while talking to an Individual Number Card.
9#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12    /// An error reported by the PC/SC layer.
13    #[cfg(feature = "pcsc")]
14    #[error("PC/SC error: {0}")]
15    Pcsc(#[from] pcsc::Error),
16
17    /// No PC/SC reader is available.
18    #[error("no PC/SC reader is available")]
19    NoReader,
20
21    /// The named PC/SC reader does not exist.
22    #[error("no such PC/SC reader: {0}")]
23    ReaderNotFound(String),
24
25    /// The card returned a status word other than success.
26    #[error("card returned {0}")]
27    Status(StatusWord),
28
29    /// The response was too short to even contain a status word.
30    #[error("response is too short to contain a status word ({0} byte(s))")]
31    ShortResponse(usize),
32
33    /// The command data field exceeds what even an extended APDU can encode.
34    #[error("APDU data field is too long ({0} byte(s), maximum 65535)")]
35    DataTooLong(usize),
36
37    /// `Le` is outside the 1 to 65536 an APDU can ask for.
38    #[error("expected response length {0} is out of range (1 to 65536)")]
39    ExpectedLengthOutOfRange(u32),
40
41    /// The PIN was rejected.
42    ///
43    /// `retries` is the number of attempts left before the key is blocked, or `None` if the key
44    /// has no retry limit (JICSAP 5.2.2: the card answers 6300 rather than 63Cx).
45    #[error("incorrect PIN{}", match retries {
46        Some(n) => format!(", {n} attempt(s) remaining"),
47        None => String::from(" (retries are not limited)"),
48    })]
49    PinIncorrect {
50        /// Attempts left on the card's retry counter, or `None` if it is unlimited.
51        retries: Option<u8>,
52    },
53
54    /// The key is blocked because its retry counter reached zero.
55    #[error("PIN is blocked")]
56    PinBlocked,
57
58    /// The supplied PIN is not well formed.
59    #[error("invalid PIN: {0}")]
60    InvalidPin(&'static str),
61
62    /// The requested offset cannot be encoded in a short READ BINARY.
63    #[error("offset {0} is out of range for READ BINARY (maximum 32767)")]
64    OffsetOutOfRange(usize),
65
66    /// A DF name must be 1 to 16 bytes (JICSAP 4.2 (1)).
67    #[error("DF name must be 1 to 16 bytes, got {0}")]
68    InvalidDfName(usize),
69
70    /// The EF identifier has no short form; only `0001`-`001E` do (JICSAP 4.2 (2)).
71    #[error("EF identifier {0:04X} has no short form (must be 0001-001E)")]
72    NoShortEfId(u16),
73
74    /// Record 0 means "the current record", which this crate does not use.
75    #[error("record numbers start at 1")]
76    InvalidRecordNumber,
77
78    /// The data field does not suit the signing scheme it was given to.
79    #[error("{len} bytes is not a valid input for {scheme:?}")]
80    BadSigningInput {
81        /// The scheme the data was rejected for.
82        scheme: crate::ap::jpki::SignatureScheme,
83        /// How many bytes were supplied.
84        len: usize,
85    },
86
87    /// A card-verifiable certificate names a CA this crate has no key for.
88    ///
89    /// Distinct from a failed signature: nothing was checked. See [`crate::ca`].
90    #[error("no CA key for 証明者鍵ID {0}")]
91    UnknownCertificateAuthority(crate::data::KeyId),
92
93    /// A signature did not verify under the key it was checked against.
94    #[error("signature verification failed: {0}")]
95    SignatureInvalid(&'static str),
96
97    /// Data read from the card did not have the expected structure.
98    #[error("malformed data: {0}")]
99    Malformed(String),
100}
101
102impl Error {
103    /// Classify a status word, routing the ones we understand to dedicated variants
104    /// and everything else to [`Error::Status`].
105    ///
106    /// Two statuses mean "blocked": 63C0, returned by the attempt that exhausts the counter, and
107    /// 6984, returned by every attempt after that (JICSAP 5.2.2, example 1).
108    pub(crate) fn from_status(sw: StatusWord) -> Self {
109        match sw.value() {
110            0x63C0 | 0x6984 => Error::PinBlocked,
111            0x6300 => Error::PinIncorrect { retries: None },
112            _ => match sw.retries_remaining() {
113                Some(retries) => Error::PinIncorrect {
114                    retries: Some(retries),
115                },
116                None => Error::Status(sw),
117            },
118        }
119    }
120}