From 1172e81b3ad5bcdfd60d043385ba4ea241331b77 Mon Sep 17 00:00:00 2001 From: Puck Meerburg Date: Fri, 6 Mar 2026 10:13:58 +0000 Subject: [PATCH] Ensure the APDUs are shaped like we expect This is probably a tiny bit fragile, but the WOO source, as well as the fact that the counts of APDUs is hard-coded in the client, means this is plausibly safe. We leave the stage 1 (certificate validation) requests a bit more flexible, to allow for server-side changes in certificate authentication. Post-terminal authentication APDUs, however, are scrutinized more. --- src/main.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 6c38dc2..c407bde 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, env::args, thread, time::Duration}; +use std::{collections::HashMap, env::args, io::ErrorKind, thread, time::Duration}; use der::{Any, Decode, asn1::SetOfVec, oid::ObjectIdentifier}; use openssl::{bn::BigNumContext, ec::PointConversionForm, pkey::PKey}; @@ -93,6 +93,53 @@ pub trait Card { ) -> impl Future> + Send; } +fn is_stage_1_apdu_expected(apdu_buf: &[u8]) -> bool { + // MSE:Set DST, encrypted + if apdu_buf.starts_with(&[0x0c, 0x22, 0x81, 0xb6]) { + return true; + } + + // PSO:Verify self-descriptive certificate, encrypted (start of chain) + if apdu_buf.starts_with(&[0x1c, 0x2a, 0x00, 0xbe]) { + return true; + } + // Rest of the chain + if apdu_buf.starts_with(&[0x0c, 0x2a, 0x00, 0xbe]) { + return true; + } + + // Set AT for external authentication + if apdu_buf.starts_with(&[0x0c, 0x22, 0x81, 0xa4]) { + return true; + } + + // Get challenge (terminal authentication) + if apdu_buf.starts_with(&[0x0c, 0x84, 0x00, 0x00]) { + return true; + } + + false +} + +fn are_stage_2_apdus_expected(apdus: &[Vec]) -> bool { + // External Authenticate + if !apdus[0].starts_with(&[0x0c, 0x82, 0x00, 0x00]) { + return false; + } + + // MSE:Set AT, chip/restricted authentication + if !apdus[1].starts_with(&[0x0c, 0x22, 0x41, 0xa4]) { + return false; + } + + // General Authenticate, unknown P1/P2 + if !apdus[2].starts_with(&[0x0c, 0x86, 0x00, 0x83]) { + return false; + } + + true +} + async fn run_auth( host: String, session_id: String, @@ -446,6 +493,21 @@ async fn run_auth( let apdu_count = apdus.len(); for apdu in apdus { counter += 1; + if !is_stage_1_apdu_expected(&apdu) { + ctg_pipe + .send(pipe::CardToGUI::ProcessingMessage { + message: format!( + "Refusing to run APDU (unexpected command) ({}/{})", + counter, apdu_count + ), + }) + .await; + + tokio::time::sleep(Duration::from_secs(10)).await; + ctg_pipe.send(pipe::CardToGUI::Done).await; + return Ok(()); + } + ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: format!("Running server-sent APDUs... ({}/{})", counter, apdu_count), @@ -483,6 +545,20 @@ async fn run_auth( // - 0x89 Recipient key set version (INTEGER) // - 0x8a Type (INTEGER) // - 0x8b Sequence number (BCD string) + + // Ensure we receive the APDUs we expected + if apdus.len() != 3 || !are_stage_2_apdus_expected(&apdus) { + ctg_pipe + .send(pipe::CardToGUI::ProcessingMessage { + message: format!("Refusing to run stage 2 APDUs ({}/{})", counter, apdu_count), + }) + .await; + + tokio::time::sleep(Duration::from_secs(10)).await; + ctg_pipe.send(pipe::CardToGUI::Done).await; + return Ok(()); + } + let apdu_count = apdus.len() as isize + counter; for apdu in apdus { counter += 1;