-
-
Save arno608rw/4fe1eb920460e59259776c4eeaaf389c to your computer and use it in GitHub Desktop.
Thai National ID Card reader in NodeJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 <[email protected]> | |
* @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
Thank you.
My question is, does every country have separate list of commands ?, if so how can I get them ?