Refactor:

- Move high-level API from openpgp-card to openpgp-card-sequoia
- Move the pcsc backend into the separate crate openpgp-card-pcsc
This commit is contained in:
Heiko Schaefer 2021-08-18 14:03:54 +02:00
parent b5ebc6b43c
commit 0b0e9c48fc
25 changed files with 789 additions and 745 deletions

View file

@ -6,6 +6,7 @@
members = [
"openpgp-card",
"openpgp-card-sequoia",
"pcsc",
"scdc",
"card-functionality",
]

View file

@ -27,6 +27,7 @@ path = "src/other.rs"
openpgp-card = { path = "../openpgp-card" }
openpgp-card-sequoia = { path = "../openpgp-card-sequoia" }
openpgp-card-scdc = { path = "../scdc" }
openpgp-card-pcsc = { path = "../pcsc" }
sequoia-openpgp = "1.3"
anyhow = "1"
thiserror = "1.0"

View file

@ -8,9 +8,8 @@ use anyhow::{anyhow, Result};
use serde_derive::Deserialize;
use std::collections::BTreeMap;
use openpgp_card::apdu::PcscClient;
use openpgp_card::card_app::CardApp;
use openpgp_card::CardClientBox;
use openpgp_card_pcsc::PcscClient;
use openpgp_card_scdc::ScdClient;
#[derive(Debug, Deserialize)]
@ -98,14 +97,9 @@ impl TestCard {
let res = ScdClient::shutdown_scd(None);
log::trace!(" Attempt to shutdown scd: {:?}", res);
for card in PcscClient::list_cards()? {
let card_client = Box::new(card) as CardClientBox;
for card_client in PcscClient::list_cards()? {
let mut ca = CardApp::new(card_client);
// Select OpenPGP applet
let res = ca.select()?;
res.check_ok()?;
// Set Card Capabilities (chaining, command length, ..)
let ard = ca.get_app_data()?;
let app_id = CardApp::get_aid(&ard)?;

View file

@ -15,6 +15,7 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia"
sequoia-openpgp = "1.3"
openpgp-card = { path = "../openpgp-card", version = "0.0.1" }
openpgp-card-scdc = { path = "../scdc" }
openpgp-card-pcsc = { path = "../pcsc" }
chrono = "0.4"
anyhow = "1"
thiserror = "1"

View file

@ -9,6 +9,7 @@ use std::convert::TryFrom;
use std::convert::TryInto;
use std::error::Error;
use std::io;
use std::ops::{Deref, DerefMut};
use std::time::SystemTime;
use openpgp::armor;
@ -30,13 +31,18 @@ use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType};
use openpgp::{Cert, Packet};
use sequoia_openpgp as openpgp;
use crate::signer::CardSigner;
use openpgp_card::card_app::CardApp;
use openpgp_card::apdu::response::Response;
use openpgp_card::card_app::{CardApp, ARD};
use openpgp_card::{
errors::OpenpgpCardError, Algo, CardAdmin, CardUploadableKey, Curve,
EccKey, EccType, KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
errors::OpenpgpCardError, Algo, AlgoInfo, ApplicationId, CardClientBox,
CardHolder, CardUploadableKey, Curve, DecryptMe, EccKey, EccType,
ExtendedCap, ExtendedLengthInfo, Features, Fingerprint, Hash, Historical,
KeySet, KeyType, PWStatus, PrivateKeyMaterial, PublicKeyMaterial, RSAKey,
Sex,
};
use crate::signer::CardSigner;
mod decryptor;
pub mod signer;
@ -543,3 +549,379 @@ pub fn public_to_fingerprint(
assert_eq!(fp.len(), 20);
Ok(fp.try_into()?)
}
// --------
/// Representation of an opened OpenPGP card in its basic, freshly opened,
/// state (i.e. no passwords have been verified, default privileges apply).
pub struct CardBase {
card_app: CardApp,
// Cache of "application related data".
//
// FIXME: Should be invalidated when changing data on the card!
// (e.g. uploading keys, etc)
ard: ARD,
}
impl CardBase {
pub fn new(card_app: CardApp, ard: ARD) -> Self {
Self { card_app, ard }
}
/// Get a reference to the internal CardApp object (for use in tests)
pub fn get_card_app(&mut self) -> &mut CardApp {
&mut self.card_app
}
/// Set up connection (cache "application related data") to a
/// CardClient, on which the openpgp applet has already been opened.
pub fn open_card(ccb: CardClientBox) -> Result<Self, OpenpgpCardError> {
// read and cache "application related data"
let mut card_app = CardApp::new(ccb);
let ard = card_app.get_app_data()?;
card_app.init_caps(&ard)?;
Ok(Self { card_app, ard })
}
// --- application data ---
/// Load "application related data".
///
/// This is done once, after opening the OpenPGP card applet
/// (the data is stored in the OpenPGPCard object).
fn get_app_data(&mut self) -> Result<ARD> {
self.card_app.get_app_data()
}
pub fn get_aid(&self) -> Result<ApplicationId, OpenpgpCardError> {
CardApp::get_aid(&self.ard)
}
pub fn get_historical(&self) -> Result<Historical, OpenpgpCardError> {
CardApp::get_historical(&self.ard)
}
pub fn get_extended_length_information(
&self,
) -> Result<Option<ExtendedLengthInfo>> {
CardApp::get_extended_length_information(&self.ard)
}
pub fn get_general_feature_management() -> Option<bool> {
unimplemented!()
}
pub fn get_discretionary_data_objects() {
unimplemented!()
}
pub fn get_extended_capabilities(
&self,
) -> Result<ExtendedCap, OpenpgpCardError> {
CardApp::get_extended_capabilities(&self.ard)
}
pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result<Algo> {
CardApp::get_algorithm_attributes(&self.ard, key_type)
}
/// PW status Bytes
pub fn get_pw_status_bytes(&self) -> Result<PWStatus> {
CardApp::get_pw_status_bytes(&self.ard)
}
pub fn get_fingerprints(
&self,
) -> Result<KeySet<Fingerprint>, OpenpgpCardError> {
CardApp::get_fingerprints(&self.ard)
}
pub fn get_ca_fingerprints(&self) {
unimplemented!()
}
pub fn get_key_generation_times() {
unimplemented!()
}
pub fn get_key_information() {
unimplemented!()
}
pub fn get_uif_pso_cds() {
unimplemented!()
}
pub fn get_uif_pso_dec() {
unimplemented!()
}
pub fn get_uif_pso_aut() {
unimplemented!()
}
pub fn get_uif_attestation() {
unimplemented!()
}
// --- optional private DOs (0101 - 0104) ---
// --- login data (5e) ---
// --- URL (5f50) ---
pub fn get_url(&mut self) -> Result<String> {
self.card_app.get_url()
}
// --- cardholder related data (65) ---
pub fn get_cardholder_related_data(&mut self) -> Result<CardHolder> {
self.card_app.get_cardholder_related_data()
}
// --- security support template (7a) ---
pub fn get_security_support_template(&mut self) -> Result<Vec<u8>> {
self.card_app.get_security_support_template()
}
// DO "Algorithm Information" (0xFA)
pub fn list_supported_algo(&mut self) -> Result<Option<AlgoInfo>> {
// The DO "Algorithm Information" (Tag FA) shall be present if
// Algorithm attributes can be changed
let ec = self.get_extended_capabilities()?;
if !ec.features.contains(&Features::AlgoAttrsChangeable) {
// Algorithm attributes can not be changed,
// list_supported_algo is not supported
return Ok(None);
}
self.card_app.list_supported_algo()
}
// ----------
/// Delete all state on this OpenPGP card
pub fn factory_reset(&mut self) -> Result<()> {
self.card_app.factory_reset()
}
pub fn verify_pw1_for_signing(
mut self,
pin: &str,
) -> Result<CardSign, CardBase> {
assert!(pin.len() >= 6); // FIXME: Err
let res = self.card_app.verify_pw1_for_signing(pin);
if let Ok(resp) = res {
if resp.is_ok() {
return Ok(CardSign { oc: self });
}
}
Err(self)
}
pub fn check_pw1(&mut self) -> Result<Response, OpenpgpCardError> {
self.card_app.check_pw1()
}
pub fn verify_pw1(mut self, pin: &str) -> Result<CardUser, CardBase> {
assert!(pin.len() >= 6); // FIXME: Err
let res = self.card_app.verify_pw1(pin);
if let Ok(resp) = res {
if resp.is_ok() {
return Ok(CardUser { oc: self });
}
}
Err(self)
}
pub fn check_pw3(&mut self) -> Result<Response, OpenpgpCardError> {
self.card_app.check_pw3()
}
pub fn verify_pw3(mut self, pin: &str) -> Result<CardAdmin, CardBase> {
assert!(pin.len() >= 8); // FIXME: Err
let res = self.card_app.verify_pw3(pin);
if let Ok(resp) = res {
if resp.is_ok() {
return Ok(CardAdmin { oc: self });
}
}
Err(self)
}
}
/// An OpenPGP card after successful verification of PW1 in mode 82
/// (verification for operations other than signing)
pub struct CardUser {
oc: CardBase,
}
/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser.
impl Deref for CardUser {
type Target = CardBase;
fn deref(&self) -> &Self::Target {
&self.oc
}
}
/// Allow access to fn of CardBase, through CardUser.
impl DerefMut for CardUser {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.oc
}
}
impl CardUser {
/// Decrypt the ciphertext in `dm`, on the card.
pub fn decrypt(
&mut self,
dm: DecryptMe,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.decrypt(dm)
}
/// Run decryption operation on the smartcard
/// (7.2.11 PSO: DECIPHER)
pub(crate) fn pso_decipher(
&mut self,
data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.pso_decipher(data)
}
}
/// An OpenPGP card after successful verification of PW1 in mode 81
/// (verification for signing)
pub struct CardSign {
oc: CardBase,
}
/// Allow access to fn of CardBase, through CardSign.
impl Deref for CardSign {
type Target = CardBase;
fn deref(&self) -> &Self::Target {
&self.oc
}
}
/// Allow access to fn of CardBase, through CardSign.
impl DerefMut for CardSign {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.oc
}
}
// FIXME: depending on the setting in "PW1 Status byte", only one
// signature can be made after verification for signing
impl CardSign {
/// Sign the message in `hash`, on the card.
pub fn signature_for_hash(
&mut self,
hash: Hash,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.signature_for_hash(hash)
}
/// Run signing operation on the smartcard
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
pub(crate) fn compute_digital_signature(
&mut self,
data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.compute_digital_signature(data)
}
}
/// An OpenPGP card after successful verification of PW3 ("Admin privileges")
pub struct CardAdmin {
oc: CardBase,
}
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
impl Deref for CardAdmin {
type Target = CardBase;
fn deref(&self) -> &Self::Target {
&self.oc
}
}
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
impl DerefMut for CardAdmin {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.oc
}
}
impl CardAdmin {
pub fn set_name(
&mut self,
name: &str,
) -> Result<Response, OpenpgpCardError> {
if name.len() >= 40 {
return Err(anyhow!("name too long").into());
}
// All chars must be in ASCII7
if name.chars().any(|c| !c.is_ascii()) {
return Err(anyhow!("Invalid char in name").into());
};
self.card_app.set_name(name)
}
pub fn set_lang(
&mut self,
lang: &str,
) -> Result<Response, OpenpgpCardError> {
if lang.len() > 8 {
return Err(anyhow!("lang too long").into());
}
self.card_app.set_lang(lang)
}
pub fn set_sex(&mut self, sex: Sex) -> Result<Response, OpenpgpCardError> {
self.card_app.set_sex(sex)
}
pub fn set_url(
&mut self,
url: &str,
) -> Result<Response, OpenpgpCardError> {
if url.chars().any(|c| !c.is_ascii()) {
return Err(anyhow!("Invalid char in url").into());
}
// Check for max len
let ec = self.get_extended_capabilities()?;
if url.len() < ec.max_len_special_do as usize {
self.card_app.set_url(url)
} else {
Err(anyhow!("URL too long").into())
}
}
pub fn upload_key(
&mut self,
key: Box<dyn CardUploadableKey>,
key_type: KeyType,
) -> Result<(), OpenpgpCardError> {
self.card_app.upload_key(key, key_type)
}
}

