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.
This commit is contained in:
puck 2026-03-06 10:13:58 +00:00
parent 759b9ac93d
commit 1172e81b3a

View file

@ -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 der::{Any, Decode, asn1::SetOfVec, oid::ObjectIdentifier};
use openssl::{bn::BigNumContext, ec::PointConversionForm, pkey::PKey}; use openssl::{bn::BigNumContext, ec::PointConversionForm, pkey::PKey};
@ -93,6 +93,53 @@ pub trait Card {
) -> impl Future<Output = std::io::Result<ResultAPDU>> + Send; ) -> impl Future<Output = std::io::Result<ResultAPDU>> + 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<u8>]) -> 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( async fn run_auth(
host: String, host: String,
session_id: String, session_id: String,
@ -446,6 +493,21 @@ async fn run_auth(
let apdu_count = apdus.len(); let apdu_count = apdus.len();
for apdu in apdus { for apdu in apdus {
counter += 1; 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 ctg_pipe
.send(pipe::CardToGUI::ProcessingMessage { .send(pipe::CardToGUI::ProcessingMessage {
message: format!("Running server-sent APDUs... ({}/{})", counter, apdu_count), message: format!("Running server-sent APDUs... ({}/{})", counter, apdu_count),
@ -483,6 +545,20 @@ async fn run_auth(
// - 0x89 Recipient key set version (INTEGER) // - 0x89 Recipient key set version (INTEGER)
// - 0x8a Type (INTEGER) // - 0x8a Type (INTEGER)
// - 0x8b Sequence number (BCD string) // - 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; let apdu_count = apdus.len() as isize + counter;
for apdu in apdus { for apdu in apdus {
counter += 1; counter += 1;