sioul_sync/securitykey/
smartcard.rs1use super::ocard::OpenCard;
24use card_backend_pcsc::PcscBackend;
25use openpgp_card::state::Open;
26use sioul_core::securitykey::{Card, CardError, Readers, Visit, Visited};
27use std::ffi::CString;
28use std::time::Duration;
29
30pub struct PcscReaders;
32
33fn establish() -> Result<pcsc::Context, CardError> {
35 pcsc::Context::establish(pcsc::Scope::User).map_err(|e| match e {
36 pcsc::Error::NoService | pcsc::Error::ServiceStopped | pcsc::Error::NoReadersAvailable => CardError::NoService,
37 other => CardError::Other(other.to_string()),
38 })
39}
40
41fn readers_of(context: &pcsc::Context) -> Result<Vec<CString>, CardError> {
43 match context.list_readers_owned() {
44 Ok(readers) => Ok(readers),
45 Err(pcsc::Error::NoReadersAvailable) => Ok(Vec::new()),
46 Err(pcsc::Error::NoService | pcsc::Error::ServiceStopped) => Err(CardError::NoService),
47 Err(other) => Err(CardError::Other(other.to_string())),
48 }
49}
50
51impl Readers for PcscReaders {
52 fn present(&self) -> Result<Vec<String>, CardError> {
53 let context = establish()?;
54 let mut states: Vec<pcsc::ReaderState> = readers_of(&context)?.into_iter().map(|name| pcsc::ReaderState::new(name, pcsc::State::UNAWARE)).collect();
55 if states.is_empty() {
56 return Ok(Vec::new());
57 }
58 match context.get_status_change(Duration::ZERO, &mut states) {
60 Ok(()) | Err(pcsc::Error::Timeout) => {}
61 Err(pcsc::Error::NoService | pcsc::Error::ServiceStopped) => return Err(CardError::NoService),
62 Err(other) => return Err(CardError::Other(other.to_string())),
63 }
64 Ok(states.iter().filter(|s| s.event_state().contains(pcsc::State::PRESENT)).map(|s| s.name().to_string_lossy().into_owned()).collect())
65 }
66
67 fn visit(&self, visit: &mut dyn FnMut(&mut dyn Card) -> Visit) -> Result<Visited, CardError> {
68 let context = establish()?;
69 let mut visited = Visited::default();
70 for reader in readers_of(&context)? {
71 let connected = match context.connect(&reader, pcsc::ShareMode::Shared, pcsc::Protocols::ANY) {
72 Ok(card) => card,
73 Err(pcsc::Error::SharingViolation) => {
75 visited.busy += 1;
76 continue;
77 }
78 Err(_) => continue,
80 };
81 let Ok(backend) = PcscBackend::from_pcsc_card(&reader, pcsc::ShareMode::Shared, connected) else { continue };
82 let Ok(mut opened) = openpgp_card::Card::<Open>::new(backend) else { continue };
84 let Ok(transaction) = opened.transaction() else { continue };
85 let mut card = OpenCard::new(transaction, reader.to_string_lossy().into_owned());
86 visited.opened += 1;
87 let next = visit(&mut card);
88 drop(card);
90 drop(opened);
91 if next == Visit::Done {
92 break;
93 }
94 }
95 Ok(visited)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
108 #[ignore = "reads a real security key: the owner runs it himself, his key plugged in"]
109 fn a_real_key_reads_without_its_pin() {
110 let tr = sioul_core::i18n::Translator::new("en");
111 match sioul_core::securitykey::cards(&PcscReaders) {
112 Ok(cards) => {
113 for card in cards {
114 println!("{} ({}), holder {:?}, reader {:?}", card.label(&tr), card.ident, card.holder, card.reader);
115 for (what, slot) in [("signs", &card.sign), ("decrypts", &card.decrypt)] {
116 if let Some(slot) = slot {
117 println!(" {what}: {} {}, made {}, touch {:?}, public part read: {}", slot.algorithm, slot.fingerprint, slot.created, slot.touch, slot.public.is_some());
118 }
119 }
120 println!(" PIN tries {}, reset code {}, Admin PIN {}; PIN for each signature: {}; signatures made {}; URL {:?}", card.pin_tries, card.reset_tries, card.admin_tries, card.pin_each_signature, card.signatures, card.url);
121 }
122 }
123 Err(e) => println!("{}", e.sentence(&tr, sioul_core::securitykey::Purpose::Read)),
124 }
125 }
126}