View file

@ -8,9 +8,13 @@ use anyhow::Result;
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::Cert;
use openpgp_card::{CardBase, KeyType};
use openpgp_card::KeyType;
use openpgp_card_scdc::ScdClient;
use openpgp_card::card_app::CardApp;
use openpgp_card_pcsc::PcscClient;
use openpgp_card_sequoia::CardBase;
// Filename of test key and test message to use:
const TEST_KEY_PATH: &str = "example/test4k.sec";
@ -230,9 +234,15 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("The following OpenPGP cards are connected to your system:");
let cards = openpgp_card::CardBase::list_cards_pcsc()?;
let cards = PcscClient::list_cards()?;
for c in cards {
println!(" '{}'", c.get_aid()?.ident());
let mut ca = CardApp::new(c);
let ard = ca.get_app_data()?;
let app_id = CardApp::get_aid(&ard)?;
let ident = app_id.ident();
println!(" '{}'", ident);
}
}

View file

@ -12,7 +12,6 @@ repository = "https://gitlab.com/hkos/openpgp-card"
documentation = "https://docs.rs/crate/openpgp-card"
[dependencies]
pcsc = "2"
nom = "6"
hex-literal = "0.3"
anyhow = "1"

View file

@ -5,16 +5,16 @@ pub mod command;
pub mod commands;
pub mod response;
use anyhow::{anyhow, Result};
use pcsc::Card;
use anyhow::Result;
use std::convert::TryFrom;
use crate::apdu::command::Command;
use crate::apdu::response::Response;
use crate::card;
use crate::card_app::CardApp;
use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError};
use crate::{CardBase, CardCaps, CardClient, CardClientBox};
use crate::errors::{OcErrorStatus, OpenpgpCardError};
use crate::CardClientBox;
// "Maximum amount of bytes in a short APDU command or response" (from pcsc)
const MAX_BUFFER_SIZE: usize = 264;
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) enum Le {
@ -121,7 +121,7 @@ fn send_command_low_level(
log::debug!(" -> full APDU command: {:x?}", cmd);
let buf_size = if !ext_support || ext == Le::Short {
pcsc::MAX_BUFFER_SIZE
MAX_BUFFER_SIZE
} else {
max_rsp_bytes
};
@ -198,68 +198,3 @@ fn send_command_low_level(
Ok(resp)
}
}
pub struct PcscClient {
card: Card,
card_caps: Option<CardCaps>,
}
impl PcscClient {
fn new(card: Card) -> Self {
Self {
card,
card_caps: None,
}
}
pub fn list_cards() -> Result<Vec<PcscClient>> {
Ok(card::get_cards()
.map_err(|err| anyhow!(err))?
.into_iter()
.map(PcscClient::new)
.collect())
}
/// Take a PCSC Card object and try to open the OpenPGP card applet.
/// If successful, wrap and return the resulting CardClient as a
/// CardBase object (which involves caching the "application related
/// data").
pub fn open(card: Card) -> Result<CardBase, OpenpgpCardError> {
let card_client = PcscClient::new(card);
let ccb = Box::new(card_client) as CardClientBox;
let mut ca = CardApp::new(ccb);
let resp = ca.select()?;
if resp.is_ok() {
CardBase::open_card(ca.take_card())
} else {
Err(anyhow!("Couldn't open OpenPGP application").into())
}
}
}
impl CardClient for PcscClient {
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>> {
let mut resp_buffer = vec![0; buf_size];
let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| {
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
"Transmit failed: {:?}",
e
)))
})?;
log::debug!(" <- APDU response: {:x?}", resp);
Ok(resp.to_vec())
}
fn init_caps(&mut self, caps: CardCaps) {
self.card_caps = Some(caps);
}
fn get_caps(&self) -> Option<&CardCaps> {
self.card_caps.as_ref()
}
}

