use card_backend_pcsc::PcscBackend;
use openpgp_card::Card;
use openpgp_card::ocard::{OpenPGP, data::UserInteractionFlag};
use secrecy::{SecretString, SecretVec};
use super::types::{CardError, CardInfo, CardSummary, KeySlot, TouchMode};
use crate::error::{Error, Result};
pub fn is_card_connected() -> bool {
match PcscBackend::cards(None) {
Ok(mut cards) => cards.next().is_some(),
Err(_) => false,
}
}
pub fn list_all_cards() -> Result<Vec<CardSummary>> {
let cards = PcscBackend::cards(None)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
let mut result = Vec::new();
for backend in cards {
let backend = match backend {
Ok(b) => b,
Err(_) => continue,
};
let mut card = match Card::new(backend) {
Ok(c) => c,
Err(_) => continue,
};
let mut tx = match card.transaction() {
Ok(t) => t,
Err(_) => continue,
};
if let Ok(aid) = tx.application_identifier() {
let ident = aid.ident();
let manufacturer_name = aid.manufacturer_name().to_string();
let serial_number = format!("{:08X}", aid.serial());
let cardholder_name = tx.cardholder_name().ok()
.filter(|n| !n.is_empty());
result.push(CardSummary {
ident,
manufacturer_name,
serial_number,
cardholder_name,
});
}
}
Ok(result)
}
pub(crate) fn get_card_backend(ident: Option<&str>) -> Result<PcscBackend> {
match ident {
None => {
let mut cards = PcscBackend::cards(None)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
cards.next()
.ok_or(Error::Card(CardError::NotConnected))?
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))
}
Some(target_ident) => {
let target = target_ident.to_ascii_uppercase();
let cards = PcscBackend::cards(None)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
for backend in cards {
let backend = match backend {
Ok(b) => b,
Err(_) => continue,
};
let mut card = match Card::new(backend) {
Ok(c) => c,
Err(_) => continue,
};
let card_ident = {
let tx = match card.transaction() {
Ok(t) => t,
Err(_) => continue,
};
match tx.application_identifier() {
Ok(aid) => aid.ident(),
Err(_) => continue,
}
};
if card_ident == target {
drop(card);
let cards2 = PcscBackend::cards(None)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
for b2 in cards2 {
let b2 = match b2 {
Ok(b) => b,
Err(_) => continue,
};
let mut c2 = match Card::new(b2) {
Ok(c) => c,
Err(_) => continue,
};
let matches = {
let tx2 = match c2.transaction() {
Ok(t) => t,
Err(_) => continue,
};
tx2.application_identifier()
.map(|aid| aid.ident() == target)
.unwrap_or(false)
};
if matches {
drop(c2);
let cards3 = PcscBackend::cards(None)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
for b3 in cards3 {
if let Ok(b3) = b3 {
return Ok(b3);
}
}
}
}
return Err(Error::Card(CardError::NotConnected));
}
}
Err(Error::Card(CardError::CommunicationError(
format!("Card with ident '{}' not found", target_ident)
)))
}
}
}
fn pin_to_secret(pin: &[u8]) -> Result<SecretString> {
let pin_str = std::str::from_utf8(pin)
.map_err(|_| Error::Card(CardError::InvalidData("PIN must be valid UTF-8".to_string())))?;
Ok(SecretString::new(pin_str.to_string()))
}
pub fn get_card_details(ident: Option<&str>) -> Result<CardInfo> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut info = CardInfo::default();
if let Ok(aid) = tx.application_identifier() {
info.serial_number = format!("{:08X}", aid.serial());
info.manufacturer = Some(format!("{:04X}", aid.manufacturer()));
info.manufacturer_name = Some(aid.manufacturer_name().to_string());
info.ident = aid.ident();
}
if let Ok(fps) = tx.fingerprints() {
if let Some(fp) = fps.signature() {
info.signature_fingerprint = Some(hex::encode(fp.as_bytes()));
}
if let Some(fp) = fps.decryption() {
info.encryption_fingerprint = Some(hex::encode(fp.as_bytes()));
}
if let Some(fp) = fps.authentication() {
info.authentication_fingerprint = Some(hex::encode(fp.as_bytes()));
}
}
if let Ok(status) = tx.pw_status_bytes() {
info.pin_retry_counter = status.err_count_pw1();
info.reset_code_retry_counter = status.err_count_rc();
info.admin_pin_retry_counter = status.err_count_pw3();
}
if let Ok(name) = tx.cardholder_name() {
if !name.is_empty() {
info.cardholder_name = Some(name);
}
}
if let Ok(url) = tx.url() {
if !url.is_empty() {
info.public_key_url = Some(url);
}
}
Ok(info)
}
pub fn get_card_version(ident: Option<&str>) -> Result<String> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let aid = tx.application_identifier()
.map_err(|e| Error::Card(CardError::from(e)))?;
let version = aid.version();
let major = version >> 8;
let minor = version & 0xFF;
Ok(format!("{}.{}", major, minor))
}
pub fn get_card_serial(ident: Option<&str>) -> Result<String> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let aid = tx.application_identifier()
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(format!("{:08X}", aid.serial()))
}
pub fn verify_user_pin(pin: &[u8], ident: Option<&str>) -> Result<bool> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let secret_pin = pin_to_secret(pin)?;
tx.verify_user_pin(secret_pin)
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(true)
}
pub fn verify_admin_pin(pin: &[u8], ident: Option<&str>) -> Result<bool> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let secret_pin = pin_to_secret(pin)?;
tx.verify_admin_pin(secret_pin)
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(true)
}
pub fn get_pin_retry_counters(ident: Option<&str>) -> Result<(u8, u8, u8)> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let status = tx.pw_status_bytes()
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok((
status.err_count_pw1(),
status.err_count_rc(),
status.err_count_pw3(),
))
}
pub fn reset_card(ident: Option<&str>) -> Result<()> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
tx.factory_reset()
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(())
}
pub fn change_user_pin(old_pin: &[u8], new_pin: &[u8], ident: Option<&str>) -> Result<()> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let old_secret = pin_to_secret(old_pin)?;
let new_secret = pin_to_secret(new_pin)?;
tx.change_user_pin(old_secret, new_secret)
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(())
}
pub fn change_admin_pin(old_pin: &[u8], new_pin: &[u8], ident: Option<&str>) -> Result<()> {
let backend = get_card_backend(ident)?;
let mut card = Card::new(backend)
.map_err(|e| Error::Card(CardError::from(e)))?;
let mut tx = card.transaction()
.map_err(|e| Error::Card(CardError::from(e)))?;
let old_secret = pin_to_secret(old_pin)?;
let new_secret = pin_to_secret(new_pin)?;
tx.change_admin_pin(old_secret, new_secret)
.map_err(|e| Error::Card(CardError::from(e)))?;
Ok(())
}
pub fn set_touch_mode(slot: KeySlot, mode: TouchMode, admin_pin: &[u8], ident: Option<&str>) -> Result<()> {
let backend = get_card_backend(ident)?;
let mut opgp = OpenPGP::new(backend)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let mut tx = opgp.transaction()
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let secret_pin = SecretVec::new(admin_pin.to_vec());
tx.verify_pw3(secret_pin)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let policy_byte: u8 = match mode {
TouchMode::Off => 0x00,
TouchMode::On => 0x01,
TouchMode::Fixed => 0x02,
TouchMode::Cached => 0x03,
TouchMode::CachedFixed => 0x04,
};
let uif_bytes = vec![policy_byte, 0x20];
let uif = UserInteractionFlag::try_from(uif_bytes)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(format!("Failed to create UIF: {}", e))))?;
match slot {
KeySlot::Signature => {
tx.set_uif_pso_cds(&uif)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
}
KeySlot::Encryption => {
tx.set_uif_pso_dec(&uif)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
}
KeySlot::Authentication => {
tx.set_uif_pso_aut(&uif)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
}
}
Ok(())
}
pub fn set_cardholder_name(name: &str, admin_pin: &[u8], ident: Option<&str>) -> Result<()> {
let backend = get_card_backend(ident)?;
let mut opgp = OpenPGP::new(backend)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let mut tx = opgp.transaction()
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let secret_pin = SecretVec::new(admin_pin.to_vec());
tx.verify_pw3(secret_pin)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
tx.set_name(name.as_bytes())
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
Ok(())
}
pub fn set_public_key_url(url: &str, admin_pin: &[u8], ident: Option<&str>) -> Result<()> {
let backend = get_card_backend(ident)?;
let mut opgp = OpenPGP::new(backend)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let mut tx = opgp.transaction()
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
let secret_pin = SecretVec::new(admin_pin.to_vec());
tx.verify_pw3(secret_pin)
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
tx.set_url(url.as_bytes())
.map_err(|e: openpgp_card::Error| Error::Card(CardError::CardError(e.to_string())))?;
Ok(())
}
#[cfg(test)]
mod tests {
}