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 = [ members = [
"openpgp-card", "openpgp-card",
"openpgp-card-sequoia", "openpgp-card-sequoia",
"pcsc",
"scdc", "scdc",
"card-functionality", "card-functionality",
] ]

View file

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

View file

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

View file

@ -9,6 +9,7 @@ use std::convert::TryFrom;
use std::convert::TryInto; use std::convert::TryInto;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::ops::{Deref, DerefMut};
use std::time::SystemTime; use std::time::SystemTime;
use openpgp::armor; use openpgp::armor;
@ -30,13 +31,18 @@ use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType};
use openpgp::{Cert, Packet}; use openpgp::{Cert, Packet};
use sequoia_openpgp as openpgp; use sequoia_openpgp as openpgp;
use crate::signer::CardSigner; use openpgp_card::apdu::response::Response;
use openpgp_card::card_app::CardApp; use openpgp_card::card_app::{CardApp, ARD};
use openpgp_card::{ use openpgp_card::{
errors::OpenpgpCardError, Algo, CardAdmin, CardUploadableKey, Curve, errors::OpenpgpCardError, Algo, AlgoInfo, ApplicationId, CardClientBox,
EccKey, EccType, KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, 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; mod decryptor;
pub mod signer; pub mod signer;
@ -543,3 +549,379 @@ pub fn public_to_fingerprint(
assert_eq!(fp.len(), 20); assert_eq!(fp.len(), 20);
Ok(fp.try_into()?) 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::parse::Parse;
use sequoia_openpgp::Cert; use sequoia_openpgp::Cert;
use openpgp_card::{CardBase, KeyType}; use openpgp_card::KeyType;
use openpgp_card_scdc::ScdClient; 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: // Filename of test key and test message to use:
const TEST_KEY_PATH: &str = "example/test4k.sec"; 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:"); 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 { 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" documentation = "https://docs.rs/crate/openpgp-card"
[dependencies] [dependencies]
pcsc = "2"
nom = "6" nom = "6"
hex-literal = "0.3" hex-literal = "0.3"
anyhow = "1" anyhow = "1"

View file

@ -5,16 +5,16 @@ pub mod command;
pub mod commands; pub mod commands;
pub mod response; pub mod response;
use anyhow::{anyhow, Result}; use anyhow::Result;
use pcsc::Card;
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::apdu::command::Command; use crate::apdu::command::Command;
use crate::apdu::response::Response; use crate::apdu::response::Response;
use crate::card; use crate::errors::{OcErrorStatus, OpenpgpCardError};
use crate::card_app::CardApp; use crate::CardClientBox;
use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError};
use crate::{CardBase, CardCaps, CardClient, 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)] #[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) enum Le { pub(crate) enum Le {
@ -121,7 +121,7 @@ fn send_command_low_level(
log::debug!(" -> full APDU command: {:x?}", cmd); log::debug!(" -> full APDU command: {:x?}", cmd);
let buf_size = if !ext_support || ext == Le::Short { let buf_size = if !ext_support || ext == Le::Short {
pcsc::MAX_BUFFER_SIZE MAX_BUFFER_SIZE
} else { } else {
max_rsp_bytes max_rsp_bytes
}; };
@ -198,68 +198,3 @@ fn send_command_low_level(
Ok(resp) 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::apdu::{commands, response::Response};
use crate::errors::OpenpgpCardError; use crate::errors::OpenpgpCardError;
use crate::parse::{ use crate::parse::{fingerprint, key_generation_times};
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::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::tlv::{tag::Tag, Tlv, TlvEntry};
use crate::{ use crate::{
apdu, keys, Algo, AlgoSimple, CardCaps, CardClientBox, CardUploadableKey, apdu, keys, Algo, AlgoInfo, AlgoSimple, ApplicationId, CardCaps,
DecryptMe, EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial, CardClientBox, CardHolder, CardUploadableKey, DecryptMe, EccType,
RsaAttrs, Sex, ExtendedCap, ExtendedLengthInfo, Fingerprint, Hash, Historical,
KeyGeneration, KeySet, KeyType, PWStatus, PublicKeyMaterial, RsaAttrs,
Sex,
}; };
pub struct ARD(Tlv);
pub struct CardApp { pub struct CardApp {
card_client: CardClientBox, card_client: CardClientBox,
} }
@ -39,7 +38,7 @@ impl CardApp {
Self { card_client } Self { card_client }
} }
pub(crate) fn take_card(self) -> CardClientBox { pub fn take_card(self) -> CardClientBox {
self.card_client self.card_client
} }
@ -47,7 +46,7 @@ impl CardApp {
/// ///
/// Also initializes the underlying CardClient with the caps - some /// Also initializes the underlying CardClient with the caps - some
/// implementations may need this information. /// 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 // Determine chaining/extended length support from card
// metadata and cache this information in CardApp (as a // metadata and cache this information in CardApp (as a
// CardCaps) // CardCaps)
@ -98,23 +97,24 @@ impl CardApp {
/// Load "application related data". /// Load "application related data".
/// ///
/// This is done once, after opening the OpenPGP card applet /// This data should probably cached in a higher layer, some parts of
/// (the data is stored in the OpenPGPCard object). /// it are needed regularly, and it will not usually change, during
pub fn get_app_data(&mut self) -> Result<Tlv> { /// normal use of a card.
pub fn get_app_data(&mut self) -> Result<ARD> {
let ad = commands::get_application_data(); let ad = commands::get_application_data();
let resp = apdu::send_command(&mut self.card_client, ad, true)?; let resp = apdu::send_command(&mut self.card_client, ad, true)?;
let entry = TlvEntry::from(resp.data()?, true)?; let entry = TlvEntry::from(resp.data()?, true)?;
log::debug!(" App data TlvEntry: {:x?}", entry); 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 --- // --- 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" // 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 { if let Some(aid) = aid {
Ok(ApplicationId::try_from(&aid.serialize()[..])?) 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" // 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 { if let Some(hist) = hist {
log::debug!("Historical bytes: {:x?}", hist); log::debug!("Historical bytes: {:x?}", hist);
@ -136,10 +136,10 @@ impl CardApp {
} }
pub fn get_extended_length_information( pub fn get_extended_length_information(
ard: &Tlv, ard: &ARD,
) -> Result<Option<ExtendedLengthInfo>> { ) -> Result<Option<ExtendedLengthInfo>> {
// get from cached "application related data" // 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); log::debug!("Extended length information: {:x?}", eli);
@ -161,10 +161,10 @@ impl CardApp {
} }
pub fn get_extended_capabilities( pub fn get_extended_capabilities(
ard: &Tlv, ard: &ARD,
) -> Result<ExtendedCap, OpenpgpCardError> { ) -> Result<ExtendedCap, OpenpgpCardError> {
// get from cached "application related data" // 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 { if let Some(ecap) = ecap {
Ok(ExtendedCap::try_from(&ecap.serialize()[..])?) Ok(ExtendedCap::try_from(&ecap.serialize()[..])?)
@ -174,11 +174,11 @@ impl CardApp {
} }
pub fn get_algorithm_attributes( pub fn get_algorithm_attributes(
ard: &Tlv, ard: &ARD,
key_type: KeyType, key_type: KeyType,
) -> Result<Algo> { ) -> Result<Algo> {
// get from cached "application related data" // 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 { if let Some(aa) = aa {
Algo::try_from(&aa.serialize()[..]) Algo::try_from(&aa.serialize()[..])
@ -191,9 +191,9 @@ impl CardApp {
} }
/// PW status Bytes /// 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" // 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 { if let Some(psb) = psb {
let pws = PWStatus::try_from(&psb.serialize())?; let pws = PWStatus::try_from(&psb.serialize())?;
@ -207,10 +207,10 @@ impl CardApp {
} }
pub fn get_fingerprints( pub fn get_fingerprints(
ard: &Tlv, ard: &ARD,
) -> Result<KeySet<fingerprint::Fingerprint>, OpenpgpCardError> { ) -> Result<KeySet<Fingerprint>, OpenpgpCardError> {
// Get from cached "application related data" // 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 { if let Some(fp) = fp {
let fp = fingerprint::from(&fp.serialize())?; let fp = fingerprint::from(&fp.serialize())?;
@ -230,9 +230,9 @@ impl CardApp {
} }
pub fn get_key_generation_times( pub fn get_key_generation_times(
ard: &Tlv, ard: &ARD,
) -> Result<KeySet<KeyGeneration>, OpenpgpCardError> { ) -> Result<KeySet<KeyGeneration>, OpenpgpCardError> {
let kg = ard.find(&Tag::from([0xCD])); let kg = ard.0.find(&Tag::from([0xCD]));
if let Some(kg) = kg { if let Some(kg) = kg {
let kg = key_generation_times::from(&kg.serialize())?; let kg = key_generation_times::from(&kg.serialize())?;
@ -290,12 +290,14 @@ impl CardApp {
} }
// --- security support template (7a) --- // --- 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 sst = commands::get_security_support_template();
let resp = apdu::send_command(&mut self.card_client, sst, true)?; let resp = apdu::send_command(&mut self.card_client, sst, true)?;
resp.check_ok()?; resp.check_ok()?;
Tlv::try_from(resp.data()?) let tlv = Tlv::try_from(resp.data()?)?;
Ok(tlv.serialize())
} }
// DO "Algorithm Information" (0xFA) // DO "Algorithm Information" (0xFA)
@ -430,7 +432,7 @@ impl CardApp {
/// Run decryption operation on the smartcard /// Run decryption operation on the smartcard
/// (7.2.11 PSO: DECIPHER) /// (7.2.11 PSO: DECIPHER)
pub(crate) fn pso_decipher( pub fn pso_decipher(
&mut self, &mut self,
data: Vec<u8>, data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> { ) -> Result<Vec<u8>, OpenpgpCardError> {
@ -483,7 +485,7 @@ impl CardApp {
/// Run signing operation on the smartcard /// Run signing operation on the smartcard
/// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE)
pub(crate) fn compute_digital_signature( pub fn compute_digital_signature(
&mut self, &mut self,
data: Vec<u8>, data: Vec<u8>,
) -> Result<Vec<u8>, OpenpgpCardError> { ) -> Result<Vec<u8>, OpenpgpCardError> {

View file

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

View file

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

View file

@ -1,23 +1,11 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name> // SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Result}; use anyhow::Result;
use std::collections::HashSet;
use std::fmt; 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; pub mod apdu;
mod card;
pub mod card_app; pub mod card_app;
pub mod errors; pub mod errors;
mod keys; mod keys;
@ -169,6 +157,9 @@ impl AlgoSimple {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AlgoInfo(Vec<(KeyType, Algo)>);
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum Algo { pub enum Algo {
Rsa(RsaAttrs), Rsa(RsaAttrs),
@ -430,6 +421,93 @@ pub enum DecryptMe<'a> {
ECDH(&'a [u8]), 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)] #[derive(Debug, PartialEq)]
pub enum Sex { pub enum Sex {
NotKnown, 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 /// Enum to identify one of the Key-slots on an OpenPGP card
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum KeyType { 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)] #[cfg(test)]
mod test { mod test {
use super::tlv::tag::Tag; 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 std::fmt;
use crate::parse::algo_attrs; use crate::parse::algo_attrs;
use crate::{Algo, KeyType}; use crate::{Algo, AlgoInfo, KeyType};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AlgoInfo(Vec<(KeyType, Algo)>);
impl AlgoInfo { impl AlgoInfo {
pub fn get_by_keytype(&self, kt: KeyType) -> Vec<&Algo> { 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 nom::{bytes::complete as bytes, number::complete as number};
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::parse; use crate::{parse, ApplicationId};
#[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,
}
fn parse(input: &[u8]) -> nom::IResult<&[u8], ApplicationId> { fn parse(input: &[u8]) -> nom::IResult<&[u8], ApplicationId> {
let (input, _) = bytes::tag([0xd2, 0x76, 0x0, 0x1, 0x24])(input)?; 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::tag::Tag;
use crate::tlv::{Tlv, TlvEntry}; use crate::tlv::{Tlv, TlvEntry};
use crate::Sex; use crate::{CardHolder, Sex};
#[derive(Debug)]
pub struct CardHolder {
pub name: Option<String>,
pub lang: Option<Vec<[char; 2]>>,
pub sex: Option<Sex>,
}
impl TryFrom<&[u8]> for CardHolder { impl TryFrom<&[u8]> for CardHolder {
type Error = anyhow::Error; type Error = anyhow::Error;

View file

@ -8,28 +8,7 @@ use nom::{combinator, number::complete as number, sequence};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
#[derive(Debug, Eq, PartialEq)] use crate::{ExtendedCap, Features};
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,
}
fn features(input: &[u8]) -> nom::IResult<&[u8], HashSet<Features>> { fn features(input: &[u8]) -> nom::IResult<&[u8], HashSet<Features>> {
combinator::map(number::u8, |b| { combinator::map(number::u8, |b| {

View file

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

View file

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

View file

@ -2,15 +2,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use crate::errors::OpenpgpCardError; use crate::errors::OpenpgpCardError;
use crate::{CardCapabilities, CardSeviceData, Historical};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
#[derive(Debug)]
pub struct CardCapabilities {
command_chaining: bool,
extended_lc_le: bool,
extended_length_information: bool,
}
impl CardCapabilities { impl CardCapabilities {
pub fn get_command_chaining(&self) -> bool { pub fn get_command_chaining(&self) -> bool {
self.command_chaining 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 { impl CardSeviceData {
pub fn from(data: u8) -> Self { pub fn from(data: u8) -> Self {
let select_by_full_df_name = data & 0x80 != 0; 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 { impl Historical {
pub fn get_card_capabilities(&self) -> Option<&CardCapabilities> { pub fn get_card_capabilities(&self) -> Option<&CardCapabilities> {
self.cc.as_ref() self.cc.as_ref()

View file

@ -15,15 +15,9 @@ pub mod historical;
pub mod key_generation_times; pub mod key_generation_times;
pub mod pw_status; pub mod pw_status;
use crate::KeySet;
use anyhow::{anyhow, Error}; 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> { impl<T> From<(Option<T>, Option<T>, Option<T>)> for KeySet<T> {
fn from(tuple: (Option<T>, Option<T>, Option<T>)) -> Self { fn from(tuple: (Option<T>, Option<T>, Option<T>)) -> Self {
Self { Self {

View file

@ -2,21 +2,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use crate::errors::OpenpgpCardError; use crate::errors::OpenpgpCardError;
use crate::PWStatus;
use anyhow::anyhow; 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 { impl PWStatus {
pub fn try_from(input: &[u8]) -> Result<Self, OpenpgpCardError> { pub fn try_from(input: &[u8]) -> Result<Self, OpenpgpCardError> {
if input.len() == 7 { 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] [package]
name = "openpgp-card-scdc" 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" version = "0.0.1"
edition = "2018" edition = "2018"
repository = "https://gitlab.com/hkos/openpgp-card"
documentation = "https://docs.rs/crate/openpgp-card-scdc"
[dependencies] [dependencies]
openpgp-card = { path = "../openpgp-card" } openpgp-card = { path = "../openpgp-card" }
sequoia-ipc = { git = "https://gitlab.com/sequoia-pgp/sequoia.git" } sequoia-ipc = { git = "https://gitlab.com/sequoia-pgp/sequoia.git" }
libc = "0.2"
hex = "0.4" hex = "0.4"
anyhow = "1" anyhow = "1"
futures = "0.3" futures = "0.3"