Skip to content

Instantly share code, notes, and snippets.

@bouroo
Last active January 25, 2024 04:20
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save bouroo/ca8a41ac876f713fa8c64cb37c7b36a9 to your computer and use it in GitHub Desktop.
Save bouroo/ca8a41ac876f713fa8c64cb37c7b36a9 to your computer and use it in GitHub Desktop.
Thai National ID Card reader in NodeJS
/* eslint-disable no-console */
/*
* Thai National ID Card reader in NodeJS
*
* Linux
* apt install libpcsclite-dev libpcsclite1 pcscd build-essential
*
* Windows
* yarn global add windows-build-tools
*
* All OS
* yarn add --dev eslint eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard
* yarn add smartcard legacy-encoding hex2imagebase64
*
* @author Kawin Viriyaprasopsook <kawin.vir@zercle.tech>
* @requires smartcard legacy-encoding hex2imagebase64
* @since 11/06/2019
*
*/
const smartcard = require('smartcard')
const legacy = require('legacy-encoding')
const hex2imagebase64 = require('hex2imagebase64')
const Devices = smartcard.Devices
let devices
try {
devices = new Devices()
} catch (err) {
console.error(err)
process.exit()
}
let card
let cmdReq = [0x00, 0xc0, 0x00, 0x00]
const cmdSelectThaiCard = [0x00, 0xa4, 0x04, 0x00, 0x08, 0xa0, 0x00, 0x00, 0x00, 0x54, 0x48, 0x00, 0x01]
const cmdCID = [0x80, 0xb0, 0x00, 0x04, 0x02, 0x00, 0x0d]
const cmdTHFullname = [0x80, 0xb0, 0x00, 0x11, 0x02, 0x00, 0x64]
const cmdENFullname = [0x80, 0xb0, 0x00, 0x75, 0x02, 0x00, 0x64]
const cmdBirth = [0x80, 0xb0, 0x00, 0xd9, 0x02, 0x00, 0x08]
const cmdGender = [0x80, 0xb0, 0x00, 0xe1, 0x02, 0x00, 0x01]
const cmdIssuer = [0x80, 0xb0, 0x00, 0xf6, 0x02, 0x00, 0x64]
const cmdIssueDate = [0x80, 0xb0, 0x01, 0x67, 0x02, 0x00, 0x08]
const cmdExpireDate = [0x80, 0xb0, 0x01, 0x6f, 0x02, 0x00, 0x08]
const cmdAddress = [0x80, 0xb0, 0x15, 0x79, 0x02, 0x00, 0x64]
const cmdPhoto = [
[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]
]
devices.on('device-activated', event => {
const currentDevices = event.devices
let device = event.device
console.log(`Device '${device}' activated, devices: ${currentDevices}`)
device.on('card-inserted', event => {
card = event.card
console.log(`Card '${card.getAtr()}' inserted into '${event.device}'`)
// Check Thai National ID Card Version
let cardAttr = card.getAtr()
if (cardAttr.substr(0, 2) === '3b' && cardAttr.substr(2, 2) === '67') {
cmdReq = [0x00, 0xC0, 0x00, 0x01]
} else {
cmdReq = [0x00, 0xC0, 0x00, 0x00]
}
// Get data from card
card.issueCommand(cmdSelectThaiCard).then((selectResult) => {
console.log(selectResult.toString())
console.log('Get CID: ')
return getData(cmdCID, cmdReq)
}).then((cid) => {
console.log(cid.slice(0, -2).toString())
console.log('Get thFullname: ')
return getData(cmdTHFullname, cmdReq)
}).then((thFullname) => {
console.log(legacy.decode(thFullname.slice(0, -2), 'iso-8859-11'))
console.log('Get cmdENFullname: ')
return getData(cmdENFullname, cmdReq)
}).then((enFullname) => {
console.log(legacy.decode(enFullname.slice(0, -2), 'iso-8859-11'))
console.log('Get dateOfBirth: ')
return getData(cmdBirth, cmdReq)
}).then((dateOfBirth) => {
console.log(legacy.decode(dateOfBirth.slice(0, -2), 'iso-8859-11'))
console.log('Get gender: ')
return getData(cmdGender, cmdReq)
}).then((gender) => {
console.log(legacy.decode(gender.slice(0, -2), 'iso-8859-11'))
console.log('Get issuer: ')
return getData(cmdIssuer, cmdReq)
}).then((issuer) => {
console.log(legacy.decode(issuer.slice(0, -2), 'iso-8859-11'))
console.log('Get issueDate: ')
return getData(cmdIssueDate, cmdReq)
}).then((issueDate) => {
console.log(legacy.decode(issueDate.slice(0, -2), 'iso-8859-11'))
console.log('Get expireDate: ')
return getData(cmdExpireDate, cmdReq)
}).then((expireDate) => {
console.log(legacy.decode(expireDate.slice(0, -2), 'iso-8859-11'))
console.log('Get address: ')
return getData(cmdAddress, cmdReq)
}).then((address) => {
console.log(legacy.decode(address.slice(0, -2), 'iso-8859-11'))
console.log('Get photo: ')
return getPhoto()
}).then((photo) => {
console.log(photo)
console.log('Done')
}).catch((err) => {
console.error(err)
process.exit()
})
})
device.on('card-removed', event => {
console.log(`Card removed from '${event.name}' `)
})
})
devices.on('device-deactivated', event => {
console.log(`Device '${event.device}' deactivated, devices: '${event.devices}'`)
})
async function getPhoto () {
let hexPhoto = ''
for (let cmd of cmdPhoto) {
let result = await getData(cmd, cmdReq)
hexPhoto += result.toString('hex').slice(0, -4)
}
return 'data:image/jpg;base64, ' + hex2imagebase64(hexPhoto)
}
function getData (cmd, req = [0x00, 0xC0, 0x00, 0x00]) {
return new Promise(function (resolve, reject) {
card.issueCommand(
cmd
).then((_result) => {
return card.issueCommand(
[...req, ...cmd.slice(-1)]
)
}).then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
})
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment