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:
parent
b5ebc6b43c
commit
0b0e9c48fc
25 changed files with 789 additions and 745 deletions
|
@ -6,6 +6,7 @@
|
|||
members = [
|
||||
"openpgp-card",
|
||||
"openpgp-card-sequoia",
|
||||
"pcsc",
|
||||
"scdc",
|
||||
"card-functionality",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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),
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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)) =
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
18
pcsc/Cargo.toml
Normal 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
189
pcsc/src/lib.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue