diff --git a/Cargo.lock b/Cargo.lock index b6a99c0..ddc486b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1633,6 +1633,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -2201,6 +2221,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "thiserror", "tokio", "url", ] diff --git a/Cargo.toml b/Cargo.toml index 1f809e2..e9549b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ url = "2.5.7" gdk = { package = "gdk4", version = "0.10.3", features = ["v4_20"] } async-channel = "2.5.0" glib = { version = "0.21.5", features = ["v2_86"] } +thiserror = "2.0.17" diff --git a/package.nix b/package.nix index d00bbf6..d4ba455 100644 --- a/package.nix +++ b/package.nix @@ -15,7 +15,7 @@ rustPlatform.buildRustPackage rec { src = ./.; - cargoHash = "sha256-hrxH1Cxuf6oaVKuEDoB6W1qYdxlJ+dstU58ZO0NY+xg="; + cargoHash = "sha256-CO/9N4hDU1sh3gycE/FMZ3k1eDJR8krpBy6rCkKTfjo="; nativeBuildInputs = [ pkg-config diff --git a/src/gui.rs b/src/gui.rs index d99833c..36f9d30 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -108,6 +108,7 @@ fn build_ui( btn.set_sensitive(false); let pass = password_field.text().to_string(); gtc_pipe.send_blocking(pipe::GUIToCard::PIN(pass)).unwrap(); + password_field.set_text(""); } )); diff --git a/src/main.rs b/src/main.rs index 3165eca..6a2a4a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -157,74 +157,88 @@ async fn run_auth( break crad; }; - // 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?; + 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?; - // Select _its_ MF (what?) - iso7816::select( - &mut crad, - 0, - iso7816::SelectFile::File(&[]), - iso7816::SelectOccurrence::First, - ) - .await?; + // Select _its_ MF (what?) + iso7816::select( + &mut crad, + 0, + iso7816::SelectFile::File(&[]), + 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(); + 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 status = pace::set_authentication_template( - &mut crad, - ef_cardaccess.get(0).unwrap().protocol, - pace::PasswordType::PIN, - ) - .await?; - let (msg, can_continue) = match status { - pace::PACEStatus::Okay => (None, true), - pace::PACEStatus::TriesLeft(n) => (Some(format!("{} tries left", n)), true), - pace::PACEStatus::Error(unk) => (Some(format!("Unknown error {:04x}", unk)), false), - pace::PACEStatus::PasswordSuspended => (Some("PIN suspended. Use app.".to_string()), false), - pace::PACEStatus::PasswordBlocked => (Some("PIN blocked. Use app.".to_string()), false), - }; + 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) + } + }; - if can_continue { - ctg_pipe - .send(pipe::CardToGUI::ReadyForPIN { message: msg }) - .await; - } else { + 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: msg.unwrap(), + message: String::from("Negotiating with the card..."), }) .await; - } - - let GUIToCard::PIN(pin) = gtc_pipe.recv().await.unwrap(); - ctg_pipe - .send(pipe::CardToGUI::ProcessingMessage { - message: String::from("Negotiating with the card..."), - }) - .await; - let creds = pace::authenticate_pin( - &mut crad, - pin.as_bytes(), - ef_cardaccess.get(0).unwrap().protocol, - ) - .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), + _ => (), + } + }; let apdus; diff --git a/src/pace.rs b/src/pace.rs index a7389ac..c1fe1f7 100644 --- a/src/pace.rs +++ b/src/pace.rs @@ -317,13 +317,19 @@ pub enum PasswordType { PUK = 0x04, } -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum PACEStatus { - Okay, + #[error("transmit error: {0:04x}")] Error(u16), + #[error("{0} tries left")] TriesLeft(u8), + #[error("PIN suspended")] PasswordSuspended, + #[error("PIN blocked")] PasswordBlocked, + + #[error("Card error: {0}")] + CardError(#[from] std::io::Error), } fn make_set_authentication_template_apdu( @@ -351,7 +357,7 @@ pub async fn set_authentication_template( card: &mut impl Card, cryptographic_mechanism: ObjectIdentifier, password: PasswordType, -) -> std::io::Result { +) -> Result<(), PACEStatus> { let d = card .transmit(make_set_authentication_template_apdu( cryptographic_mechanism, @@ -359,21 +365,21 @@ pub async fn set_authentication_template( )) .await?; - Ok(match d.status { - 0x9000 => PACEStatus::Okay, - v if v & 0xFFF0 == 0x63C0 => PACEStatus::TriesLeft((v as u8) & 0xF), - 0x63C1 => PACEStatus::PasswordSuspended, - 0x63C0 => PACEStatus::PasswordBlocked, + match d.status { + 0x9000 => Ok(()), + v if v & 0xFFF0 == 0x63C0 => Err(PACEStatus::TriesLeft((v as u8) & 0xF)), + 0x63C1 => Err(PACEStatus::PasswordSuspended), + 0x63C0 => Err(PACEStatus::PasswordBlocked), - v => PACEStatus::Error(v), - }) + v => Err(PACEStatus::Error(v)), + } } pub async fn step_general_authenticate( card: &mut impl Card, chained: bool, make_data: impl FnOnce(&mut Vec), -) -> std::io::Result>> { +) -> Result>, PACEStatus> { let mut buf = Vec::new(); make_data(&mut buf); prepend_do(&mut buf, 0x7c); @@ -398,7 +404,16 @@ pub async fn step_general_authenticate( }) .await?; - if !res.data.starts_with(&[0x7c]) || res.status != 0x9000 { + match res.status { + 0x9000 => (), + v if v & 0xFFF0 == 0x63C0 => return Err(PACEStatus::TriesLeft((v as u8) & 0xF)), + 0x63C1 => return Err(PACEStatus::PasswordSuspended), + 0x63C0 => return Err(PACEStatus::PasswordBlocked), + + v => return Err(PACEStatus::Error(v)), + } + + if !res.data.starts_with(&[0x7c]) { return Ok(HashMap::new()); } @@ -425,7 +440,7 @@ pub async fn authenticate_pin( card: &mut impl Card, pin: &[u8], cryptographic_mechanism: ObjectIdentifier, -) -> std::io::Result { +) -> Result { // Step one: Get the encrypted nonce let mut data = step_general_authenticate(card, true, |_| {}).await?; diff --git a/src/pcsc_card.rs b/src/pcsc_card.rs index 4717422..b826468 100644 --- a/src/pcsc_card.rs +++ b/src/pcsc_card.rs @@ -42,16 +42,7 @@ impl Card for PCSCCard { apdu_buf.push(apdu.expected_length.unwrap_or_default() as u8); } - let ret_len = self - .card - .transmit(&apdu_buf, &mut self.buf) - .map_err(|f| std::io::Error::new(std::io::ErrorKind::BrokenPipe, f))? - .len(); - - let data = self.buf[..ret_len - 2].to_vec(); - let sw = (self.buf[ret_len - 2] as u16) << 8 | (self.buf[ret_len - 1] as u16); - - Ok(ResultAPDU { data, status: sw }) + self.transmit_raw(&apdu_buf).await } async fn transmit_raw(&mut self, apdu_buf: &[u8]) -> std::io::Result { let ret_len = self