View file

@ -1,57 +0,0 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::Result;
use pcsc::{Card, Context, Error, Protocols, Scope, ShareMode};
use crate::errors;
pub fn get_cards() -> Result<Vec<Card>, errors::SmartcardError> {
let ctx = match Context::establish(Scope::User) {
Ok(ctx) => ctx,
Err(err) => {
return Err(errors::SmartcardError::ContextError(err.to_string()))
}
};
// List available readers.
let mut readers_buf = [0; 2048];
let readers = match ctx.list_readers(&mut readers_buf) {
Ok(readers) => readers,
Err(err) => {
return Err(errors::SmartcardError::ReaderError(err.to_string()));
}
};
let mut found_reader = false;
let mut cards = vec![];
// Find a reader with a SmartCard.
for reader in readers {
// We've seen at least one smartcard reader
found_reader = true;
// Try connecting to card in this reader
let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY)
{
Ok(card) => card,
Err(Error::NoSmartcard) => {
continue; // try next reader
}
Err(err) => {
return Err(errors::SmartcardError::SmartCardConnectionError(
err.to_string(),
));
}
};
cards.push(card);
}
if !found_reader {
Err(errors::SmartcardError::NoReaderFoundError)
} else {
Ok(cards)
}
}

View file

@ -17,19 +17,18 @@ use anyhow::{anyhow, Result};
use crate::apdu::{commands, response::Response};
use crate::errors::OpenpgpCardError;
use crate::parse::{
algo_info::AlgoInfo, application_id::ApplicationId,
cardholder::CardHolder, extended_cap::ExtendedCap,
extended_length_info::ExtendedLengthInfo, fingerprint,
historical::Historical, key_generation_times, pw_status::PWStatus, KeySet,
};
use crate::parse::{fingerprint, key_generation_times};
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
use crate::{
apdu, keys, Algo, AlgoSimple, CardCaps, CardClientBox, CardUploadableKey,
DecryptMe, EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial,
RsaAttrs, Sex,
apdu, keys, Algo, AlgoInfo, AlgoSimple, ApplicationId, CardCaps,
CardClientBox, CardHolder, CardUploadableKey, DecryptMe, EccType,
ExtendedCap, ExtendedLengthInfo, Fingerprint, Hash, Historical,
KeyGeneration, KeySet, KeyType, PWStatus, PublicKeyMaterial, RsaAttrs,
Sex,
};
pub struct ARD(Tlv);
pub struct CardApp {
card_client: CardClientBox,
}
@ -39,7 +38,7 @@ impl CardApp {
Self { card_client }
}
pub(crate) fn take_card(self) -> CardClientBox {
pub fn take_card(self) -> CardClientBox {
self.card_client
}
@ -47,7 +46,7 @@ impl CardApp {
///
/// Also initializes the underlying CardClient with the caps - some
/// implementations may need this information.
pub fn init_caps(&mut self, ard: &Tlv) -> Result<()> {
pub fn init_caps(&mut self, ard: &ARD) -> Result<()> {
// Determine chaining/extended length support from card
// metadata and cache this information in CardApp (as a
// CardCaps)
@ -98,23 +97,24 @@ impl CardApp {
/// Load "application related data".
///
/// This is done once, after opening the OpenPGP card applet
/// (the data is stored in the OpenPGPCard object).
pub fn get_app_data(&mut self) -> Result<Tlv> {
/// This data should probably cached in a higher layer, some parts of
/// it are needed regularly, and it will not usually change, during
/// normal use of a card.
pub fn get_app_data(&mut self) -> Result<ARD> {
let ad = commands::get_application_data();
let resp = apdu::send_command(&mut self.card_client, ad, true)?;
let entry = TlvEntry::from(resp.data()?, true)?;
log::debug!(" App data TlvEntry: {:x?}", entry);
Ok(Tlv(Tag::from([0x6E]), entry))
Ok(ARD(Tlv(Tag::from([0x6E]), entry)))
}
// --- pieces of application related data ---
pub fn get_aid(ard: &Tlv) -> Result<ApplicationId, OpenpgpCardError> {
pub fn get_aid(ard: &ARD) -> Result<ApplicationId, OpenpgpCardError> {
// get from cached "application related data"
let aid = ard.find(&Tag::from([0x4F]));
let aid = ard.0.find(&Tag::from([0x4F]));
if let Some(aid) = aid {
Ok(ApplicationId::try_from(&aid.serialize()[..])?)
@ -123,9 +123,9 @@ impl CardApp {
}
}
pub fn get_historical(ard: &Tlv) -> Result<Historical, OpenpgpCardError> {
pub fn get_historical(ard: &ARD) -> Result<Historical, OpenpgpCardError> {
// get from cached "application related data"
let hist = ard.find(&Tag::from([0x5F, 0x52]));
let hist = ard.0.find(&Tag::from([0x5F, 0x52]));
if let Some(hist) = hist {
log::debug!("Historical bytes: {:x?}", hist);
@ -136,10 +136,10 @@ impl CardApp {
}
pub fn get_extended_length_information(
ard: &Tlv,
ard: &ARD,
) -> Result<Option<ExtendedLengthInfo>> {
// get from cached "application related data"
let eli = ard.find(&Tag::from([0x7F, 0x66]));
let eli = ard.0.find(&Tag::from([0x7F, 0x66]));
log::debug!("Extended length information: {:x?}", eli);
@ -161,10 +161,10 @@ impl CardApp {
}
pub fn get_extended_capabilities(
ard: &Tlv,
ard: &ARD,
) -> Result<ExtendedCap, OpenpgpCardError> {
// get from cached "application related data"
let ecap = ard.find(&Tag::from([0xc0]));
let ecap = ard.0.find(&Tag::from([0xc0]));
if let Some(ecap) = ecap {
Ok(ExtendedCap::try_from(&ecap.serialize()[..])?)
@ -174,11 +174,11 @@ impl CardApp {
}
pub fn get_algorithm_attributes(
ard: &Tlv,
ard: &ARD,
key_type: KeyType,
) -> Result<Algo> {
// get from cached "application related data"
let aa = ard.find(&Tag::from([key_type.get_algorithm_tag()]));
let aa = ard.0.find(&Tag::from([key_type.get_algorithm_tag()]));
if let Some(aa) = aa {
Algo::try_from(&aa.serialize()[..])
@ -191,9 +191,9 @@ impl CardApp {
}
/// PW status Bytes
pub fn get_pw_status_bytes(ard: &Tlv) -> Result<PWStatus> {
pub fn get_pw_status_bytes(ard: &ARD) -> Result<PWStatus> {
// get from cached "application related data"
let psb = ard.find(&Tag::from([0xc4]));
let psb = ard.0.find(&Tag::from([0xc4]));
if let Some(psb) = psb {
let pws = PWStatus::try_from(&psb.serialize())?;
@ -207,10 +207,10 @@ impl CardApp {
}
pub fn get_fingerprints(
ard: &Tlv,
) -> Result<KeySet<fingerprint::Fingerprint>, OpenpgpCardError> {
ard: &ARD,
) -> Result<KeySet<Fingerprint>, OpenpgpCardError> {
// Get from cached "application related data"
let fp = ard.find(&Tag::from([0xc5]));
let fp = ard.0.find(&Tag::from([0xc5]));
if let Some(fp) = fp {
let fp = fingerprint::from(&fp.serialize())?;
@ -230,9 +230,9 @@ impl CardApp {
}
pub fn get_key_generation_times(
ard: &Tlv,
ard: &ARD,
) -> Result<KeySet<KeyGeneration>, OpenpgpCardError> {
let kg = ard.find(&Tag::from([0xCD]));
let kg = ard.0.find(&Tag::from([0xCD]));
if let Some(kg) = kg {
let kg = key_generation_times::from(&kg.serialize())?;
@ -290,12 +290,14 @@ impl CardApp {
}
// --- security support template (7a) ---
pub fn get_security_support_template(&mut self) -> Result<Tlv> {
// FIXME: parse data into a proper data structure
pub fn get_security_support_template(&mut self) -> Result<Vec<u8>> {
let sst = commands::get_security_support_template();
let resp = apdu::send_command(&mut self.card_client, sst, true)?;
resp.check_ok()?;
Tlv::try_from(resp.data()?)
let tlv = Tlv::try_from(resp.data()?)?;
Ok(tlv.serialize())
}
// DO "Algorithm Information" (0xFA)
@ -430,7 +432,7 @@ impl CardApp {
/// Run decryption operation on the smartcard
/// (7.2.11 PSO: DECIPHER)
pub(crate) fn pso_decipher(
pub fn pso_decipher(
&mut self,
data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> {
@ -483,7 +485,7 @@ impl CardApp {
/// Run signing operation on the smartcard
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
pub(crate) fn compute_digital_signature(
pub fn compute_digital_signature(
&mut self,
data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> {

View file

@ -151,6 +151,9 @@ pub enum SmartcardError {
#[error("The requested card '{0}' was not found.")]
CardNotFound(String),
#[error("Couldn't select the OpenPGP card application")]
SelectOpenPGPCardFailed,
#[error("Failed to connect to the card: {0}")]
SmartCardConnectionError(String),

View file

@ -10,11 +10,10 @@ use crate::apdu::command::Command;
use crate::apdu::commands;
use crate::card_app::CardApp;
use crate::errors::OpenpgpCardError;
use crate::parse::algo_info::AlgoInfo;
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
use crate::{apdu, Curve, EccPub, PublicKeyMaterial, RSAPub};
use crate::{
tlv, Algo, CardUploadableKey, EccAttrs, EccKey, KeyType,
tlv, Algo, AlgoInfo, CardUploadableKey, EccAttrs, EccKey, KeyType,
PrivateKeyMaterial, RSAKey, RsaAttrs,
};

View file

@ -1,23 +1,11 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Result};
use anyhow::Result;
use std::collections::HashSet;
use std::fmt;
use std::ops::{Deref, DerefMut};
use apdu::{response::Response, PcscClient};
use card_app::CardApp;
use errors::{OpenpgpCardError, SmartcardError};
use parse::{
algo_info::AlgoInfo, application_id::ApplicationId,
cardholder::CardHolder, extended_cap::ExtendedCap, extended_cap::Features,
extended_length_info::ExtendedLengthInfo, fingerprint,
historical::Historical, pw_status::PWStatus, KeySet,
};
use tlv::Tlv;
pub mod apdu;
mod card;
pub mod card_app;
pub mod errors;
mod keys;
@ -169,6 +157,9 @@ impl AlgoSimple {
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AlgoInfo(Vec<(KeyType, Algo)>);
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Algo {
Rsa(RsaAttrs),
@ -430,6 +421,93 @@ pub enum DecryptMe<'a> {
ECDH(&'a [u8]),
}
// ----------
#[derive(Debug, Eq, PartialEq)]
pub struct ApplicationId {
pub application: u8,
// GnuPG says:
// if (app->appversion >= 0x0200)
// app->app_local->extcap.is_v2 = 1;
//
// if (app->appversion >= 0x0300)
// app->app_local->extcap.is_v3 = 1;
pub version: u16,
pub manufacturer: u16,
pub serial: u32,
}
#[derive(Debug)]
pub struct CardCapabilities {
command_chaining: bool,
extended_lc_le: bool,
extended_length_information: bool,
}
#[derive(Debug)]
pub struct CardSeviceData {
select_by_full_df_name: bool,
select_by_partial_df_name: bool,
dos_available_in_ef_dir: bool,
dos_available_in_ef_atr_info: bool,
access_services: [bool; 3],
mf: bool,
}
#[derive(Debug)]
pub struct Historical {
// category indicator byte
cib: u8,
// Card service data (31)
csd: Option<CardSeviceData>,
// Card Capabilities (73)
cc: Option<CardCapabilities>,
// status indicator byte (o-card 3.4.1, pg 44)
sib: u8,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ExtendedCap {
pub features: HashSet<Features>,
sm: u8,
max_len_challenge: u16,
max_len_cardholder_cert: u16,
pub max_len_special_do: u16,
pin_2_format: bool,
mse_command: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Features {
SecureMessaging,
GetChallenge,
KeyImport,
PwStatusChange,
PrivateUseDOs,
AlgoAttrsChangeable,
Aes,
KdfDo,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ExtendedLengthInfo {
pub max_command_bytes: u16,
pub max_response_bytes: u16,
}
#[derive(Debug)]
pub struct CardHolder {
pub name: Option<String>,
pub lang: Option<Vec<[char; 2]>>,
pub sex: Option<Sex>,
}
#[derive(Debug, PartialEq)]
pub enum Sex {
NotKnown,
@ -460,6 +538,29 @@ impl From<u8> for Sex {
}
}
#[derive(Debug)]
pub struct PWStatus {
pub(crate) pw1_cds_multi: bool,
pub(crate) pw1_derived: bool,
pub(crate) pw1_len: u8,
pub(crate) rc_len: u8,
pub(crate) pw3_derived: bool,
pub(crate) pw3_len: u8,
pub(crate) err_count_pw1: u8,
pub(crate) err_count_rst: u8,
pub(crate) err_count_pw3: u8,
}
#[derive(Clone, Eq, PartialEq)]
pub struct Fingerprint([u8; 20]);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeySet<T> {
signature: Option<T>,
decryption: Option<T>,
authentication: Option<T>,
}
/// Enum to identify one of the Key-slots on an OpenPGP card
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum KeyType {
@ -520,439 +621,6 @@ impl KeyType {
}
}
/// Representation of an opened OpenPGP card in its basic, freshly opened,
/// state (i.e. no passwords have been verified, default privileges apply).
pub struct CardBase {
card_app: CardApp,
// Cache of "application related data".
//
// FIXME: Should be invalidated when changing data on the card!
// (e.g. uploading keys, etc)
ard: Tlv,
}
impl CardBase {
pub fn new(card_app: CardApp, ard: Tlv) -> Self {
Self { card_app, ard }
}
/// Get a reference to the internal CardApp object (for use in tests)
pub fn get_card_app(&mut self) -> &mut CardApp {
&mut self.card_app
}
/// Get all cards that can be opened as an OpenPGP card applet via pcsc
pub fn list_cards_pcsc() -> Result<Vec<Self>> {
let cards = card::get_cards().map_err(|err| anyhow!(err))?;
let ocs: Vec<_> = cards
.into_iter()
.map(PcscClient::open)
.map(|oc| oc.ok())
.flatten()
.collect();
Ok(ocs)
}
/// Find an OpenPGP card by "ident", open and return it.
///
/// The ident is constructed as a concatenation of manufacturer
/// id, a colon, and the card serial. Example: "1234:5678ABCD".
pub fn open_by_ident_pcsc(ident: &str) -> Result<Self, OpenpgpCardError> {
let cards = card::get_cards().map_err(|e| {
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
"{:?}",
e
)))
})?;
for card in cards {
let res = PcscClient::open(card);
if let Ok(opened_card) = res {
let res = opened_card.get_aid();
if let Ok(aid) = res {
if aid.ident() == ident {
return Ok(opened_card);
}
}
}
}
Err(OpenpgpCardError::Smartcard(SmartcardError::CardNotFound(
ident.to_string(),
)))
}
/// Open connection to some card and select the openpgp applet
pub fn open_yolo_pcsc() -> Result<Self, OpenpgpCardError> {
let mut cards = card::get_cards().map_err(|e| {
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
"{:?}",
e
)))
})?;
// randomly use the first card in the list
let card = cards.swap_remove(0);
PcscClient::open(card)
}
/// Set up connection (cache "application related data") to a
/// CardClient, on which the openpgp applet has already been opened.
pub fn open_card(ccb: CardClientBox) -> Result<Self, OpenpgpCardError> {
// read and cache "application related data"
let mut card_app = CardApp::new(ccb);
let ard = card_app.get_app_data()?;
card_app.init_caps(&ard)?;
Ok(Self { card_app, ard })
}
// --- application data ---
/// Load "application related data".
///
/// This is done once, after opening the OpenPGP card applet
/// (the data is stored in the OpenPGPCard object).
fn get_app_data(&mut self) -> Result<Tlv> {
self.card_app.get_app_data()
}
pub fn get_aid(&self) -> Result<ApplicationId, OpenpgpCardError> {
CardApp::get_aid(&self.ard)
}
pub fn get_historical(&self) -> Result<Historical, OpenpgpCardError> {
CardApp::get_historical(&self.ard)
}
pub fn get_extended_length_information(
&self,
) -> Result<Option<ExtendedLengthInfo>> {
CardApp::get_extended_length_information(&self.ard)
}
pub fn get_general_feature_management() -> Option<bool> {
unimplemented!()
}
pub fn get_discretionary_data_objects() {
unimplemented!()
}
pub fn get_extended_capabilities(
&self,
) -> Result<ExtendedCap, OpenpgpCardError> {
CardApp::get_extended_capabilities(&self.ard)
}
pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result<Algo> {
CardApp::get_algorithm_attributes(&self.ard, key_type)
}
/// PW status Bytes
pub fn get_pw_status_bytes(&self) -> Result<PWStatus> {
CardApp::get_pw_status_bytes(&self.ard)
}
pub fn get_fingerprints(
&self,
) -> Result<KeySet<fingerprint::Fingerprint>, OpenpgpCardError> {
CardApp::get_fingerprints(&self.ard)
}
pub fn get_ca_fingerprints(&self) {
unimplemented!()
}
pub fn get_key_generation_times() {
unimplemented!()
}
pub fn get_key_information() {
unimplemented!()
}
pub fn get_uif_pso_cds() {
unimplemented!()
}
pub fn get_uif_pso_dec() {
unimplemented!()
}
pub fn get_uif_pso_aut() {
unimplemented!()
}
pub fn get_uif_attestation() {
unimplemented!()
}
// --- optional private DOs (0101 - 0104) ---
// --- login data (5e) ---
// --- URL (5f50) ---
pub fn get_url(&mut self) -> Result<String> {
self.card_app.get_url()
}
// --- cardholder related data (65) ---
pub fn get_cardholder_related_data(&mut self) -> Result<CardHolder> {
self.card_app.get_cardholder_related_data()
}
// --- security support template (7a) ---
pub fn get_security_support_template(&mut self) -> Result<Tlv> {
self.card_app.get_security_support_template()
}
// DO "Algorithm Information" (0xFA)
pub fn list_supported_algo(&mut self) -> Result<Option<AlgoInfo>> {
// The DO "Algorithm Information" (Tag FA) shall be present if
// Algorithm attributes can be changed
let ec = self.get_extended_capabilities()?;
if !ec.features.contains(&Features::AlgoAttrsChangeable) {
// Algorithm attributes can not be changed,
// list_supported_algo is not supported
return Ok(None);
}
self.card_app.list_supported_algo()
}
// ----------
/// Delete all state on this OpenPGP card
pub fn factory_reset(&mut self) -> Result<()> {
self.card_app.factory_reset()
}
pub fn verify_pw1_for_signing(
mut self,
pin: &str,
) -> Result<CardSign, CardBase> {
assert!(pin.len() >= 6); // FIXME: Err
let res = self.card_app.verify_pw1_for_signing(pin);
if let Ok(resp) = res {
if resp.is_ok() {
return Ok(CardSign { oc: self });
}
}
Err(self)
}
pub fn check_pw1(&mut self) -> Result<Response, OpenpgpCardError> {
self.card_app.check_pw1()
}
pub fn verify_pw1(mut self, pin: &str) -> Result<CardUser, CardBase> {
assert!(pin.len() >= 6); // FIXME: Err
let res = self.card_app.verify_pw1(pin);
if let Ok(resp) = res {
if resp.is_ok() {
return Ok(CardUser { oc: self });
}
}
Err(self)
}
pub fn check_pw3(&mut self) -> Result<Response, OpenpgpCardError> {
self.card_app.check_pw3()
}
pub fn verify_pw3(mut self, pin: &str) -> Result<CardAdmin, CardBase> {
assert!(pin.len() >= 8); // FIXME: Err
let res = self.card_app.verify_pw3(pin);
if let Ok(resp) = res {
if resp.is_ok() {
return Ok(CardAdmin { oc: self });
}
}
Err(self)
}
}
/// An OpenPGP card after successful verification of PW1 in mode 82
/// (verification for operations other than signing)
pub struct CardUser {
oc: CardBase,
}
/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser.
impl Deref for CardUser {
type Target = CardBase;
fn deref(&self) -> &Self::Target {
&self.oc
}
}
/// Allow access to fn of CardBase, through CardUser.
impl DerefMut for CardUser {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.oc
}
}
impl CardUser {
/// Decrypt the ciphertext in `dm`, on the card.
pub fn decrypt(
&mut self,
dm: DecryptMe,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.decrypt(dm)
}
/// Run decryption operation on the smartcard
/// (7.2.11 PSO: DECIPHER)
pub(crate) fn pso_decipher(
&mut self,
data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.pso_decipher(data)
}
}
/// An OpenPGP card after successful verification of PW1 in mode 81
/// (verification for signing)
pub struct CardSign {
oc: CardBase,
}
/// Allow access to fn of CardBase, through CardSign.
impl Deref for CardSign {
type Target = CardBase;
fn deref(&self) -> &Self::Target {
&self.oc
}
}
/// Allow access to fn of CardBase, through CardSign.
impl DerefMut for CardSign {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.oc
}
}
// FIXME: depending on the setting in "PW1 Status byte", only one
// signature can be made after verification for signing
impl CardSign {
/// Sign the message in `hash`, on the card.
pub fn signature_for_hash(
&mut self,
hash: Hash,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.signature_for_hash(hash)
}
/// Run signing operation on the smartcard
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
pub(crate) fn compute_digital_signature(
&mut self,
data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> {
self.card_app.compute_digital_signature(data)
}
}
/// An OpenPGP card after successful verification of PW3 ("Admin privileges")
pub struct CardAdmin {
oc: CardBase,
}
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
impl Deref for CardAdmin {
type Target = CardBase;
fn deref(&self) -> &Self::Target {
&self.oc
}
}
/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin.
impl DerefMut for CardAdmin {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.oc
}
}
impl CardAdmin {
pub fn set_name(
&mut self,
name: &str,
) -> Result<Response, OpenpgpCardError> {
if name.len() >= 40 {
return Err(anyhow!("name too long").into());
}
// All chars must be in ASCII7
if name.chars().any(|c| !c.is_ascii()) {
return Err(anyhow!("Invalid char in name").into());
};
self.card_app.set_name(name)
}
pub fn set_lang(
&mut self,
lang: &str,
) -> Result<Response, OpenpgpCardError> {
if lang.len() > 8 {
return Err(anyhow!("lang too long").into());
}
self.card_app.set_lang(lang)
}
pub fn set_sex(&mut self, sex: Sex) -> Result<Response, OpenpgpCardError> {
self.card_app.set_sex(sex)
}
pub fn set_url(
&mut self,
url: &str,
) -> Result<Response, OpenpgpCardError> {
if url.chars().any(|c| !c.is_ascii()) {
return Err(anyhow!("Invalid char in url").into());
}
// Check for max len
let ec = self.get_extended_capabilities()?;
if url.len() < ec.max_len_special_do as usize {
self.card_app.set_url(url)
} else {
Err(anyhow!("URL too long").into())
}
}
pub fn upload_key(
&mut self,
key: Box<dyn CardUploadableKey>,
key_type: KeyType,
) -> Result<(), OpenpgpCardError> {
let algo_list = self.list_supported_algo()?;
keys::upload_key(&mut self.card_app, key, key_type, algo_list)
}
}
#[cfg(test)]
mod test {
use super::tlv::tag::Tag;

View file

@ -10,10 +10,7 @@ use nom::{branch, bytes::complete as bytes, combinator, multi, sequence};
use std::fmt;
use crate::parse::algo_attrs;
use crate::{Algo, KeyType};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AlgoInfo(Vec<(KeyType, Algo)>);
use crate::{Algo, AlgoInfo, KeyType};
impl AlgoInfo {
pub fn get_by_keytype(&self, kt: KeyType) -> Vec<&Algo> {

View file

@ -5,24 +5,7 @@ use anyhow::Result;
use nom::{bytes::complete as bytes, number::complete as number};
use std::convert::TryFrom;
use crate::parse;
#[derive(Debug, Eq, PartialEq)]
pub struct ApplicationId {
pub application: u8,
// GnuPG says:
// if (app->appversion >= 0x0200)
// app->app_local->extcap.is_v2 = 1;
//
// if (app->appversion >= 0x0300)
// app->app_local->extcap.is_v3 = 1;
pub version: u16,
pub manufacturer: u16,
pub serial: u32,
}
use crate::{parse, ApplicationId};
fn parse(input: &[u8]) -> nom::IResult<&[u8], ApplicationId> {
let (input, _) = bytes::tag([0xd2, 0x76, 0x0, 0x1, 0x24])(input)?;

View file

@ -7,14 +7,7 @@ use anyhow::Result;
use crate::tlv::tag::Tag;
use crate::tlv::{Tlv, TlvEntry};
use crate::Sex;
#[derive(Debug)]
pub struct CardHolder {
pub name: Option<String>,
pub lang: Option<Vec<[char; 2]>>,
pub sex: Option<Sex>,
}
use crate::{CardHolder, Sex};
impl TryFrom<&[u8]> for CardHolder {
type Error = anyhow::Error;

View file

@ -8,28 +8,7 @@ use nom::{combinator, number::complete as number, sequence};
use std::collections::HashSet;
use std::convert::TryFrom;
#[derive(Debug, Eq, PartialEq)]
pub struct ExtendedCap {
pub features: HashSet<Features>,
sm: u8,
max_len_challenge: u16,
max_len_cardholder_cert: u16,
pub max_len_special_do: u16,
pin_2_format: bool,
mse_command: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Features {
SecureMessaging,
GetChallenge,
KeyImport,
PwStatusChange,
PrivateUseDOs,
AlgoAttrsChangeable,
Aes,
KdfDo,
}
use crate::{ExtendedCap, Features};
fn features(input: &[u8]) -> nom::IResult<&[u8], HashSet<Features>> {
combinator::map(number::u8, |b| {

View file

@ -1,15 +1,10 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::parse;
use anyhow::Result;
use nom::{bytes::complete::tag, number::complete as number, sequence};
#[derive(Debug, Eq, PartialEq)]
pub struct ExtendedLengthInfo {
pub max_command_bytes: u16,
pub max_response_bytes: u16,
}
use crate::{parse, ExtendedLengthInfo};
fn parse(input: &[u8]) -> nom::IResult<&[u8], (u16, u16)> {
let (input, (_, cmd, _, resp)) =

View file

@ -7,9 +7,7 @@ use std::fmt;
use crate::errors::OpenpgpCardError;
use crate::parse::KeySet;
#[derive(Clone, Eq, PartialEq)]
pub struct Fingerprint([u8; 20]);
use crate::Fingerprint;
impl From<[u8; 20]> for Fingerprint {
fn from(data: [u8; 20]) -> Self {

View file

@ -2,15 +2,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::errors::OpenpgpCardError;
use crate::{CardCapabilities, CardSeviceData, Historical};
use anyhow::{anyhow, Result};
#[derive(Debug)]
pub struct CardCapabilities {
command_chaining: bool,
extended_lc_le: bool,
extended_length_information: bool,
}
impl CardCapabilities {
pub fn get_command_chaining(&self) -> bool {
self.command_chaining
@ -39,16 +33,6 @@ impl CardCapabilities {
}
}
#[derive(Debug)]
pub struct CardSeviceData {
select_by_full_df_name: bool,
select_by_partial_df_name: bool,
dos_available_in_ef_dir: bool,
dos_available_in_ef_atr_info: bool,
access_services: [bool; 3],
mf: bool,
}
impl CardSeviceData {
pub fn from(data: u8) -> Self {
let select_by_full_df_name = data & 0x80 != 0;
@ -70,21 +54,6 @@ impl CardSeviceData {
}
}
#[derive(Debug)]
pub struct Historical {
// category indicator byte
cib: u8,
// Card service data (31)
csd: Option<CardSeviceData>,
// Card Capabilities (73)
cc: Option<CardCapabilities>,
// status indicator byte (o-card 3.4.1, pg 44)
sib: u8,
}
impl Historical {
pub fn get_card_capabilities(&self) -> Option<&CardCapabilities> {
self.cc.as_ref()

View file

@ -15,15 +15,9 @@ pub mod historical;
pub mod key_generation_times;
pub mod pw_status;
use crate::KeySet;
use anyhow::{anyhow, Error};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeySet<T> {
signature: Option<T>,
decryption: Option<T>,
authentication: Option<T>,
}
impl<T> From<(Option<T>, Option<T>, Option<T>)> for KeySet<T> {
fn from(tuple: (Option<T>, Option<T>, Option<T>)) -> Self {
Self {

View file

@ -2,21 +2,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::errors::OpenpgpCardError;
use crate::PWStatus;
use anyhow::anyhow;
#[derive(Debug)]
pub struct PWStatus {
pub(crate) pw1_cds_multi: bool,
pub(crate) pw1_derived: bool,
pub(crate) pw1_len: u8,
pub(crate) rc_len: u8,
pub(crate) pw3_derived: bool,
pub(crate) pw3_len: u8,
pub(crate) err_count_pw1: u8,
pub(crate) err_count_rst: u8,
pub(crate) err_count_pw3: u8,
}
impl PWStatus {
pub fn try_from(input: &[u8]) -> Result<Self, OpenpgpCardError> {
if input.len() == 7 {

18
pcsc/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
# SPDX-License-Identifier: MIT OR Apache-2.0
[package]
name = "openpgp-card-pcsc"
description = "PCSC OpenPGP card backend, for use with the openpgp-card crate"
authors = ["Heiko Schaefer <heiko@schaefer.name>"]
license = "MIT OR Apache-2.0"
version = "0.0.1"
edition = "2018"
repository = "https://gitlab.com/hkos/openpgp-card"
documentation = "https://docs.rs/crate/openpgp-card-pcsc"
[dependencies]
openpgp-card = { path = "../openpgp-card" }
pcsc = "2"
anyhow = "1"
log = "0.4"

189
pcsc/src/lib.rs Normal file
View file

@ -0,0 +1,189 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Result};
use pcsc::{Card, Context, Error, Protocols, Scope, ShareMode};
use openpgp_card::card_app::CardApp;
use openpgp_card::errors::{OpenpgpCardError, SmartcardError};
use openpgp_card::{CardCaps, CardClient, CardClientBox};
pub struct PcscClient {
card: Card,
card_caps: Option<CardCaps>,
}
impl PcscClient {
fn new(card: Card) -> Self {
Self {
card,
card_caps: None,
}
}
/// Opened PCSC Cards without selecting the OpenPGP card application
fn pcsc_cards() -> Result<Vec<Card>, SmartcardError> {
let ctx = match Context::establish(Scope::User) {
Ok(ctx) => ctx,
Err(err) => {
return Err(SmartcardError::ContextError(err.to_string()))
}
};
// List available readers.
let mut readers_buf = [0; 2048];
let readers = match ctx.list_readers(&mut readers_buf) {
Ok(readers) => readers,
Err(err) => {
return Err(SmartcardError::ReaderError(err.to_string()));
}
};
let mut found_reader = false;
let mut cards = vec![];
// Find a reader with a SmartCard.
for reader in readers {
// We've seen at least one smartcard reader
found_reader = true;
// Try connecting to card in this reader
let card =
match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) {
Ok(card) => card,
Err(Error::NoSmartcard) => {
continue; // try next reader
}
Err(err) => {
return Err(SmartcardError::SmartCardConnectionError(
err.to_string(),
));
}
};
cards.push(card);
}
if !found_reader {
Err(SmartcardError::NoReaderFoundError)
} else {
Ok(cards)
}
}
/// All PCSC cards, wrapped as PcscClient
fn unopened_cards() -> Result<Vec<PcscClient>> {
Ok(Self::pcsc_cards()
.map_err(|err| anyhow!(err))?
.into_iter()
.map(PcscClient::new)
.collect())
}
/// Return all cards on which the OpenPGP application could be selected.
pub fn list_cards() -> Result<Vec<CardClientBox>> {
let cards = Self::unopened_cards()?
.into_iter()
.map(Self::select)
.map(|res| res.ok())
.flatten()
.map(|ca| ca.take_card())
.collect();
Ok(cards)
}
/// Try to select the OpenPGP application on a card
fn select(card_client: PcscClient) -> Result<CardApp, OpenpgpCardError> {
let ccb = Box::new(card_client) as CardClientBox;
let mut ca = CardApp::new(ccb);
let resp = ca.select()?;
if resp.is_ok() {
Ok(ca)
} else {
Err(OpenpgpCardError::Smartcard(
SmartcardError::SelectOpenPGPCardFailed,
))
}
}
/// Returns the first OpenPGP card, with the OpenPGP application selected.
///
/// If multiple cards are connected, this will effectively be a random
/// pick. You should consider using `open_by_ident` instead.
pub fn open_yolo() -> Result<CardClientBox, OpenpgpCardError> {
for card in Self::unopened_cards()? {
if let Ok(ca) = Self::select(card) {
return Ok(ca.take_card());
}
}
Err(OpenpgpCardError::Smartcard(SmartcardError::CardNotFound(
"No OpenPGP card found".to_string(),
)))
}
/// Get application related data from the card and check if 'ident'
/// matches
fn match_by_ident(
mut ca: CardApp,
ident: &str,
) -> Result<Option<CardClientBox>, OpenpgpCardError> {
let ard = ca.get_app_data()?;
let aid = CardApp::get_aid(&ard)?;
if aid.ident() == ident {
Ok(Some(ca.take_card()))
} else {
Ok(None)
}
}
/// Returns the OpenPGP card that matches `ident`, if it is available.
/// The OpenPGP application of the `CardClientBox` has been selected.
pub fn open_by_ident(
ident: &str,
) -> Result<CardClientBox, OpenpgpCardError> {
for card in Self::unopened_cards()? {
if let Ok(ca) = Self::select(card) {
if let Some(matched_card) =
PcscClient::match_by_ident(ca, ident)?
{
return Ok(matched_card);
}
}
}
Err(OpenpgpCardError::Smartcard(SmartcardError::CardNotFound(
ident.to_string(),
)))
}
}
impl CardClient for PcscClient {
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>> {
let mut resp_buffer = vec![0; buf_size];
let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| {
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
"Transmit failed: {:?}",
e
)))
})?;
log::debug!(" <- APDU response: {:x?}", resp);
Ok(resp.to_vec())
}
fn init_caps(&mut self, caps: CardCaps) {
self.card_caps = Some(caps);
}
fn get_caps(&self) -> Option<&CardCaps> {
self.card_caps.as_ref()
}
}

View file

@ -3,14 +3,17 @@
[package]
name = "openpgp-card-scdc"
description = "Experimental SCDaemon Client, intended for use with the openpgp-card crate"
description = "Experimental SCDaemon Client, for use with the openpgp-card crate"
authors = ["Heiko Schaefer <heiko@schaefer.name>"]
license = "MIT OR Apache-2.0"
version = "0.0.1"
edition = "2018"
repository = "https://gitlab.com/hkos/openpgp-card"
documentation = "https://docs.rs/crate/openpgp-card-scdc"
[dependencies]
openpgp-card = { path = "../openpgp-card" }
sequoia-ipc = { git = "https://gitlab.com/sequoia-pgp/sequoia.git" }
libc = "0.2"
hex = "0.4"
anyhow = "1"
futures = "0.3"