From 033ed40e48eeae3717faf1b0599bf0ce8c0ce744 Mon Sep 17 00:00:00 2001 From: Puck Meerburg Date: Thu, 5 Mar 2026 20:15:29 +0000 Subject: [PATCH] Improve multi-reader support This is especially important for developers, which tend to have at least one Yubikey hanging off their machines. --- src/main.rs | 6 ++-- src/pcsc_card.rs | 78 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 497a168..fdf7f18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use url::Url; use crate::{ pace::{append_do, prepend_do}, - pcsc_card::PCSCCard, + pcsc_card::{PCSCCard, PCSCCardFinder}, pipe::GUIToCard, }; @@ -122,8 +122,9 @@ async fn run_auth( .await; ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; + let mut finder = PCSCCardFinder::new(); let mut crad = loop { - let mut crad = PCSCCard::new(); + let mut crad = finder.find_valid().await; // Select MF iso7816::select( @@ -154,7 +155,6 @@ async fn run_auth( message: String::from("Card is not eID"), }) .await; - crad.wait_for_remove(); ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; continue; } diff --git a/src/pcsc_card.rs b/src/pcsc_card.rs index b826468..8085e58 100644 --- a/src/pcsc_card.rs +++ b/src/pcsc_card.rs @@ -1,12 +1,12 @@ -use std::ffi::CString; +use std::{collections::HashMap, ffi::CString}; +use std::sync::{Arc, Mutex}; -use pcsc::{Protocols, ReaderState, State}; +use pcsc::{PNP_NOTIFICATION, Protocols, ReaderState, State}; +use tokio::task::spawn_blocking; use crate::{Card, ResultAPDU}; pub struct PCSCCard { - pub ctx: pcsc::Context, - pub reader: CString, pub card: pcsc::Card, pub buf: [u8; 0x10002], } @@ -75,24 +75,76 @@ impl PCSCCard { .unwrap(); return Self { - ctx, - reader: reader.to_owned(), card, buf: [0; 0x10002], }; } } } +} + +struct PCSCCardFinderInner { + ctx: pcsc::Context, + reader_states: Vec, + event_count: HashMap, +} + +pub struct PCSCCardFinder(Arc>); + +impl PCSCCardFinder { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(PCSCCardFinderInner { + ctx: pcsc::Context::establish(pcsc::Scope::User).unwrap(), + reader_states: vec![ + ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE), + ], + event_count: HashMap::new(), + }))) + } + + pub async fn find_valid(&mut self) -> PCSCCard { + let mut readers_buf = [0; 2048]; - pub fn wait_for_remove(self) { - let mut rs = [ReaderState::new(self.reader, State::empty())]; loop { - self.ctx.get_status_change(None, &mut rs[..]).unwrap(); - if rs[0].event_state().contains(State::PRESENT) { - rs[0].sync_current_state(); - continue; + { + let mut m = self.0.lock().unwrap(); + m.reader_states.retain(|reader| !reader.event_state().intersects(State::IGNORE | State::UNKNOWN)); + + for reader in m.ctx.list_readers(&mut readers_buf).unwrap() { + if !m.reader_states.iter().any(|f| f.name() == reader) { + m.reader_states.push(ReaderState::new(reader, State::UNAWARE)); + } + } + + for reader in &mut m.reader_states { + reader.sync_current_state(); + } + } + + let c = self.0.clone(); + spawn_blocking(move || { let mut c = c.lock().unwrap(); let c = &mut *c; c.ctx.get_status_change(None, &mut c.reader_states) }).await.unwrap().unwrap(); + + let mut m = self.0.lock().unwrap(); + let m = &mut *m; + + for reader in &mut m.reader_states { + if reader.name() == PNP_NOTIFICATION() { + continue + } + + // Anything with "Yubico" in it will never be valid. + if reader.name().to_bytes().starts_with(b"Yubico") { + continue + } + + let last_event_count = m.event_count.insert(reader.name().to_owned(), reader.event_count()); + if reader.event_state().contains(State::PRESENT) && !reader.event_state().contains(State::INUSE) && Some(reader.event_count()) != last_event_count { + // Newly present card, let's give it a try. + if let Ok(card) = m.ctx.connect(reader.name(), pcsc::ShareMode::Shared, Protocols::ANY) { + return PCSCCard { card, buf: [0; 0x10002] } + } + } } - break; } } }