use std::{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 url::Url; use crate::{ pace::{append_do, prepend_do}, pcsc_card::PCSCCard, pipe::GUIToCard, }; mod digid_api; mod gui; mod iso7816; mod pace; mod pcsc_card; mod pipe; #[derive(Clone, Debug)] pub struct ResultAPDU { pub data: Vec, pub status: u16, } #[derive(Clone, Debug)] pub struct OwnedCommandAPDU { pub class: Class, pub instruction: u8, pub parameter: [u8; 2], pub command: Vec, pub expected_length: Option, } #[derive(Copy, Clone, Debug)] pub enum CommandChaining { LastOrOnly = 0, NotLast = 1, } #[derive(Copy, Clone, Debug)] pub enum SecureMessaging { None = 0b00, Proprietary = 0b01, StandardNoHeader = 0b10, StandardHeaderAuthenticated = 0b11, } #[derive(Copy, Clone, Debug)] pub enum Class { Proprietary(u8), Standard { command_chaining: CommandChaining, secure_messaging: SecureMessaging, channel: u8, }, } impl Class { pub fn encode(&self) -> Option { match *self { Class::Proprietary(n) if n < 0x80 => Some(0x80 | n), Class::Standard { command_chaining, secure_messaging, channel, } if channel < 4 => { Some(((command_chaining as u8) << 4) | ((secure_messaging as u8) << 2) | channel) } Class::Standard { command_chaining, secure_messaging: secure_messaging @ (SecureMessaging::None | SecureMessaging::StandardNoHeader), channel, } if channel < 20 => Some( 0x40 | ((command_chaining as u8) << 4) | ((secure_messaging as u8) << 4) | channel, ), _ => None, } } } pub trait Card { fn transmit( &mut self, apdu: OwnedCommandAPDU, ) -> impl Future> + Send; fn transmit_raw( &mut self, apdu_buf: &[u8], ) -> impl Future> + Send; } async fn run_auth( session_id: String, ctg_pipe: async_channel::Sender, gtc_pipe: async_channel::Receiver, ) -> std::io::Result<()> { let ctx = if session_id == "test" { digid_api::ClientContext { host: String::from("http://localhost"), session: String::from("test"), service: String::from("UI Test"), } } else { let ctx = digid_api::wid_init(&session_id).await; ctx.start().await; ctx }; ctg_pipe .send(pipe::CardToGUI::AuthenticationTarget { target: ctx.service.clone(), }) .await; ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; let mut crad = loop { let mut crad = PCSCCard::new(); // 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; crad.wait_for_remove(); ctg_pipe.send(pipe::CardToGUI::WaitForCard).await; continue; } 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?; // 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(); 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), }; 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; let creds = pace::authenticate_pin( &mut crad, pin.as_bytes(), ef_cardaccess.get(0).unwrap().protocol, ) .await?; let apdus; { let mut enc_crad = pace::EncryptedCardWrapper::new(&mut crad, creds.clone()); // Select the PCA again iso7816::select( &mut enc_crad, 0, iso7816::SelectFile::DedicatedFileName(&[ 0xA0, 0x00, 0x00, 0x07, 0x88, 0x50, 0x43, 0x41, 0x2D, 0x65, 0x4D, 0x52, 0x54, 0x44, ]), iso7816::SelectOccurrence::First, ) .await?; ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: String::from("Reading EF.DG14..."), }) .await; iso7816::select( &mut enc_crad, 0, iso7816::files::EF_DG14, iso7816::SelectOccurrence::First, ) .await?; let dg14 = iso7816::read_binary(&mut enc_crad, 0).await?.unwrap(); ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: String::from("Reading EF.SOD..."), }) .await; iso7816::select( &mut enc_crad, 0, iso7816::files::EF_SOD, iso7816::SelectOccurrence::First, ) .await?; let ef_sod = iso7816::read_binary(&mut enc_crad, 0).await?.unwrap(); ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: String::from("Reading EF.CVCA..."), }) .await; iso7816::select( &mut enc_crad, 0, iso7816::files::EF_CVCA, iso7816::SelectOccurrence::First, ) .await?; let ef_cvca = iso7816::read_binary(&mut enc_crad, 0).await?.unwrap(); ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: String::from("Provided files for EAC..."), }) .await; if ctx.session == "test" { ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: String::from("Test done."), }) .await; tokio::time::sleep(Duration::from_secs(2)).await; ctg_pipe.send(pipe::CardToGUI::Done).await; return Ok(()); } let (apdus_, remote_ephemeral_pkey_bytes) = ctx .prepare_eac(&ef_cvca, &dg14, &ef_sod, &creds.card_ephemeral_key) .await; apdus = apdus_; let barely_parsed_dg14_one = Any::from_der(&dg14).unwrap(); let barely_parsed_dg14_contents = SetOfVec::>::from_der(barely_parsed_dg14_one.value()).unwrap(); let ca_info_oid = ObjectIdentifier::new_unwrap("0.4.0.127.0.7.2.2.3"); let mut ca_info = None; for item in barely_parsed_dg14_contents.into_vec() { let oid = item .first() .unwrap() .decode_as::() .unwrap(); if oid.as_bytes().starts_with(ca_info_oid.as_bytes()) { ca_info = Some((oid, item.get(2).map(|f| f.value().to_vec()))); break; } } let ca_info = ca_info.unwrap(); let remote_ephemeral_key_pkey = PKey::public_key_from_der(&remote_ephemeral_pkey_bytes).unwrap(); let remote_ephemeral_key = remote_ephemeral_key_pkey.ec_key().unwrap(); let remote_ephemeral_key_group = remote_ephemeral_key.group(); let remote_ephemeral_key_point = remote_ephemeral_key.public_key(); let mut bn_ctx = BigNumContext::new().unwrap(); let mut remote_ephemeral_key_bytes = remote_ephemeral_key_point .to_bytes( remote_ephemeral_key_group, PointConversionForm::UNCOMPRESSED, &mut bn_ctx, ) .unwrap(); // We send a new SET AT along with a GENERAL AUTHENTICATE for internal/restricted identification. // This has to be encrypted, for Reasons. let mut set_at_params = ca_info.0.as_bytes().to_vec(); prepend_do(&mut set_at_params, 0x80); if let Some(v) = ca_info.1 { append_do(&mut set_at_params, 0x84, &v); } let new_apdu = OwnedCommandAPDU { class: Class::Standard { command_chaining: CommandChaining::LastOrOnly, secure_messaging: SecureMessaging::None, channel: 0, }, instruction: 0x22, parameter: [0x41, 0xA4], command: set_at_params, expected_length: Some(0), }; let resp = enc_crad.transmit(new_apdu).await?; assert_eq!(resp.status, 0x9000); prepend_do(&mut remote_ephemeral_key_bytes, 0x80); prepend_do(&mut remote_ephemeral_key_bytes, 0x7c); let new_apdu = OwnedCommandAPDU { class: Class::Standard { command_chaining: CommandChaining::LastOrOnly, secure_messaging: SecureMessaging::None, channel: 0, }, instruction: 0x86, parameter: [0x00, 0x00], command: remote_ephemeral_key_bytes, expected_length: Some(0x16), }; let resp = enc_crad.transmit(new_apdu).await?; assert_eq!(resp.status, 0x9000); } ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: String::from("Running server-sent APDUs... (0/?)"), }) .await; let mut counter = -1isize; let mut last_response = ResultAPDU { data: Vec::new(), status: 0, }; let apdu_count = apdus.len(); for apdu in apdus { counter += 1; ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: format!("Running server-sent APDUs... ({}/{})", counter, apdu_count), }) .await; last_response = crad.transmit_raw(&apdu).await?; if last_response.status != 0x9000 { break; } } last_response .data .extend_from_slice(&last_response.status.to_be_bytes()); let apdus = ctx.prepare_pca(counter, &last_response.data).await; let apdu_count = apdus.len() as isize + counter; for apdu in apdus { counter += 1; ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: format!("Running server-sent APDUs... ({}/{})", counter, apdu_count), }) .await; last_response = crad.transmit_raw(&apdu).await?; if last_response.status != 0x9000 { break; } } last_response .data .extend_from_slice(&last_response.status.to_be_bytes()); let result = ctx.get_polymorphic_data(counter, &last_response.data).await; ctg_pipe .send(pipe::CardToGUI::ProcessingMessage { message: format!("Server said: {}", result), }) .await; tokio::time::sleep(Duration::from_secs(3)).await; ctg_pipe.send(pipe::CardToGUI::Done).await; Ok(()) } fn main() { let (ctg_pipe_s, ctg_pipe_r) = async_channel::unbounded(); let (gtc_pipe_s, gtc_pipe_r) = async_channel::unbounded(); let s = args().nth(1).unwrap(); let session_id = s .split('&') .next() .unwrap() .split('=') .last() .unwrap() .to_owned(); let rt = Runtime::new().unwrap(); rt.spawn(async { run_auth(session_id, ctg_pipe_s, gtc_pipe_r).await.unwrap() }); gui::run_gui(ctg_pipe_r, gtc_pipe_s); }