Support scdaemon as an alternative backend for interaction with OpenPGP cards.
This commit is contained in:
parent
8e3c6c0046
commit
01126aabdf
11 changed files with 252 additions and 100 deletions
|
@ -6,4 +6,5 @@
|
||||||
members = [
|
members = [
|
||||||
"openpgp-card",
|
"openpgp-card",
|
||||||
"openpgp-card-sequoia",
|
"openpgp-card-sequoia",
|
||||||
|
"scdc",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,6 +14,7 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
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" }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
|
@ -282,7 +282,7 @@ pub fn decrypt(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign(
|
pub fn sign(
|
||||||
ocu: &mut CardSign,
|
ocu: CardSign,
|
||||||
cert: &sequoia_openpgp::Cert,
|
cert: &sequoia_openpgp::Cert,
|
||||||
input: &mut dyn io::Read,
|
input: &mut dyn io::Read,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
|
|
|
@ -8,7 +8,8 @@ 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;
|
||||||
|
|
||||||
// Filename of test key and test message to use:
|
// Filename of test key and test message to use:
|
||||||
|
|
||||||
|
@ -21,6 +22,8 @@ use openpgp_card::{CardBase, KeyType};
|
||||||
const TEST_KEY_PATH: &str = "example/test25519.sec";
|
const TEST_KEY_PATH: &str = "example/test25519.sec";
|
||||||
const TEST_ENC_MSG: &str = "example/encrypted_to_25519.asc";
|
const TEST_ENC_MSG: &str = "example/encrypted_to_25519.asc";
|
||||||
|
|
||||||
|
const SOCKET: &str = "/run/user/1000/gnupg/S.scdaemon";
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
@ -29,7 +32,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
if let Ok(test_card_ident) = test_card_ident {
|
if let Ok(test_card_ident) = test_card_ident {
|
||||||
println!("** get card");
|
println!("** get card");
|
||||||
let mut oc = CardBase::open_by_ident(&test_card_ident)?;
|
// let mut oc = CardBase::open_by_ident(&test_card_ident)?;
|
||||||
|
let mut oc = ScdClient::open_scdc(SOCKET)?;
|
||||||
|
|
||||||
// card metadata
|
// card metadata
|
||||||
|
|
||||||
|
@ -140,7 +144,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// Open fresh Card for decrypt
|
// Open fresh Card for decrypt
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
let mut oc = CardBase::open_by_ident(&test_card_ident)?;
|
// let mut oc = CardBase::open_by_ident(&test_card_ident)?;
|
||||||
|
let mut oc = ScdClient::open_scdc(SOCKET)?;
|
||||||
|
|
||||||
let app_id = oc.get_aid()?;
|
let app_id = oc.get_aid()?;
|
||||||
|
|
||||||
// Check that we're still using the expected card
|
// Check that we're still using the expected card
|
||||||
|
@ -179,18 +185,19 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Open fresh Card for signing
|
// Open fresh Card for signing
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
let oc = CardBase::open_by_ident(&test_card_ident)?;
|
// let oc = CardBase::open_by_ident(&test_card_ident)?;
|
||||||
|
let oc = ScdClient::open_scdc(SOCKET)?;
|
||||||
|
|
||||||
// Sign
|
// Sign
|
||||||
match oc.verify_pw1_for_signing("123456") {
|
match oc.verify_pw1_for_signing("123456") {
|
||||||
Ok(mut oc_user) => {
|
Ok(oc_user) => {
|
||||||
println!("pw1 81 verify ok");
|
println!("pw1 81 verify ok");
|
||||||
|
|
||||||
let cert = Cert::from_file(TEST_KEY_PATH)?;
|
let cert = Cert::from_file(TEST_KEY_PATH)?;
|
||||||
|
|
||||||
let text = "Hello world, I am signed.";
|
let text = "Hello world, I am signed.";
|
||||||
let res = openpgp_card_sequoia::sign(
|
let res = openpgp_card_sequoia::sign(
|
||||||
&mut oc_user,
|
oc_user,
|
||||||
&cert,
|
&cert,
|
||||||
&mut text.as_bytes(),
|
&mut text.as_bytes(),
|
||||||
);
|
);
|
||||||
|
@ -213,7 +220,7 @@ 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()?;
|
let cards = openpgp_card::CardBase::list_cards_pcsc()?;
|
||||||
for c in cards {
|
for c in cards {
|
||||||
println!(" '{}'", c.get_aid()?.ident());
|
println!(" '{}'", c.get_aid()?.ident());
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,26 +16,26 @@ use openpgp_card::Hash;
|
||||||
|
|
||||||
use crate::PublicKey;
|
use crate::PublicKey;
|
||||||
|
|
||||||
pub(crate) struct CardSigner<'a> {
|
pub(crate) struct CardSigner {
|
||||||
/// The OpenPGP card (authenticated to allow signing operations)
|
/// The OpenPGP card (authenticated to allow signing operations)
|
||||||
ocu: &'a mut CardSign,
|
ocu: CardSign,
|
||||||
|
|
||||||
/// The matching public key for the card's signing key
|
/// The matching public key for the card's signing key
|
||||||
public: PublicKey,
|
public: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CardSigner<'a> {
|
impl CardSigner {
|
||||||
/// Try to create a CardSigner.
|
/// Try to create a CardSigner.
|
||||||
///
|
///
|
||||||
/// An Error is returned if no match between the card's signing
|
/// An Error is returned if no match between the card's signing
|
||||||
/// key and a (sub)key of `cert` can be made.
|
/// key and a (sub)key of `cert` can be made.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ocs: &'a mut CardSign,
|
cs: CardSign,
|
||||||
cert: &openpgp::Cert,
|
cert: &openpgp::Cert,
|
||||||
policy: &dyn Policy,
|
policy: &dyn Policy,
|
||||||
) -> Result<CardSigner<'a>, OpenpgpCardError> {
|
) -> Result<CardSigner, OpenpgpCardError> {
|
||||||
// Get the fingerprint for the signing key from the card.
|
// Get the fingerprint for the signing key from the card.
|
||||||
let fps = ocs.get_fingerprints()?;
|
let fps = cs.get_fingerprints()?;
|
||||||
let fp = fps.signature();
|
let fp = fps.signature();
|
||||||
|
|
||||||
if let Some(fp) = fp {
|
if let Some(fp) = fp {
|
||||||
|
@ -58,7 +58,7 @@ impl<'a> CardSigner<'a> {
|
||||||
let public = keys[0].clone();
|
let public = keys[0].clone();
|
||||||
|
|
||||||
Ok(CardSigner {
|
Ok(CardSigner {
|
||||||
ocu: ocs,
|
ocu: cs,
|
||||||
public: public.role_as_unspecified().clone(),
|
public: public.role_as_unspecified().clone(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,7 +75,7 @@ impl<'a> CardSigner<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> crypto::Signer for CardSigner<'a> {
|
impl<'a> crypto::Signer for CardSigner {
|
||||||
fn public(&self) -> &PublicKey {
|
fn public(&self) -> &PublicKey {
|
||||||
&self.public
|
&self.public
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,14 @@ pub mod command;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use pcsc::Card;
|
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::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError};
|
use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError};
|
||||||
use crate::CardCaps;
|
use crate::{CardBase, CardCaps, CardClient, CardClientBox};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub(crate) enum Le {
|
pub(crate) enum Le {
|
||||||
|
@ -26,7 +26,7 @@ pub(crate) enum Le {
|
||||||
/// If the reply is truncated, this fn assembles all the parts and returns
|
/// If the reply is truncated, this fn assembles all the parts and returns
|
||||||
/// them as one aggregated Response.
|
/// them as one aggregated Response.
|
||||||
pub(crate) fn send_command(
|
pub(crate) fn send_command(
|
||||||
card_client: &mut Box<dyn CardClient + Send + Sync>,
|
card_client: &mut CardClientBox,
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
expect_reply: bool,
|
expect_reply: bool,
|
||||||
card_caps: Option<&CardCaps>,
|
card_caps: Option<&CardCaps>,
|
||||||
|
@ -70,7 +70,7 @@ pub(crate) fn send_command(
|
||||||
/// If the response is chained, this fn only returns one chunk, the caller
|
/// If the response is chained, this fn only returns one chunk, the caller
|
||||||
/// needs take care of chained responses
|
/// needs take care of chained responses
|
||||||
fn send_command_low_level(
|
fn send_command_low_level(
|
||||||
card_client: &mut Box<dyn CardClient + Send + Sync>,
|
card_client: &mut CardClientBox,
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
expect_reply: bool,
|
expect_reply: bool,
|
||||||
card_caps: Option<&CardCaps>,
|
card_caps: Option<&CardCaps>,
|
||||||
|
@ -179,18 +179,32 @@ fn send_command_low_level(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CardClient {
|
|
||||||
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PcscClient {
|
pub struct PcscClient {
|
||||||
card: Card,
|
card: Card,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PcscClient {
|
impl PcscClient {
|
||||||
pub fn new(card: Card) -> Self {
|
fn new(card: Card) -> Self {
|
||||||
Self { card }
|
Self { card }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 mut ccb = Box::new(card_client) as CardClientBox;
|
||||||
|
|
||||||
|
let select_openpgp = commands::select_openpgp();
|
||||||
|
let resp = send_command(&mut ccb, select_openpgp, false, None)?;
|
||||||
|
|
||||||
|
if resp.is_ok() {
|
||||||
|
CardBase::open_card(ccb)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Couldn't open OpenPGP application").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardClient for PcscClient {
|
impl CardClient for PcscClient {
|
||||||
|
|
|
@ -13,35 +13,28 @@ use std::borrow::BorrowMut;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use pcsc::*;
|
|
||||||
|
|
||||||
use apdu::{commands, response::Response};
|
use crate::apdu::{commands, response::Response};
|
||||||
use parse::{
|
use crate::errors::OpenpgpCardError;
|
||||||
|
use crate::parse::{
|
||||||
algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId,
|
algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId,
|
||||||
cardholder::CardHolder, extended_cap::ExtendedCap,
|
cardholder::CardHolder, extended_cap::ExtendedCap,
|
||||||
extended_length_info::ExtendedLengthInfo, fingerprint,
|
extended_length_info::ExtendedLengthInfo, fingerprint,
|
||||||
historical::Historical, pw_status::PWStatus, KeySet,
|
historical::Historical, pw_status::PWStatus, KeySet,
|
||||||
};
|
};
|
||||||
use tlv::Tlv;
|
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||||
|
|
||||||
use crate::errors::OpenpgpCardError;
|
|
||||||
use crate::tlv::tag::Tag;
|
|
||||||
use crate::tlv::TlvEntry;
|
|
||||||
|
|
||||||
use crate::apdu::CardClient;
|
|
||||||
use crate::Hash;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
apdu, key_upload, parse, tlv, CardCaps, CardUploadableKey, DecryptMe,
|
apdu, key_upload, parse, tlv, CardCaps, CardClientBox, CardUploadableKey,
|
||||||
KeyType, Sex,
|
DecryptMe, Hash, KeyType, Sex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct CardApp {
|
pub struct CardApp {
|
||||||
card_client: Box<dyn CardClient + Send + Sync>,
|
card_client: CardClientBox,
|
||||||
card_caps: Option<CardCaps>,
|
card_caps: Option<CardCaps>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardApp {
|
impl CardApp {
|
||||||
pub fn new(card_client: Box<dyn CardClient + Send + Sync>) -> Self {
|
pub fn new(card_client: CardClientBox) -> Self {
|
||||||
Self {
|
Self {
|
||||||
card_client,
|
card_client,
|
||||||
card_caps: None,
|
card_caps: None,
|
||||||
|
@ -55,7 +48,7 @@ impl CardApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn card(&mut self) -> &mut Box<dyn CardClient + Send + Sync> {
|
pub fn card(&mut self) -> &mut CardClientBox {
|
||||||
&mut self.card_client
|
&mut self.card_client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,17 @@
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use crate::apdu::command::Command;
|
use crate::apdu::{command::Command, commands};
|
||||||
use crate::apdu::{commands, CardClient};
|
|
||||||
use crate::card_app::CardApp;
|
use crate::card_app::CardApp;
|
||||||
use crate::errors::OpenpgpCardError;
|
use crate::errors::OpenpgpCardError;
|
||||||
use crate::parse::algo_attrs::{Algo, RsaAttrs};
|
use crate::parse::algo_attrs::{Algo, RsaAttrs};
|
||||||
use crate::parse::algo_info::AlgoInfo;
|
use crate::parse::algo_info::AlgoInfo;
|
||||||
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
use crate::tlv::{tag::Tag, Tlv, TlvEntry};
|
||||||
use crate::{apdu, CardCaps};
|
use crate::{apdu, CardCaps, CardClientBox};
|
||||||
use crate::{
|
use crate::{
|
||||||
tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial,
|
tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial,
|
||||||
RSAKey,
|
RSAKey,
|
||||||
};
|
};
|
||||||
use pcsc::Card;
|
|
||||||
|
|
||||||
/// Upload an explicitly selected Key to the card as a specific KeyType.
|
/// Upload an explicitly selected Key to the card as a specific KeyType.
|
||||||
///
|
///
|
||||||
|
@ -370,7 +368,7 @@ fn ecc_algo_attrs_cmd(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_key_to_card(
|
fn copy_key_to_card(
|
||||||
card_client: &mut Box<dyn CardClient + Send + Sync>,
|
card_client: &mut CardClientBox,
|
||||||
key_type: KeyType,
|
key_type: KeyType,
|
||||||
ts: u64,
|
ts: u64,
|
||||||
fp: Vec<u8>,
|
fp: Vec<u8>,
|
||||||
|
|
|
@ -13,28 +13,48 @@ use parse::{
|
||||||
};
|
};
|
||||||
use tlv::Tlv;
|
use tlv::Tlv;
|
||||||
|
|
||||||
use crate::apdu::{CardClient, PcscClient};
|
use crate::apdu::PcscClient;
|
||||||
use crate::card_app::CardApp;
|
use crate::card_app::CardApp;
|
||||||
use crate::errors::{OpenpgpCardError, SmartcardError};
|
use crate::errors::{OpenpgpCardError, SmartcardError};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
mod apdu;
|
mod apdu;
|
||||||
mod card;
|
mod card;
|
||||||
mod card_app;
|
pub mod card_app;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod key_upload;
|
mod key_upload;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod tlv;
|
mod tlv;
|
||||||
|
|
||||||
|
pub trait CardClient {
|
||||||
|
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type CardClientBox = Box<dyn CardClient + Send + Sync>;
|
||||||
|
|
||||||
/// Information about the capabilities of the card.
|
/// Information about the capabilities of the card.
|
||||||
/// (feature configuration from card metadata)
|
/// (feature configuration from card metadata)
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) struct CardCaps {
|
pub struct CardCaps {
|
||||||
pub(crate) ext_support: bool,
|
pub(crate) ext_support: bool,
|
||||||
pub(crate) chaining_support: bool,
|
pub(crate) chaining_support: bool,
|
||||||
pub(crate) max_cmd_bytes: u16,
|
pub(crate) max_cmd_bytes: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CardCaps {
|
||||||
|
pub fn new(
|
||||||
|
ext_support: bool,
|
||||||
|
chaining_support: bool,
|
||||||
|
max_cmd_bytes: u16,
|
||||||
|
) -> CardCaps {
|
||||||
|
Self {
|
||||||
|
ext_support,
|
||||||
|
chaining_support,
|
||||||
|
max_cmd_bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Container for a hash value.
|
/// Container for a hash value.
|
||||||
/// These hash values can be signed by the card.
|
/// These hash values can be signed by the card.
|
||||||
pub enum Hash<'a> {
|
pub enum Hash<'a> {
|
||||||
|
@ -228,12 +248,16 @@ pub struct CardBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardBase {
|
impl CardBase {
|
||||||
/// Get all cards that can be opened as an OpenPGP card applet
|
pub fn new(card_app: CardApp, ard: Tlv) -> Self {
|
||||||
pub fn list_cards() -> Result<Vec<Self>> {
|
Self { card_app, ard }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 cards = card::get_cards().map_err(|err| anyhow!(err))?;
|
||||||
let ocs: Vec<_> = cards
|
let ocs: Vec<_> = cards
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Self::open_card)
|
.map(PcscClient::open)
|
||||||
.map(|oc| oc.ok())
|
.map(|oc| oc.ok())
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -245,7 +269,7 @@ impl CardBase {
|
||||||
///
|
///
|
||||||
/// The ident is constructed as a concatenation of manufacturer
|
/// The ident is constructed as a concatenation of manufacturer
|
||||||
/// id, a colon, and the card serial. Example: "1234:5678ABCD".
|
/// id, a colon, and the card serial. Example: "1234:5678ABCD".
|
||||||
pub fn open_by_ident(ident: &str) -> Result<Self, OpenpgpCardError> {
|
pub fn open_by_ident_pcsc(ident: &str) -> Result<Self, OpenpgpCardError> {
|
||||||
let cards = card::get_cards().map_err(|e| {
|
let cards = card::get_cards().map_err(|e| {
|
||||||
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
|
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
|
@ -254,7 +278,7 @@ impl CardBase {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for card in cards {
|
for card in cards {
|
||||||
let res = Self::open_card(card);
|
let res = PcscClient::open(card);
|
||||||
if let Ok(opened_card) = res {
|
if let Ok(opened_card) = res {
|
||||||
let res = opened_card.get_aid();
|
let res = opened_card.get_aid();
|
||||||
if let Ok(aid) = res {
|
if let Ok(aid) = res {
|
||||||
|
@ -271,7 +295,7 @@ impl CardBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open connection to some card and select the openpgp applet
|
/// Open connection to some card and select the openpgp applet
|
||||||
pub fn open_yolo() -> Result<Self, OpenpgpCardError> {
|
pub fn open_yolo_pcsc() -> Result<Self, OpenpgpCardError> {
|
||||||
let mut cards = card::get_cards().map_err(|e| {
|
let mut cards = card::get_cards().map_err(|e| {
|
||||||
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
|
OpenpgpCardError::Smartcard(SmartcardError::Error(format!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
|
@ -282,20 +306,12 @@ impl CardBase {
|
||||||
// randomly use the first card in the list
|
// randomly use the first card in the list
|
||||||
let card = cards.swap_remove(0);
|
let card = cards.swap_remove(0);
|
||||||
|
|
||||||
Self::open_card(card)
|
PcscClient::open(card)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open connection to a specific card and select the openpgp applet
|
/// Set up connection (cache "application related data") to a
|
||||||
fn open_card(card: Card) -> Result<Self, OpenpgpCardError> {
|
/// CardClient, on which the openpgp applet has already been opened.
|
||||||
let select_openpgp = commands::select_openpgp();
|
pub fn open_card(ccb: CardClientBox) -> Result<Self, OpenpgpCardError> {
|
||||||
|
|
||||||
let card_client = PcscClient::new(card);
|
|
||||||
let mut ccb =
|
|
||||||
Box::new(card_client) as Box<dyn CardClient + Send + Sync>;
|
|
||||||
|
|
||||||
let resp = apdu::send_command(&mut ccb, select_openpgp, false, None)?;
|
|
||||||
|
|
||||||
if resp.is_ok() {
|
|
||||||
// read and cache "application related data"
|
// read and cache "application related data"
|
||||||
let mut card_app = CardApp::new(ccb);
|
let mut card_app = CardApp::new(ccb);
|
||||||
let ard = card_app.get_app_data()?;
|
let ard = card_app.get_app_data()?;
|
||||||
|
@ -330,9 +346,6 @@ impl CardBase {
|
||||||
let card_app = card_app.set_caps(caps);
|
let card_app = card_app.set_caps(caps);
|
||||||
|
|
||||||
Ok(Self { card_app, ard })
|
Ok(Self { card_app, ard })
|
||||||
} else {
|
|
||||||
Err(anyhow!("Couldn't open OpenPGP application").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- application data ---
|
// --- application data ---
|
||||||
|
|
19
scdc/Cargo.toml
Normal file
19
scdc/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "openpgp-card-scdc"
|
||||||
|
description = "Experimental SCDaemon Client, intended for use in the openpgp-card crate"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
openpgp-card = { path = "../openpgp-card" }
|
||||||
|
sequoia-ipc = { path = "../../sequoia/ipc" }
|
||||||
|
libc = "0.2"
|
||||||
|
hex = "0.4"
|
||||||
|
anyhow = "1"
|
||||||
|
futures = "0.3"
|
||||||
|
tokio = "0.2"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
log = "0.4"
|
106
scdc/src/lib.rs
Normal file
106
scdc/src/lib.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use sequoia_ipc::assuan::{Client, Response};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
use openpgp_card::errors::OpenpgpCardError;
|
||||||
|
use openpgp_card::{CardBase, CardClient, CardClientBox};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub(crate) static ref RT: Mutex<Runtime> =
|
||||||
|
Mutex::new(tokio::runtime::Runtime::new().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScdClient {
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScdClient {
|
||||||
|
/// Create a CardBase object that uses an scdaemon instance as its
|
||||||
|
/// backend.
|
||||||
|
pub fn open_scdc(socket: &str) -> Result<CardBase, OpenpgpCardError> {
|
||||||
|
let card_client = ScdClient::new(socket)?;
|
||||||
|
let card_client_box = Box::new(card_client) as CardClientBox;
|
||||||
|
|
||||||
|
CardBase::open_card(card_client_box)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a CardBase object that uses an scdaemon instance as its
|
||||||
|
/// backend, asking for a specific card by `serial`.
|
||||||
|
pub fn open_scdc_by_serial(
|
||||||
|
socket: &str,
|
||||||
|
serial: &str,
|
||||||
|
) -> Result<CardBase, OpenpgpCardError> {
|
||||||
|
let mut card_client = ScdClient::new(socket)?;
|
||||||
|
|
||||||
|
card_client.select_card(serial)?;
|
||||||
|
|
||||||
|
let card_client_box = Box::new(card_client) as CardClientBox;
|
||||||
|
|
||||||
|
CardBase::open_card(card_client_box)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(socket: &str) -> Result<Self> {
|
||||||
|
let client = RT.lock().unwrap().block_on(Client::connect(socket))?;
|
||||||
|
Ok(Self { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_card(&mut self, serial: &str) -> Result<()> {
|
||||||
|
let send = format!("SERIALNO --demand={}\n", serial);
|
||||||
|
self.client.send(send)?;
|
||||||
|
|
||||||
|
let mut rt = RT.lock().unwrap();
|
||||||
|
|
||||||
|
while let Some(response) = rt.block_on(self.client.next()) {
|
||||||
|
if let Err(_) = response {
|
||||||
|
return Err(anyhow!("Card not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(Response::Status { .. }) = response {
|
||||||
|
// drop remaining lines
|
||||||
|
while let Some(_drop) = rt.block_on(self.client.next()) {}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow!("Card not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CardClient for ScdClient {
|
||||||
|
fn transmit(&mut self, cmd: &[u8], _: usize) -> Result<Vec<u8>> {
|
||||||
|
let hex = hex::encode(cmd);
|
||||||
|
|
||||||
|
let send = format!("APDU {}\n", hex);
|
||||||
|
println!("send: '{}'", send);
|
||||||
|
self.client.send(send)?;
|
||||||
|
|
||||||
|
let mut rt = RT.lock().unwrap();
|
||||||
|
|
||||||
|
while let Some(response) = rt.block_on(self.client.next()) {
|
||||||
|
log::trace!("res: {:x?}", response);
|
||||||
|
if let Err(_) = response {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(Response::Data { partial }) = response {
|
||||||
|
let res = partial;
|
||||||
|
|
||||||
|
// drop remaining lines
|
||||||
|
while let Some(drop) = rt.block_on(self.client.next()) {
|
||||||
|
log::trace!("drop: {:x?}", drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow!("no response found"))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue