Merge branch 'main' of github.com:LordGrimmauld/yubi-oath-rs

This commit is contained in:
Grimmauld 2025-02-13 13:09:04 +01:00
commit bb83774861
No known key found for this signature in database
9 changed files with 123 additions and 189 deletions

3
.rustfmt.toml Normal file
View file

@ -0,0 +1,3 @@
imports_granularity = "Crate"
format_code_in_doc_comments = true
group_imports = "StdExternalCrate"

26
Cargo.lock generated
View file

@ -121,19 +121,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "libc" name = "lib_ykoath2"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "oath-rs-experiments"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"apdu-core", "apdu-core",
@ -150,6 +138,18 @@ dependencies = [
"strum_macros", "strum_macros",
] ]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "ouroboros" name = "ouroboros"
version = "0.18.5" version = "0.18.5"

View file

@ -13,9 +13,13 @@ strum = { version = "0.27.0", features = [ ] }
strum_macros = "0.27.0" strum_macros = "0.27.0"
[package] [package]
name = "oath-rs-experiments" name = "lib_ykoath2"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Grimmauld <grimmauld@grimmauld.de>"] authors = ["Grimmauld <grimmauld@grimmauld.de>"]
description = "experiments with smartcards" description = "experiments with smartcards"
license-file = "LICENSE" license-file = "LICENSE"
[[example]]
name = "example"
path = "./src/example.rs"

View file

