Backend library for communication with smartcards using system native PCSC interface. Plain Iso7816 + EMV + GlobalPlatform functionality.
npm install smartcardxThis package expands upon original smartcard library by tomkp and is built around pcsclite library by Santiago Gimeno
Original smartcard package on NPM pcsclite package on NPM
> Tested with ACR39U and ACR122U readers
- CommonJS + ES6 modules support
- Event-based devices and cards management (like in the original smartcard package)
- CommandAPDU class with helper methods for simpler command construction. (no more bitwise)
- ResponseAPDU with helpers for status decoding according to Iso7816 specifications.
- Auto GetResponse in case of 0x61XX response. (can be disabled)
- Auto command adjustment in case of 0x6CXX response. (can be disabled).
- Built-in Iso7816 commands(still expanding)
- Built-in GlobalPlatform commands (still expanding)
- Built-in GlobalPlatform secure sessions
- SCP02 (i=55)
- SCP11b
- Built-in BER encoder/decoder with wildcard search (e.g. /6f/*/88)
- Card ATR decode
- API Documentation
- Add missing Iso7816 commands
- Add missing GlobalPlatform commands
- Add EMV BER tags dictionary for EMV/Iso7816/GlobalPlatform tags
- SCP03 support
- SCP11a with mutual authentication support
- BER object in-place editing. Currently a new object must be created.
Install basic dependencies by running sudo apt install build-essential libpcsclite1 libpcsclite-dev pcscd
and check pcscd service status for any error with service pcscd status
Optionally install pcsc tools package sudo apt install pcsc-tools
and check if your reader(s) and card(s) are working by executing pcsc_scan
This should show all (contact and NFC) readers recognized by your system and print info about inserted cards (if any).
Sometimes NFC readers won't work because of other drivers blocking the usb bus.
Plug in your reader and check pcscd service logs:
``text`
$ journalctl -u pcscd.service
...
xxx XX XX:XX:XX xxxxxx pcscd[5465]: 29870365 ccid_usb.c:672:OpenUSBByName() Can't claim interface 1/10: LIBUSB_ERROR_BUSY
...
LIBUSB_ERROR_BUSY tells that the interface is used by something else.
Check what driver is using the device:
`text`
$ lsusb -t
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/16p, 480M
|__ Port 1: Dev 23, If 0, Class=Chip/SmartCard, Driver=pn533, 12M
Then view the dependency tree of that driver (pn533 in this case)
`text`
$ lsmod | grep pn533
Module Size Used by
pn533_usb 20480 0
pn533 49152 1 pn533_usb
nfc 147456 1 pn533
in this case we have following dependency tree
`text`
pn533_usb
└pn533
└nfc
There are two possible ways to disable those drivers:
1. Unload modules from kernel:
sudo rmmod pn533_usb pn533 nfc sudo modprobe pn533_usb pn533 nfc
In case you need to reenable them again just run:
> No system reboot required
2. Add drivers to blacklist
Open(create) file /etc/modprobe.d/nfc-blacklist.conf and add following lines:
`text`
blacklist pn533_usb
blacklist pn533
blacklist nfc
>System reboot required
Now install libnfc: sudo apt install libnfc-bin
And finally restart pcscd: sudo service pcscd restart
Use one of the following tools to check if the reader is working:
`text
$ nfc-scan-device
nfc-scan-device uses libnfc 1.8.0
1 NFC device(s) found:
- ACS / ACR122U PICC Interface:
acr122_usb:001:023
`
`text`
$ nfc-list
nfc-list uses libnfc 1.8.0
NFC device: ACS / ACR122U PICC Interface opened
`text
$ pcsc_scan
Using reader plug'n play mechanism
Scanning present readers...
0: ACS ACR122U PICC Interface 00 00
Fri Aug 23 16:47:33 2024
Reader 0: ACS ACR122U PICC Interface 00 00
Event number: 0
Card state: Card removed,
`
Secure Channel Protocol 02 uses cryptographic functions which are no longer supported on NodeJS v17+
Example:
Error: error:0308010C:digital envelope routines::unsupported
In order to reenable those functions you need to add --openssl-legacy-provider to environment variable NODE_OPTIONS (create if missing)export NODE_OPTIONS=--openssl-legacy-provider
For example: export NODE_OPTIONS="$NODE_OPTIONS --openssl-legacy-provider"
or
Multiple options must be separated by a space
> // Under construction
This code cam be found in package repository
`ts
import {
Logger,
PcscDevicesManager,
Device,
Card,
CommandApdu,
Utils,
BER,
Iso7816Commands,
GPCommands,
GPUtils,
ResponseApdu,
} from 'smartcardx';
Logger.setLogLevel(Logger.ELogLevel.INFO); // NONE(0), FATAL(1), ERROR(2), WARN(3), INFO(4), DEBUG(5), TRACE(6)
const devices: {[key: string]: {device: Device, card: Card | null}} = {};
function printDeviceList() {
console.clear();
console.log('============================================');
const names = Object.keys(devices);
const maxNameLen = names.reduce((currMaxLen, name) => {
return Math.max(currMaxLen, name.length);
}, 0)
names.reduce((_, name) => {
console.log(${name.padEnd(maxNameLen, ' ')}: ${devices[name].card ? devices[name].card.atrHex : 'no card'})
return null;
}, null)
console.log('============================================');
};
const pcscDM = new PcscDevicesManager();
console.log('============================================================');
pcscDM.on('error', (event) => {
console.error(Device manager error: ${event.error.message});
})
pcscDM.on('device-deactivated', (event) => {
delete devices[event.device.name];
printDeviceList();
})
pcscDM.on('device-activated', (event => {
const device = event.device;
devices[event.device.name] = {device: event.device, card: null};
printDeviceList();
device.on('error', (error) => {
console.error(Device error: ${error.message});
})
device.on('card-removed', (event) => {
devices[device.name].card = null;
printDeviceList();
})
device.on('card-inserted', async (event) => {
devices[device.name].card = event.card;
printDeviceList();
const card = event.card;
// logging card communications
// event.card.on('command-issued', (event) => {
// console.log([${device.name}][CMD]<< [${event.command}])[${device.name}][RSP]>> [${Utils.hexEncode([...event.response.data])}][${Utils.hexEncode([...event.response.status])}](${event.response.meaning})
// })
// event.card.on('response-received', (event) => {
// console.log()
// })
console.log(Utils.decodeAtr(card.atr));
console.log('Selecting default applet...');
console.log();
// Uncomment this to disable autoGetResponse feature
// In that case GET_RESPONSE commands must be sent manually
// event.card.autoGetResponse = false;
event.card.issueCommand(Iso7816Commands.select())
.then((selectResponse) => {
console.log();
let berObj: BER.BerObject | undefined;
try {
berObj = BER.BerObject.parse(selectResponse.data);
} catch (error) {
// decode error, probably not BER
}
if (berObj && selectResponse.data.byteLength > 0) {
console.log('Decoded card response BER:');
berObj.print();
// // custom print function
// berObj.print((line, lvl, obj) => {
// console.log([${obj.isPrimitive() ? 'P': obj.isRoot() ? 'R' : 'C'}][${lvl}]${line});${selectResponse.toString()} (${selectResponse.meaning})
// });
} else {
console.log('Card response:');
console.log();``
}
})
.then(() => {
// get info from a GlobalPlatform card
return GPUtils.getInfo(card);
}).then((cardInfo) => {
console.log();
console.log('Card info:')
console.log(cardInfo);
})
.catch((e) => {
console.log();
console.error(e);
console.log();
})
})
}));