use card_backend_pcsc::PcscBackend;
use openpgp_card::ocard::{data::UserInteractionFlag, OpenPGP};
use openpgp_card::Card;
use secrecy::{SecretBox, SecretString};
use super::types::{CardError, CardInfo, CardKeyMatch, CardSummary, KeySlot, SlotMatch, 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()))
})?;
if let Some(b3) = cards3.flatten().next() {
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(),
))
})?;
let mut pin_owned = pin_str.to_string();
let secret = pin_owned.clone().into();
zeroize::Zeroize::zeroize(&mut pin_owned);
Ok(secret)
}
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);
}
}
if let Ok(count) = tx.digital_signature_count() {
info.signature_counter = count;
}
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 get_touch_modes(
ident: Option<&str>,
) -> Result<(Option<TouchMode>, Option<TouchMode>, Option<TouchMode>)> {
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 convert = |policy: openpgp_card::ocard::data::TouchPolicy| -> TouchMode {
match policy {
openpgp_card::ocard::data::TouchPolicy::Off => TouchMode::Off,
openpgp_card::ocard::data::TouchPolicy::On => TouchMode::On,
openpgp_card::ocard::data::TouchPolicy::Fixed => TouchMode::Fixed,
openpgp_card::ocard::data::TouchPolicy::Cached => TouchMode::Cached,
openpgp_card::ocard::data::TouchPolicy::CachedFixed => TouchMode::CachedFixed,
openpgp_card::ocard::data::TouchPolicy::Unknown(_) => TouchMode::Off,
}
};
let sig = tx
.user_interaction_flag(openpgp_card::ocard::KeyType::Signing)
.ok()
.flatten()
.map(|uif| convert(uif.touch_policy()));
let enc = tx
.user_interaction_flag(openpgp_card::ocard::KeyType::Decryption)
.ok()
.flatten()
.map(|uif| convert(uif.touch_policy()));
let auth = tx
.user_interaction_flag(openpgp_card::ocard::KeyType::Authentication)
.ok()
.flatten()
.map(|uif| convert(uif.touch_policy()));
Ok((sig, enc, auth))
}
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 = SecretBox::new(Box::from(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 = SecretBox::new(Box::from(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 = SecretBox::new(Box::from(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(())
}
pub fn find_cards_for_key(cert_data: &[u8]) -> Result<Vec<CardKeyMatch>> {
let cert_info = crate::parse_cert_bytes(cert_data, true)?;
let mut key_fingerprints: Vec<String> = Vec::new();
key_fingerprints.push(cert_info.fingerprint.to_lowercase());
for subkey in &cert_info.subkeys {
key_fingerprints.push(subkey.fingerprint.to_lowercase());
}
let cards = PcscBackend::cards(None)
.map_err(|e| Error::Card(CardError::CommunicationError(e.to_string())))?;
let mut results = 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,
};
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();
}
let mut matching_slots = Vec::new();
if let Ok(fps) = tx.fingerprints() {
if let Some(fp) = fps.signature() {
let fp_hex = hex::encode(fp.as_bytes());
info.signature_fingerprint = Some(fp_hex.clone());
if key_fingerprints.contains(&fp_hex) {
matching_slots.push(SlotMatch {
slot: KeySlot::Signature,
fingerprint: fp_hex,
});
}
}
if let Some(fp) = fps.decryption() {
let fp_hex = hex::encode(fp.as_bytes());
info.encryption_fingerprint = Some(fp_hex.clone());
if key_fingerprints.contains(&fp_hex) {
matching_slots.push(SlotMatch {
slot: KeySlot::Encryption,
fingerprint: fp_hex,
});
}
}
if let Some(fp) = fps.authentication() {
let fp_hex = hex::encode(fp.as_bytes());
info.authentication_fingerprint = Some(fp_hex.clone());
if key_fingerprints.contains(&fp_hex) {
matching_slots.push(SlotMatch {
slot: KeySlot::Authentication,
fingerprint: fp_hex,
});
}
}
}
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);
}
}
if let Ok(count) = tx.digital_signature_count() {
info.signature_counter = count;
}
if !matching_slots.is_empty() {
results.push(CardKeyMatch {
card: info,
matching_slots,
});
}
}
Ok(results)
}
#[cfg(test)]
mod tests {
}