@ -1,8 +1,7 @@
use std::fmt::Display; use std::fmt::Display;
use iso7816_tlv::simple::Tlv; use iso7816_tlv::simple::Tlv;
use sha1::{Digest, Sha1}; use sha1::Digest;
use sha2::{Sha256, Sha512};
use strum::IntoEnumIterator; // 0.17.1 use strum::IntoEnumIterator; // 0.17.1
use strum_macros::EnumIter; // 0.17.1 use strum_macros::EnumIter; // 0.17.1
pub const INS_SELECT: u8 = 0xa4; pub const INS_SELECT: u8 = 0xa4;
@ -25,19 +24,6 @@ pub enum ErrorResponse {
} }
impl ErrorResponse { impl ErrorResponse {
pub fn as_string(self) -> String {
match self {
ErrorResponse::NoSpace => "No Space left on device",
ErrorResponse::CommandAborted => "Command aborted",
ErrorResponse::InvalidInstruction => "Invalid instruction",
ErrorResponse::AuthRequired => "Authentication required",
ErrorResponse::WrongSyntax => "Wrong syntax",
ErrorResponse::GenericError => "Generic Error",
ErrorResponse::NoSuchObject => "No such Object",
}
.to_string()
}
pub fn any_match(code: u16) -> Option<ErrorResponse> { pub fn any_match(code: u16) -> Option<ErrorResponse> {
for resp in ErrorResponse::iter() { for resp in ErrorResponse::iter() {
if code == resp as u16 { if code == resp as u16 {
@ -48,6 +34,22 @@ impl ErrorResponse {
} }
} }
impl std::fmt::Display for ErrorResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoSpace => f.write_str("No Space left on device"),
Self::CommandAborted => f.write_str("Command aborted"),
Self::InvalidInstruction => f.write_str("Invalid instruction"),
Self::AuthRequired => f.write_str("Authentication required"),
Self::WrongSyntax => f.write_str("Wrong syntax"),
Self::GenericError => f.write_str("Generic Error"),
Self::NoSuchObject => f.write_str("No such Object"),
}
}
}
impl std::error::Error for ErrorResponse {}
#[derive(Debug, EnumIter, Clone, Copy)] #[derive(Debug, EnumIter, Clone, Copy)]
#[repr(u16)] #[repr(u16)]
pub enum SuccessResponse { pub enum SuccessResponse {
@ -114,18 +116,18 @@ impl HashAlgo {
// returns a function capable of hashing a byte array // returns a function capable of hashing a byte array
pub fn get_hash_fun(&self) -> impl Fn(&[u8]) -> Vec<u8> { pub fn get_hash_fun(&self) -> impl Fn(&[u8]) -> Vec<u8> {
match self { match self {
HashAlgo::Sha1 => |m: &[u8]| { Self::Sha1 => |m: &[u8]| {
let mut hasher = Sha1::new(); let mut hasher = sha1::Sha1::new();
hasher.update(m); hasher.update(m);
hasher.finalize().to_vec() hasher.finalize().to_vec()
}, },
HashAlgo::Sha256 => |m: &[u8]| { Self::Sha256 => |m: &[u8]| {
let mut hasher = Sha256::new(); let mut hasher = sha2::Sha256::new();
hasher.update(m); hasher.update(m);
hasher.finalize().to_vec() hasher.finalize().to_vec()
}, },
HashAlgo::Sha512 => |m: &[u8]| { Self::Sha512 => |m: &[u8]| {
let mut hasher = Sha512::new(); let mut hasher = sha2::Sha512::new();
hasher.update(m); hasher.update(m);
hasher.finalize().to_vec() hasher.finalize().to_vec()
}, },
@ -135,9 +137,9 @@ impl HashAlgo {
// returns digest output size in number of bytes // returns digest output size in number of bytes
pub fn digest_size(&self) -> usize { pub fn digest_size(&self) -> usize {
match self { match self {
HashAlgo::Sha1 => 20, Self::Sha1 => 20,
HashAlgo::Sha256 => 32, Self::Sha256 => 32,
HashAlgo::Sha512 => 64, Self::Sha512 => 64,
} }
} }
} }

View file

@ -1,12 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
mod lib_ykoath2;
use core::time;
use lib_ykoath2::OathSession; use lib_ykoath2::OathSession;
use pcsc;
use std::process;
use std::thread;
// use crate::args::Cli; // use crate::args::Cli;
// use clap::Parser; // use clap::Parser;
@ -29,14 +23,14 @@ fn main() {
// Show message if no YubiKey(s) // Show message if no YubiKey(s)
if yubikeys.len() == 0 { if yubikeys.len() == 0 {
println!("No yubikeys detected"); println!("No yubikeys detected");
process::exit(0); std::process::exit(0);
} }
// Print device info for all the YubiKeys we detected // Print device info for all the YubiKeys we detected
for yubikey in yubikeys { for yubikey in yubikeys {
let device_label: &str = yubikey; let device_label: &str = yubikey;
println!("Found device with label {}", device_label); println!("Found device with label {}", device_label);
let session = OathSession::new(yubikey); let session = OathSession::new(yubikey).unwrap();
println!("YubiKey version is {:?}", session.get_version()); println!("YubiKey version is {:?}", session.get_version());
for c in session.list_oath_codes().unwrap() { for c in session.list_oath_codes().unwrap() {
println!("{}", c); println!("{}", c);
@ -55,7 +49,7 @@ fn main() {
println!("No credentials on device {}", device_label); println!("No credentials on device {}", device_label);
} }
thread::sleep(time::Duration::from_secs(5)); // show refresh is working std::thread::sleep(std::time::Duration::from_secs(5)); // show refresh is working
// Enumerate the OATH codes // Enumerate the OATH codes
for oath in codes { for oath in codes {

View file

@ -4,23 +4,13 @@ mod transaction;
use transaction::*; use transaction::*;
mod oath_credential; mod oath_credential;
mod oath_credentialid; mod oath_credentialid;
use oath_credential::*;
use oath_credentialid::*;
/// Utilities for interacting with YubiKey OATH/TOTP functionality /// Utilities for interacting with YubiKey OATH/TOTP functionality
extern crate pcsc; use std::{fmt::Display, time::Duration, time::SystemTime};
use pbkdf2::pbkdf2_hmac_array;
use sha1::Sha1;
use std::{
fmt::Display,
str::{self},
time::Duration,
};
use base64::{engine::general_purpose, Engine as _}; use base64::{engine::general_purpose, Engine as _};
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use oath_credential::*;
use std::time::SystemTime; use oath_credentialid::*;
fn _get_device_id(salt: Vec<u8>) -> String { fn _get_device_id(salt: Vec<u8>) -> String {
let result = HashAlgo::Sha256.get_hash_fun()(salt.leak()); let result = HashAlgo::Sha256.get_hash_fun()(salt.leak());
@ -32,13 +22,13 @@ fn _get_device_id(salt: Vec<u8>) -> String {
return general_purpose::URL_SAFE_NO_PAD.encode(hash_16_bytes); return general_purpose::URL_SAFE_NO_PAD.encode(hash_16_bytes);
} }
fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> { fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac = Hmac::<Sha1>::new_from_slice(key).expect("Invalid key length"); let mut mac = Hmac::<sha1::Sha1>::new_from_slice(key).expect("Invalid key length");
mac.update(message); mac.update(message);
mac.finalize().into_bytes().to_vec() mac.finalize().into_bytes().to_vec()
} }
fn _derive_key(salt: &[u8], passphrase: &str) -> Vec<u8> { fn _derive_key(salt: &[u8], passphrase: &str) -> Vec<u8> {
pbkdf2_hmac_array::<Sha1, 16>(passphrase.as_bytes(), salt, 1000).to_vec() pbkdf2::pbkdf2_hmac_array::<sha1::Sha1, 16>(passphrase.as_bytes(), salt, 1000).to_vec()
} }
fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> { fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
@ -143,11 +133,10 @@ impl<'a> RefreshableOathCredential<'a> {
} }
impl<'a> OathSession<'a> { impl<'a> OathSession<'a> {
pub fn new(name: &str) -> Self { pub fn new(name: &str) -> Result<Self, Error> {
let transaction_context = TransactionContext::from_name(name); let transaction_context = TransactionContext::from_name(name)?;
let info_buffer = transaction_context let info_buffer =
.apdu_read_all(0, INS_SELECT, 0x04, 0, Some(&OATH_AID)) transaction_context.apdu_read_all(0, INS_SELECT, 0x04, 0, Some(&OATH_AID))?;
.unwrap();
let info_map = tlv_to_map(info_buffer); let info_map = tlv_to_map(info_buffer);
for (tag, data) in &info_map { for (tag, data) in &info_map {
@ -155,7 +144,7 @@ impl<'a> OathSession<'a> {
println!("{:?}: {:?}", tag, data); println!("{:?}: {:?}", tag, data);
} }
OathSession { Ok(Self {
version: clone_with_lifetime( version: clone_with_lifetime(
info_map.get(&(Tag::Version as u8)).unwrap_or(&vec![0u8; 0]), info_map.get(&(Tag::Version as u8)).unwrap_or(&vec![0u8; 0]),
) )
@ -170,7 +159,7 @@ impl<'a> OathSession<'a> {
.leak(), .leak(),
name: name.to_string(), name: name.to_string(),
transaction_context, transaction_context,
} })
} }
pub fn get_version(&self) -> &[u8] { pub fn get_version(&self) -> &[u8] {
@ -181,7 +170,7 @@ impl<'a> OathSession<'a> {
&self, &self,
old: CredentialIDData, old: CredentialIDData,
new: CredentialIDData, new: CredentialIDData,
) -> Result<CredentialIDData, FormattableErrorResponse> { ) -> Result<CredentialIDData, Error> {
// require_version(self.version, (5, 3, 1)) TODO: version checking // require_version(self.version, (5, 3, 1)) TODO: version checking
self.transaction_context.apdu( self.transaction_context.apdu(
0, 0,
@ -193,10 +182,7 @@ impl<'a> OathSession<'a> {
Ok(new) Ok(new)
} }
pub fn delete_code( pub fn delete_code(&self, cred: OathCredential) -> Result<ApduResponse, Error> {
&self,
cred: OathCredential,
) -> Result<ApduResponse, FormattableErrorResponse> {
self.transaction_context.apdu( self.transaction_context.apdu(
0, 0,
Instruction::Delete as u8, Instruction::Delete as u8,
@ -210,9 +196,9 @@ impl<'a> OathSession<'a> {
&self, &self,
cred: OathCredential, cred: OathCredential,
timestamp_sys: Option<SystemTime>, timestamp_sys: Option<SystemTime>,
) -> Result<OathCodeDisplay, FormattableErrorResponse> { ) -> Result<OathCodeDisplay, Error> {
if self.name != cred.device_id { if self.name != cred.device_id {
return Err(FormattableErrorResponse::DeviceMismatchError); return Err(Error::DeviceMismatchError);
} }
let timestamp = time_to_u64(timestamp_sys.unwrap_or_else(SystemTime::now)); let timestamp = time_to_u64(timestamp_sys.unwrap_or_else(SystemTime::now));
@ -233,22 +219,18 @@ impl<'a> OathSession<'a> {
Some(&data), Some(&data),
); );
let meta = let meta = TlvIter::from_vec(resp?).next().ok_or(Error::ParsingError(
TlvIter::from_vec(resp?)
.next()
.ok_or(FormattableErrorResponse::ParsingError(
"No credentials to unpack found in response".to_string(), "No credentials to unpack found in response".to_string(),
))?; ))?;
OathCodeDisplay::from_tlv(meta).ok_or(FormattableErrorResponse::ParsingError( OathCodeDisplay::from_tlv(meta).ok_or(Error::ParsingError(
"error parsing calculation response".to_string(), "error parsing calculation response".to_string(),
)) ))
} }
/// Read the OATH codes from the device, calculate TOTP codes that don't need touch /// Read the OATH codes from the device, calculate TOTP codes that don't
pub fn calculate_oath_codes( /// need touch
&self, pub fn calculate_oath_codes(&self) -> Result<Vec<RefreshableOathCredential>, Error> {
) -> Result<Vec<RefreshableOathCredential>, FormattableErrorResponse> {
let timestamp = SystemTime::now(); let timestamp = SystemTime::now();
// Request OATH codes from device // Request OATH codes from device
let response = self.transaction_context.apdu_read_all( let response = self.transaction_context.apdu_read_all(
@ -266,8 +248,8 @@ impl<'a> OathSession<'a> {
let id_data = CredentialIDData::from_tlv(cred_id.value(), meta.tag()); let id_data = CredentialIDData::from_tlv(cred_id.value(), meta.tag());
let code = OathCodeDisplay::from_tlv(meta); let code = OathCodeDisplay::from_tlv(meta);
/* println!("id bytes: {:?}", cred_id.value()); // println!("id bytes: {:?}", cred_id.value());
println!("id recon: {:?}", id_data.format_cred_id()); */ // println!("id recon: {:?}", id_data.format_cred_id());
let cred = OathCredential { let cred = OathCredential {
device_id: self.name.clone(), device_id: self.name.clone(),
@ -283,7 +265,7 @@ impl<'a> OathSession<'a> {
return Ok(key_buffer); return Ok(key_buffer);
} }
pub fn list_oath_codes(&self) -> Result<Vec<CredentialIDData>, FormattableErrorResponse> { pub fn list_oath_codes(&self) -> Result<Vec<CredentialIDData>, Error> {
// Request OATH codes from device // Request OATH codes from device
let response = let response =
self.transaction_context self.transaction_context

View file

@ -1,20 +1,9 @@
#[crate_type = "lib"] use std::{
use crate::lib_ykoath2::*; cmp::Ordering,
/// Utilities for interacting with YubiKey OATH/TOTP functionality hash::{Hash, Hasher},
extern crate pcsc; };
use pbkdf2::pbkdf2_hmac_array;
use regex::Regex;
use sha1::Sha1;
use std::str::{self}; use crate::CredentialIDData;
use base64::{engine::general_purpose, Engine as _};
use hmac::{Hmac, Mac};
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::time::SystemTime;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OathCredential { pub struct OathCredential {

View file

@ -1,13 +1,8 @@
#![crate_type = "lib"] use std::fmt::Display;
use crate::lib_ykoath2::*;
/// Utilities for interacting with YubiKey OATH/TOTP functionality
extern crate pcsc;
use regex::Regex; use regex::Regex;
use std::{ use crate::{to_tlv, OathType, Tag, DEFAULT_PERIOD};
fmt::Write,
str::{self},
};
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub struct CredentialIDData { pub struct CredentialIDData {
@ -52,7 +47,7 @@ impl CredentialIDData {
// Function to parse the credential ID // Function to parse the credential ID
fn parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option<String>, String, u32) { fn parse_cred_id(cred_id: &[u8], oath_type: OathType) -> (Option<String>, String, u32) {
let data = match str::from_utf8(cred_id) { let data = match std::str::from_utf8(cred_id) {
Ok(d) => d, Ok(d) => d,
Err(_) => return (None, String::new(), 0), // Handle invalid UTF-8 Err(_) => return (None, String::new(), 0), // Handle invalid UTF-8
}; };

View file

@ -1,22 +1,14 @@
#[crate_type = "lib"] use std::{collections::HashMap, ffi::CString, fmt::Display};
use crate::lib_ykoath2::*;
/// Utilities for interacting with YubiKey OATH/TOTP functionality
extern crate pcsc;
use iso7816_tlv::simple::{Tag as TlvTag, Tlv};
use ouroboros::self_referencing;
use std::collections::HashMap;
use std::fmt::Display;
use std::str::{self};
use apdu_core::{Command, Response}; use apdu_core::{Command, Response};
use iso7816_tlv::simple::{Tag as TlvTag, Tlv};
use ouroboros::self_referencing;
use pcsc::{Card, Transaction}; use pcsc::{Card, Transaction};
use std::ffi::CString; use crate::{ErrorResponse, Instruction, SuccessResponse, Tag};
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub enum FormattableErrorResponse { pub enum Error {
NoError,
Unknown(String), Unknown(String),
Protocol(ErrorResponse), Protocol(ErrorResponse),
PcscError(pcsc::Error), PcscError(pcsc::Error),
@ -24,50 +16,37 @@ pub enum FormattableErrorResponse {
DeviceMismatchError, DeviceMismatchError,
} }
impl FormattableErrorResponse { impl Error {
pub fn from_apdu_response(sw1: u8, sw2: u8) -> FormattableErrorResponse { fn from_apdu_response(sw1: u8, sw2: u8) -> Result<(), Self> {
let code: u16 = (sw1 as u16 | sw2 as u16) << 8; let code: u16 = (sw1 as u16 | sw2 as u16) << 8;
if let Some(e) = ErrorResponse::any_match(code) { if let Some(e) = ErrorResponse::any_match(code) {
return FormattableErrorResponse::Protocol(e); return Err(Self::Protocol(e));
} }
if SuccessResponse::any_match(code) if SuccessResponse::any_match(code)
.or(SuccessResponse::any_match(sw1.into())) .or(SuccessResponse::any_match(sw1.into()))
.is_some() .is_some()
{ {
return FormattableErrorResponse::NoError; return Ok(());
}
FormattableErrorResponse::Unknown(String::from("Unknown error"))
}
pub fn is_ok(&self) -> bool {
*self == FormattableErrorResponse::NoError
}
pub fn as_opt(self) -> Option<FormattableErrorResponse> {
if self.is_ok() {
None
} else {
Some(self)
}
}
fn from_transmit(err: pcsc::Error) -> FormattableErrorResponse {
FormattableErrorResponse::PcscError(err)
}
fn as_string(&self) -> String {
match self {
FormattableErrorResponse::NoError => "ok".to_string(),
FormattableErrorResponse::Unknown(msg) => msg.to_owned(),
FormattableErrorResponse::Protocol(error_response) => error_response.as_string(),
FormattableErrorResponse::PcscError(error) => format!("{}", error),
FormattableErrorResponse::ParsingError(msg) => msg.to_owned(),
FormattableErrorResponse::DeviceMismatchError => "Devices do not match".to_string(),
} }
Err(Self::Unknown(String::from("Unknown error")))
} }
} }
impl Display for FormattableErrorResponse { impl From<pcsc::Error> for Error {
fn from(value: pcsc::Error) -> Self {
Self::PcscError(value)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.as_string()) match self {
Self::Unknown(msg) => f.write_str(msg),
Self::Protocol(error_response) => f.write_fmt(format_args!("{}", error_response)),
Self::PcscError(error) => f.write_fmt(format_args!("{}", error)),
Self::ParsingError(msg) => f.write_str(msg),
Self::DeviceMismatchError => f.write_str("Devices do not match"),
}
} }
} }
@ -85,7 +64,7 @@ fn apdu(
parameter1: u8, parameter1: u8,
parameter2: u8, parameter2: u8,
data: Option<&[u8]>, data: Option<&[u8]>,
) -> Result<ApduResponse, FormattableErrorResponse> { ) -> Result<ApduResponse, Error> {
let command = if let Some(data) = data { let command = if let Some(data) = data {
Command::new_with_payload(class, instruction, parameter1, parameter2, data) Command::new_with_payload(class, instruction, parameter1, parameter2, data)
} else { } else {
@ -98,19 +77,9 @@ fn apdu(
let mut rx_buf = [0; pcsc::MAX_BUFFER_SIZE]; let mut rx_buf = [0; pcsc::MAX_BUFFER_SIZE];
// Write the payload to the device and error if there is a problem // Write the payload to the device and error if there is a problem
let rx_buf = match tx.transmit(&tx_buf, &mut rx_buf) { let rx_buf = tx.transmit(&tx_buf, &mut rx_buf)?;
Ok(slice) => slice,
// Err(err) => return Err(format!("{}", err)),
Err(err) => return Err(FormattableErrorResponse::from_transmit(err)),
};
let resp = Response::from(rx_buf); let resp = Response::from(rx_buf);
let error_context = Error::from_apdu_response(resp.trailer.0, resp.trailer.1)?;
FormattableErrorResponse::from_apdu_response(resp.trailer.0, resp.trailer.1);
if !error_context.is_ok() {
return Err(error_context);
}
Ok(ApduResponse { Ok(ApduResponse {
buf: resp.payload.to_vec(), buf: resp.payload.to_vec(),
@ -126,7 +95,7 @@ fn apdu_read_all(
parameter1: u8, parameter1: u8,
parameter2: u8, parameter2: u8,
data: Option<&[u8]>, data: Option<&[u8]>,
) -> Result<Vec<u8>, FormattableErrorResponse> { ) -> Result<Vec<u8>, Error> {
let mut response_buf = Vec::new(); let mut response_buf = Vec::new();
let mut resp = apdu(tx, class, instruction, parameter1, parameter2, data)?; let mut resp = apdu(tx, class, instruction, parameter1, parameter2, data)?;
response_buf.extend(resp.buf); response_buf.extend(resp.buf);
@ -146,26 +115,22 @@ pub struct TransactionContext {
} }
impl TransactionContext { impl TransactionContext {
pub fn from_name(name: &str) -> Self { pub fn from_name(name: &str) -> Result<Self, Error> {
// FIXME: error handling here
// Establish a PC/SC context // Establish a PC/SC context
let ctx = pcsc::Context::establish(pcsc::Scope::User).unwrap(); let ctx = pcsc::Context::establish(pcsc::Scope::User)?;
// Connect to the card // Connect to the card
let card = ctx let card = ctx.connect(
.connect(
&CString::new(name).unwrap(), &CString::new(name).unwrap(),
pcsc::ShareMode::Shared, pcsc::ShareMode::Shared,
pcsc::Protocols::ANY, pcsc::Protocols::ANY,
) )?;
.unwrap();
TransactionContextBuilder { Ok(TransactionContextBuilder {
card, card,
transaction_builder: |c| c.transaction().unwrap(), transaction_builder: |c| c.transaction().unwrap(),
} }
.build() .build())
} }
pub fn apdu( pub fn apdu(
@ -175,7 +140,7 @@ impl TransactionContext {
parameter1: u8, parameter1: u8,
parameter2: u8, parameter2: u8,
data: Option<&[u8]>, data: Option<&[u8]>,
) -> Result<ApduResponse, FormattableErrorResponse> { ) -> Result<ApduResponse, Error> {
apdu( apdu(
self.borrow_transaction(), self.borrow_transaction(),
class, class,
@ -193,7 +158,7 @@ impl TransactionContext {
parameter1: u8, parameter1: u8,
parameter2: u8, parameter2: u8,
data: Option<&[u8]>, data: Option<&[u8]>,
) -> Result<Vec<u8>, FormattableErrorResponse> { ) -> Result<Vec<u8>, Error> {
apdu_read_all( apdu_read_all(
self.borrow_transaction(), self.borrow_transaction(),
class, class,