card_backend/
lib.rs

1// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer <heiko@schaefer.name>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! A thin abstraction layer for accessing smart cards, including, but not
5//! limited to, [openpgp-card](https://gitlab.com/openpgp-card/openpgp-card)
6//! devices.
7
8/// This trait defines a connection with a smart card via a
9/// backend implementation (e.g. via the pcsc backend in the crate
10/// [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc)).
11///
12/// A [CardBackend] is only used to get access to a [CardTransaction] object,
13/// which supports transmitting commands to the card.
14pub trait CardBackend {
15    /// If a CardBackend introduces a additional (possibly backend-specific)
16    /// limits for any fields in CardCaps, this fn can indicate that limit by
17    /// returning an amended [`CardCaps`].
18    fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps;
19
20    fn transaction(
21        &mut self,
22        reselect_application: Option<&[u8]>,
23    ) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, SmartcardError>;
24}
25
26/// The CardTransaction trait defines communication with a smart card via a
27/// backend implementation (e.g. the pcsc backend in the crate
28/// [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc)),
29/// after opening a transaction from a CardBackend.
30pub trait CardTransaction {
31    /// Transmit the command data in `cmd` to the card.
32    ///
33    /// `buf_size` is a hint to the backend (the backend may ignore it)
34    /// indicating the expected maximum response size.
35    fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>, SmartcardError>;
36
37    /// Select `application` on the card
38    fn select(&mut self, application: &[u8]) -> Result<Vec<u8>, SmartcardError> {
39        let mut cmd = vec![0x00, 0xa4, 0x04, 0x00]; // CLA, INS, P1, P2
40        cmd.push(application.len() as u8); // Lc
41        cmd.extend_from_slice(application); // Data
42        cmd.push(0x00); // Le
43
44        self.transmit(&cmd, 254)
45    }
46
47    /// If a CardTransaction implementation introduces an additional,
48    /// backend-specific limit for maximum number of bytes per command,
49    /// this fn can indicate that limit by returning `Some(max_cmd_len)`.
50    fn max_cmd_len(&self) -> Option<usize> {
51        None
52    }
53
54    /// Does the reader support FEATURE_VERIFY_PIN_DIRECT?
55    fn feature_pinpad_verify(&self) -> bool;
56
57    /// Does the reader support FEATURE_MODIFY_PIN_DIRECT?
58    fn feature_pinpad_modify(&self) -> bool;
59
60    /// Verify the PIN `pin` via the reader pinpad
61    fn pinpad_verify(
62        &mut self,
63        pin: PinType,
64        card_caps: &Option<CardCaps>,
65    ) -> Result<Vec<u8>, SmartcardError>;
66
67    /// Modify the PIN `pin` via the reader pinpad
68    fn pinpad_modify(
69        &mut self,
70        pin: PinType,
71        card_caps: &Option<CardCaps>,
72    ) -> Result<Vec<u8>, SmartcardError>;
73
74    /// Has a reset been detected while starting this transaction?
75    ///
76    /// (Backends may choose to always return false)
77    fn was_reset(&self) -> bool;
78}
79
80/// Information about the capabilities of a card.
81///
82/// CardCaps is used to signal capabilities (chaining, extended length support, max
83/// command/response sizes, max PIN lengths) of the current card to backends.
84///
85/// CardCaps is not intended for users of this library.
86///
87/// (The information is gathered from the "Card Capabilities", "Extended length information" and
88/// "PWStatus" DOs)
89#[derive(Clone, Copy, Debug)]
90pub struct CardCaps {
91    ext_support: bool,
92    chaining_support: bool,
93    max_cmd_bytes: u16,
94    max_rsp_bytes: u16,
95    pw1_max_len: u8,
96    pw3_max_len: u8,
97}
98
99impl CardCaps {
100    pub fn new(
101        ext_support: bool,
102        chaining_support: bool,
103        max_cmd_bytes: u16,
104        max_rsp_bytes: u16,
105        pw1_max_len: u8,
106        pw3_max_len: u8,
107    ) -> Self {
108        Self {
109            ext_support,
110            chaining_support,
111            max_cmd_bytes,
112            max_rsp_bytes,
113            pw1_max_len,
114            pw3_max_len,
115        }
116    }
117
118    /// Does the card support extended Lc and Le fields?
119    pub fn ext_support(&self) -> bool {
120        self.ext_support
121    }
122
123    /// Does the card support command chaining?
124    pub fn chaining_support(&self) -> bool {
125        self.chaining_support
126    }
127
128    /// Maximum number of bytes in a command APDU
129    pub fn max_cmd_bytes(&self) -> u16 {
130        self.max_cmd_bytes
131    }
132
133    /// Maximum number of bytes in a response APDU
134    pub fn max_rsp_bytes(&self) -> u16 {
135        self.max_rsp_bytes
136    }
137
138    /// Maximum length of PW1
139    pub fn pw1_max_len(&self) -> u8 {
140        self.pw1_max_len
141    }
142
143    /// Maximum length of PW3
144    pub fn pw3_max_len(&self) -> u8 {
145        self.pw3_max_len
146    }
147}
148
149/// Specify a PIN to *verify* (distinguishes between `Sign`, `User` and `Admin`).
150///
151/// (Note that for PIN *management*, in particular changing a PIN, "signing and user" are
152/// not distinguished. They always share the same PIN value `PW1`)
153#[derive(Debug, Clone, Copy, Eq, PartialEq)]
154pub enum PinType {
155    /// Verify PW1 in mode P2=81 (for the PSO:CDS operation)
156    Sign,
157
158    /// Verify PW1 in mode P2=82 (for all other User operations)
159    User,
160
161    /// Verify PW3 (for Admin operations)
162    Admin,
163}
164
165impl PinType {
166    pub fn id(&self) -> u8 {
167        match self {
168            PinType::Sign => 0x81,
169            PinType::User => 0x82,
170            PinType::Admin => 0x83,
171        }
172    }
173}
174
175/// Errors on the smartcard/reader layer
176#[derive(thiserror::Error, Debug)]
177#[non_exhaustive]
178pub enum SmartcardError {
179    #[error("Failed to create a pcsc smartcard context {0}")]
180    ContextError(String),
181
182    #[error("Failed to list readers: {0}")]
183    ReaderError(String),
184
185    #[error("No reader found.")]
186    NoReaderFoundError,
187
188    #[error("The requested card '{0}' was not found.")]
189    CardNotFound(String),
190
191    #[error("Failed to connect to the card: {0}")]
192    SmartCardConnectionError(String),
193
194    #[error("Smart card status: [{0}, {1}]")]
195    SmartCardStatus(u8, u8),
196
197    #[error("NotTransacted (SCARD_E_NOT_TRANSACTED)")]
198    NotTransacted,
199
200    #[error("Generic SmartCard Error: {0}")]
201    Error(String),
202}