openpgp-card/card-backend/src/lib.rs
Heiko Schaefer 31eee9e738
backend: add CardBackend::limit_card_caps
This mechanism allows the pcsc backend to signal to openpgp-card that a reader doesn't support "extended length".
2023-09-06 01:16:32 +02:00

202 lines
6 KiB
Rust

// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
//! A thin abstraction layer for accessing smart cards, including, but not
//! limited to, [openpgp-card](https://gitlab.com/openpgp-card/openpgp-card)
//! devices.
/// This trait defines a connection with a smart card via a
/// backend implementation (e.g. via the pcsc backend in the crate
/// [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc)).
///
/// A [CardBackend] is only used to get access to a [CardTransaction] object,
/// which supports transmitting commands to the card.
pub trait CardBackend {
/// If a CardBackend introduces a additional (possibly backend-specific)
/// limits for any fields in CardCaps, this fn can indicate that limit by
/// returning an amended [`CardCaps`].
fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps;
fn transaction(
&mut self,
reselect_application: Option<&[u8]>,
) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, SmartcardError>;
}
/// The CardTransaction trait defines communication with a smart card via a
/// backend implementation (e.g. the pcsc backend in the crate
/// [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc)),
/// after opening a transaction from a CardBackend.
pub trait CardTransaction {
/// Transmit the command data in `cmd` to the card.
///
/// `buf_size` is a hint to the backend (the backend may ignore it)
/// indicating the expected maximum response size.
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>, SmartcardError>;
/// Select `application` on the card
fn select(&mut self, application: &[u8]) -> Result<Vec<u8>, SmartcardError> {
let mut cmd = vec![0x00, 0xa4, 0x04, 0x00]; // CLA, INS, P1, P2
cmd.push(application.len() as u8); // Lc
cmd.extend_from_slice(application); // Data
cmd.push(0x00); // Le
self.transmit(&cmd, 254)
}
/// If a CardTransaction implementation introduces an additional,
/// backend-specific limit for maximum number of bytes per command,
/// this fn can indicate that limit by returning `Some(max_cmd_len)`.
fn max_cmd_len(&self) -> Option<usize> {
None
}
/// Does the reader support FEATURE_VERIFY_PIN_DIRECT?
fn feature_pinpad_verify(&self) -> bool;
/// Does the reader support FEATURE_MODIFY_PIN_DIRECT?
fn feature_pinpad_modify(&self) -> bool;
/// Verify the PIN `pin` via the reader pinpad
fn pinpad_verify(
&mut self,
pin: PinType,
card_caps: &Option<CardCaps>,
) -> Result<Vec<u8>, SmartcardError>;
/// Modify the PIN `pin` via the reader pinpad
fn pinpad_modify(
&mut self,
pin: PinType,
card_caps: &Option<CardCaps>,
) -> Result<Vec<u8>, SmartcardError>;
/// Has a reset been detected while starting this transaction?
///
/// (Backends may choose to always return false)
fn was_reset(&self) -> bool;
}
/// Information about the capabilities of a card.
///
/// CardCaps is used to signal capabilities (chaining, extended length support, max
/// command/response sizes, max PIN lengths) of the current card to backends.
///
/// CardCaps is not intended for users of this library.
///
/// (The information is gathered from the "Card Capabilities", "Extended length information" and
/// "PWStatus" DOs)
#[derive(Clone, Copy, Debug)]
pub struct CardCaps {
ext_support: bool,
chaining_support: bool,
max_cmd_bytes: u16,
max_rsp_bytes: u16,
pw1_max_len: u8,
pw3_max_len: u8,
}
impl CardCaps {
pub fn new(
ext_support: bool,
chaining_support: bool,
max_cmd_bytes: u16,
max_rsp_bytes: u16,
pw1_max_len: u8,
pw3_max_len: u8,
) -> Self {
Self {
ext_support,
chaining_support,
max_cmd_bytes,
max_rsp_bytes,
pw1_max_len,
pw3_max_len,
}
}
/// Does the card support extended Lc and Le fields?
pub fn ext_support(&self) -> bool {
self.ext_support
}
/// Does the card support command chaining?
pub fn chaining_support(&self) -> bool {
self.chaining_support
}
/// Maximum number of bytes in a command APDU
pub fn max_cmd_bytes(&self) -> u16 {
self.max_cmd_bytes
}
/// Maximum number of bytes in a response APDU
pub fn max_rsp_bytes(&self) -> u16 {
self.max_rsp_bytes
}
/// Maximum length of PW1
pub fn pw1_max_len(&self) -> u8 {
self.pw1_max_len
}
/// Maximum length of PW3
pub fn pw3_max_len(&self) -> u8 {
self.pw3_max_len
}
}
/// Specify a PIN to *verify* (distinguishes between `Sign`, `User` and `Admin`).
///
/// (Note that for PIN *management*, in particular changing a PIN, "signing and user" are
/// not distinguished. They always share the same PIN value `PW1`)
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PinType {
/// Verify PW1 in mode P2=81 (for the PSO:CDS operation)
Sign,
/// Verify PW1 in mode P2=82 (for all other User operations)
User,
/// Verify PW3 (for Admin operations)
Admin,
}
impl PinType {
pub fn id(&self) -> u8 {
match self {
PinType::Sign => 0x81,
PinType::User => 0x82,
PinType::Admin => 0x83,
}
}
}
/// Errors on the smartcard/reader layer
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum SmartcardError {
#[error("Failed to create a pcsc smartcard context {0}")]
ContextError(String),
#[error("Failed to list readers: {0}")]
ReaderError(String),
#[error("No reader found.")]
NoReaderFoundError,
#[error("The requested card '{0}' was not found.")]
CardNotFound(String),
#[error("Failed to connect to the card: {0}")]
SmartCardConnectionError(String),
#[error("Smart card status: [{0}, {1}]")]
SmartCardStatus(u8, u8),
#[error("NotTransacted (SCARD_E_NOT_TRANSACTED)")]
NotTransacted,
#[error("Generic SmartCard Error: {0}")]
Error(String),
}