Skip to content

Instantly share code, notes, and snippets.

@bouroo
Last active October 18, 2023 17:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bouroo/493448154cadaea87de792e9baf9e8e9 to your computer and use it in GitHub Desktop.
Save bouroo/493448154cadaea87de792e9baf9e8e9 to your computer and use it in GitHub Desktop.
Thai ID card reader in Rust
extern crate pcsc;
use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::str;
use std::string::String;
use pcsc::{Card, Context, ShareMode, Scope};
use pcsc::Error;
use pcsc::CardState;
fn main() -> Result<(), Error> {
// Create context with pcscd
let context = Context::establish(Scope::User)?;
// Get all readers
let readers = context.list_readers()?;
if readers.is_empty() {
println!("Error: Card readers not found.");
return Ok(());
}
println!("Readers:");
for (r_idx, r_item) in readers.iter().enumerate() {
println!("Card reader ID: {}, Item: {}", r_idx, r_item);
}
// Select reader
let reader_idx = select_reader(&readers)?;
let reader = &readers[reader_idx];
// Connect to the card
let card = context.connect(reader, ShareMode::Shared, Default::default())?;
println!("Card status:");
let status = card.status()?;
println!(
"\treader: {}\n\tstate: {:x}\n\tactive protocol: {:x}\n\tatr: {:x?}",
status.reader, status.state, status.protocol, status.atr
);
let atr = card.get_attrib(pcsc::Attribute::AtrString)?;
// Get card attribute
println!("Card ATR: {}", str::from_utf8(&atr)?);
let cmd_req = get_cmd_req(&atr);
println!("Req: {:?}", cmd_req);
// Select the Thai national ID card
card.transmit(&[0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x00, 0x54, 0x48, 0x00, 0x01])?;
let cid = get_string(&card, &cmd_req, &cmdCID)?;
println!("cid: {}", cid);
let th_fullname = get_string(&card, &cmd_req, &cmdTHFullname)?;
println!("thFullname: {}", th_fullname);
let en_fullname = get_string(&card, &cmd_req, &cmdENFullname)?;
println!("enFullname: {}", en_fullname);
let date_of_birth = get_string(&card, &cmd_req, &cmdBirth)?;
println!("dateOfBirth: {}", date_of_birth);
let gender = get_string(&card, &cmd_req, &cmdGender)?;
println!("gender: {}", gender);
let issuer = get_string(&card, &cmd_req, &cmdIssuer)?;
println!("issuer: {}", issuer);
let issue_date = get_string(&card, &cmd_req, &cmdIssueDate)?;
println!("issueDate: {}", issue_date);
let expire_date = get_string(&card, &cmd_req, &cmdExpireDate)?;
println!("expireDate: {}", expire_date);
let address = get_string(&card, &cmd_req, &cmdAddress)?;
println!("address: {}", address);
let photo = get_photo(&card, &cmd_req)?;
save_photo(&cid, &photo)?;
Ok(())
}
fn select_reader(readers: &[String]) -> Result<usize, Error> {
let reader_idx: usize;
if readers.len() > 1 {
loop {
let reader_idx_input = user_input("Select card reader ID [0]: ")?;
match reader_idx_input.parse::<usize>() {
Ok(idx) if idx < readers.len() => {
reader_idx = idx;
break;
}
_ => {
println!("Error selecting reader: invalid input");
}
}
}
} else {
reader_idx = 0;
}
Ok(reader_idx)
}
fn user_input(prompt: &str) -> Result<String, Error> {
print!("{}", prompt);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
fn get_cmd_req(atr: &[u8]) -> Vec<u8> {
if atr.len() > 1 && atr[0] == 0x3B && atr[1] == 0x67 {
vec![0x00, 0xc0, 0x00, 0x01]
} else {
vec![0x00, 0xc0, 0x00, 0x00]
}
}
fn get_string(card: &Card, req: &[u8], cmd: &[u8]) -> Result<String, Error> {
let raw_resp = get_data(card, cmd, req)?;
let th_resp = thai_to_unicode(&raw_resp)?;
Ok(th_resp.trim().to_string())
}
fn thai_to_unicode(data: &[u8]) -> Result<String, Error> {
let decoder = charmap::decode(charmap::WINDOWS_874);
let result = decoder.decode(data);
Ok(result)
}
fn get_data(card: &Card, cmd: &[u8], req: &[u8]) -> Result<Vec<u8>, Error> {
// Send cmd
card.transmit(cmd)?;
// Send select cmd
let mut req = req.to_vec();
req.push(cmd[cmd.len() - 1]);
let resp = card.transmit(&req)?;
Ok(resp[..resp.len() - 2].to_vec())
}
fn get_photo(card: &Card, req: &[u8]) -> Result<Vec<u8>, Error> {
let mut resp = Vec::new();
for item_cmd in cmdPhoto.iter() {
let tmp_array = get_data(card, item_cmd, req)?;
resp.extend_from_slice(&tmp_array);
}
Ok(resp)
}
fn save_photo(filename: &str, data: &[u8]) -> Result<(), io::Error> {
let mut file = File::create(format!("{}.jpg", filename))?;
file.write_all(data)?;
Ok(())
}
// Define other command constants here
const cmdCID: [u8; 7] = [0x80, 0xb0, 0x00, 0x04, 0x02, 0x00, 0x0d];
const cmdTHFullname: [u8; 7] = [0x80, 0xb0, 0x00, 0x11, 0x02, 0x00, 0x64];
const cmdENFullname: [u8; 7] = [0x80, 0xb0, 0x00, 0x75, 0x02, 0x00, 0x64];
const cmdBirth: [u8; 7] = [0x80, 0xb0, 0x00, 0xD9, 0x02, 0x00, 0x08];
const cmdGender: [u8; 7] = [0x80, 0xb0, 0x00, 0xE1, 0x02, 0x00, 0x01];
const cmdIssuer: [u8; 7] = [0x80, 0xb0, 0x00, 0xF6, 0x02, 0x00, 0x64];
const cmdIssueDate: [u8; 7] = [0x80, 0xb0, 0x01, 0x67, 0x02, 0x00, 0x08];
const cmdExpireDate: [u8; 7] = [0x80, 0xb0, 0x01, 0x6F, 0x02, 0x00, 0x08];
const cmdAddress: [u8; 7] = [0x80, 0xb0, 0x15, 0x79, 0x02, 0x00, 0x64];
const cmdPhoto: [[u8; 7]; 20] = [
[0x80, 0xb0, 0x01, 0x7B, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x02, 0x7A, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x03, 0x79, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x04, 0x78, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x05, 0x77, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x06, 0x76, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x07, 0x75, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x08, 0x74, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x09, 0x73, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x0A, 0x72, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x0B, 0x71, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x0C, 0x70, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x0D, 0x6F, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x0E, 0x6E, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x0F, 0x6D, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x10, 0x6C, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x11, 0x6B, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x12, 0x6A, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x13, 0x69, 0x02, 0x00, 0xFF],
[0x80, 0xb0, 0x14, 0x68, 0x02, 0x00, 0xFF],
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment