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}