diff --git a/src/main.rs b/src/main.rs index fdf7f18..6c38dc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, env::args, thread, time::Duration}; use der::{Any, Decode, asn1::SetOfVec, oid::ObjectIdentifier}; use openssl::{bn::BigNumContext, ec::PointConversionForm, pkey::PKey}; -use tokio::runtime::Runtime; +use tokio::{runtime::Runtime, select}; use url::Url; use crate::{ @@ -107,7 +107,11 @@ async fn run_auth( } } else { let Some(ctx) = digid_api::wid_init(&host, &session_id).await else { - ctg_pipe.send(pipe::CardToGUI::ProcessingMessage { message: "Failed to initialize DigiD session.".to_owned() }).await; + ctg_pipe + .send(pipe::CardToGUI::ProcessingMessage { + message: "Failed to initialize DigiD session.".to_owned(), + }) + .await; return Ok(()); }; @@ -121,128 +125,144 @@ async fn run_auth( }) .await; - ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; let mut finder = PCSCCardFinder::new(); - let mut crad = loop { - let mut crad = finder.find_valid().await; + let (mut crad, creds) = 'outer: loop { + ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; + let mut crad = loop { + let mut crad = finder.find_valid().await; - // Select MF - iso7816::select( - &mut crad, - 0, - iso7816::SelectFile::File(&[]), - iso7816::SelectOccurrence::First, - ) - .await?; - iso7816::select( - &mut crad, - 0, - iso7816::files::EF_DIR, - iso7816::SelectOccurrence::First, - ) - .await?; - let ef_dir = iso7816::read_binary(&mut crad, 0).await?; - if ef_dir.is_none() - || !ef_dir.unwrap().windows(14).any(|w| { - w == [ + // Select MF + iso7816::select( + &mut crad, + 0, + iso7816::SelectFile::File(&[]), + iso7816::SelectOccurrence::First, + ) + .await?; + iso7816::select( + &mut crad, + 0, + iso7816::files::EF_DIR, + iso7816::SelectOccurrence::First, + ) + .await?; + let ef_dir = iso7816::read_binary(&mut crad, 0).await?; + if ef_dir.is_none() + || !ef_dir.unwrap().windows(14).any(|w| { + w == [ + 0xA0, 0x00, 0x00, 0x07, 0x88, 0x50, 0x43, 0x41, 0x2D, 0x65, 0x4D, 0x52, + 0x54, 0x44, + ] + }) + { + ctg_pipe + .send(pipe::CardToGUI::ProcessingMessage { + message: String::from("Card is not eID"), + }) + .await; + ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; + continue; + } + + break crad; + }; + + let creds = loop { + // Select the PCA application + iso7816::select( + &mut crad, + 0, + iso7816::SelectFile::DedicatedFileName(&[ 0xA0, 0x00, 0x00, 0x07, 0x88, 0x50, 0x43, 0x41, 0x2D, 0x65, 0x4D, 0x52, 0x54, 0x44, - ] - }) - { - ctg_pipe - .send(pipe::CardToGUI::ProcessingMessage { - message: String::from("Card is not eID"), - }) - .await; - ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; - continue; - } + ]), + iso7816::SelectOccurrence::First, + ) + .await?; - break crad; - }; + // Select _its_ MF (what?) + iso7816::select( + &mut crad, + 0, + iso7816::SelectFile::File(&[]), + iso7816::SelectOccurrence::First, + ) + .await?; - let creds = loop { - // Select the PCA application - iso7816::select( - &mut crad, - 0, - iso7816::SelectFile::DedicatedFileName(&[ - 0xA0, 0x00, 0x00, 0x07, 0x88, 0x50, 0x43, 0x41, 0x2D, 0x65, 0x4D, 0x52, 0x54, 0x44, - ]), - iso7816::SelectOccurrence::First, - ) - .await?; + iso7816::select( + &mut crad, + 0, + iso7816::files::EF_CARDACCESS, + iso7816::SelectOccurrence::First, + ) + .await?; + let ef_cardaccess_bytes = iso7816::read_binary(&mut crad, 0).await?.unwrap(); + let ef_cardaccess = pace::SecurityInfos::from_der(&ef_cardaccess_bytes).unwrap(); - // Select _its_ MF (what?) - iso7816::select( - &mut crad, - 0, - iso7816::SelectFile::File(&[]), - iso7816::SelectOccurrence::First, - ) - .await?; + let (msg, can_continue) = match pace::set_authentication_template( + &mut crad, + ef_cardaccess.get(0).unwrap().protocol, + pace::PasswordType::PIN, + ) + .await + { + Ok(()) => (None, true), + Err(pace::PACEStatus::CardError(e)) => return Err(e), + Err(pace::PACEStatus::TriesLeft(n)) => (Some(format!("{} tries left", n)), true), + Err(pace::PACEStatus::Error(unk)) => { + (Some(format!("Unknown error {:04x}", unk)), false) + } + Err(pace::PACEStatus::PasswordSuspended) => { + (Some("PIN suspended. Use app.".to_string()), false) + } + Err(pace::PACEStatus::PasswordBlocked) => { + (Some("PIN blocked. Use app.".to_string()), false) + } + }; - iso7816::select( - &mut crad, - 0, - iso7816::files::EF_CARDACCESS, - iso7816::SelectOccurrence::First, - ) - .await?; - let ef_cardaccess_bytes = iso7816::read_binary(&mut crad, 0).await?.unwrap(); - let ef_cardaccess = pace::SecurityInfos::from_der(&ef_cardaccess_bytes).unwrap(); - - let (msg, can_continue) = match pace::set_authentication_template( - &mut crad, - ef_cardaccess.get(0).unwrap().protocol, - pace::PasswordType::PIN, - ) - .await - { - Ok(()) => (None, true), - Err(pace::PACEStatus::CardError(e)) => return Err(e), - Err(pace::PACEStatus::TriesLeft(n)) => (Some(format!("{} tries left", n)), true), - Err(pace::PACEStatus::Error(unk)) => { - (Some(format!("Unknown error {:04x}", unk)), false) + if can_continue { + ctg_pipe + .send(pipe::CardToGUI::ReadyForPIN { message: msg }) + .await; + } else { + ctg_pipe + .send(pipe::CardToGUI::ProcessingMessage { + message: msg.unwrap(), + }) + .await; } - Err(pace::PACEStatus::PasswordSuspended) => { - (Some("PIN suspended. Use app.".to_string()), false) - } - Err(pace::PACEStatus::PasswordBlocked) => { - (Some("PIN blocked. Use app.".to_string()), false) + + let wait_until_not_present = crad.wait_until_not_present(); + let next_command = gtc_pipe.recv(); + + select! { + _ = wait_until_not_present => { + continue 'outer; + } + + val = next_command => { + let GUIToCard::PIN(pin) = val.unwrap(); + ctg_pipe + .send(pipe::CardToGUI::ProcessingMessage { + message: String::from("Negotiating with the card..."), + }) + .await; + match pace::authenticate_pin( + &mut crad, + pin.as_bytes(), + ef_cardaccess.get(0).unwrap().protocol, + ) + .await + { + Ok(creds) => break creds, + Err(pace::PACEStatus::CardError(n)) => return Err(n), + _ => (), + } + } } }; - if can_continue { - ctg_pipe - .send(pipe::CardToGUI::ReadyForPIN { message: msg }) - .await; - } else { - ctg_pipe - .send(pipe::CardToGUI::ProcessingMessage { - message: msg.unwrap(), - }) - .await; - } - - let GUIToCard::PIN(pin) = gtc_pipe.recv().await.unwrap(); - ctg_pipe - .send(pipe::CardToGUI::ProcessingMessage { - message: String::from("Negotiating with the card..."), - }) - .await; - match pace::authenticate_pin( - &mut crad, - pin.as_bytes(), - ef_cardaccess.get(0).unwrap().protocol, - ) - .await - { - Ok(creds) => break creds, - Err(pace::PACEStatus::CardError(n)) => return Err(n), - _ => (), - } + break (crad, creds); }; let apdus; @@ -498,10 +518,25 @@ fn main() { let (gtc_pipe_s, gtc_pipe_r) = async_channel::unbounded(); let s = args().nth(1).unwrap(); - let mut parsed_url = url::form_urlencoded::parse(s.split(':').last().unwrap().as_bytes()).into_owned().collect::>(); + let mut parsed_url = url::form_urlencoded::parse(s.split(':').last().unwrap().as_bytes()) + .into_owned() + .collect::>(); let rt = Runtime::new().unwrap(); - rt.spawn(async move { run_auth(parsed_url.remove("host").unwrap_or_else(|| String::from("test")), parsed_url.remove("app_session_id").unwrap_or_else(|| String::from("test")), ctg_pipe_s, gtc_pipe_r).await.unwrap() }); + rt.spawn(async move { + run_auth( + parsed_url + .remove("host") + .unwrap_or_else(|| String::from("test")), + parsed_url + .remove("app_session_id") + .unwrap_or_else(|| String::from("test")), + ctg_pipe_s, + gtc_pipe_r, + ) + .await + .unwrap() + }); gui::run_gui(ctg_pipe_r, gtc_pipe_s); } diff --git a/src/pcsc_card.rs b/src/pcsc_card.rs index 8085e58..52cd15f 100644 --- a/src/pcsc_card.rs +++ b/src/pcsc_card.rs @@ -1,8 +1,10 @@ -use std::{collections::HashMap, ffi::CString}; use std::sync::{Arc, Mutex}; +use std::time::Duration; +use std::{collections::HashMap, ffi::CString}; -use pcsc::{PNP_NOTIFICATION, Protocols, ReaderState, State}; +use pcsc::{PNP_NOTIFICATION, Protocols, ReaderState, State, Status}; use tokio::task::spawn_blocking; +use tokio::time::sleep; use crate::{Card, ResultAPDU}; @@ -81,6 +83,18 @@ impl PCSCCard { } } } + + pub async fn wait_until_not_present(&self) { + loop { + sleep(Duration::from_millis(500)).await; + let Ok(status) = self.card.status2_owned() else { + break; + }; + if !status.status().contains(Status::PRESENT) { + break; + } + } + } } struct PCSCCardFinderInner { @@ -95,9 +109,7 @@ 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), - ], + reader_states: vec![ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE)], event_count: HashMap::new(), }))) } @@ -108,11 +120,16 @@ impl PCSCCardFinder { loop { { let mut m = self.0.lock().unwrap(); - m.reader_states.retain(|reader| !reader.event_state().intersects(State::IGNORE | State::UNKNOWN)); + 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)); + m.reader_states + .push(ReaderState::new(reader, State::UNAWARE)); } } @@ -122,26 +139,44 @@ impl PCSCCardFinder { } 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(); + 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 + continue; } // Anything with "Yubico" in it will never be valid. if reader.name().to_bytes().starts_with(b"Yubico") { - continue + 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 { + 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] } + if let Ok(card) = + m.ctx + .connect(reader.name(), pcsc::ShareMode::Shared, Protocols::ANY) + { + return PCSCCard { + card, + buf: [0; 0x10002], + }; } } }