372 lines
9.8 KiB
Plaintext
372 lines
9.8 KiB
Plaintext
|
# smartcard
|
||
|
|
||
|
|
||
|
Smartcard library.
|
||
|
|
||
|
This is a simple wrapper around [Santiago Gimeno's](https://www.npmjs.org/~sgimeno) great [pcsclite](https://github.com/santigimeno/node-pcsclite) library.
|
||
|
|
||
|
Used by [Card Spy](http://card-spy.surge.sh)
|
||
|
|
||
|
## API
|
||
|
|
||
|
The following objects are defined by the `smartcard` library, each contains its own set of methods and events.
|
||
|
|
||
|
### Class: Devices
|
||
|
A general object that provides access to all smartcard related devices.
|
||
|
|
||
|
#### Events
|
||
|
The `devices` object emits the following events
|
||
|
|
||
|
##### Event: 'device-activated'
|
||
|
Emitted when a card reader is attached.
|
||
|
Returns:
|
||
|
* `Object`
|
||
|
* `Device`
|
||
|
* `Array`: List of all devices, returned via `devices.listDevices()`
|
||
|
|
||
|
##### Event: 'device-deactivated'
|
||
|
Emitted when a card reader is detached.
|
||
|
Returns:
|
||
|
* `Object`
|
||
|
* `Device`
|
||
|
* `Array`: List of all devices, returned via `devices.listDevices()`
|
||
|
|
||
|
##### Event: 'error'
|
||
|
Emitted when an error occurs
|
||
|
Returns `Object`:
|
||
|
* _error_ `Error`
|
||
|
|
||
|
#### Methods
|
||
|
The following methods are available within the `devices` class.
|
||
|
|
||
|
##### Constructor
|
||
|
The constructor for a devices object takes no arguments,
|
||
|
```javascript
|
||
|
devices = new Devices();
|
||
|
```
|
||
|
##### `devices.onActivated()`
|
||
|
Returns `Promise`
|
||
|
* Resolves with activation _event_
|
||
|
|
||
|
##### `devices.onDeactivated()`
|
||
|
Returns `Promise`
|
||
|
* Resolves with deactivation _event_
|
||
|
|
||
|
##### `devices.listDevices()`
|
||
|
Returns `Object` a list of the different devices attached, each a `device` object
|
||
|
|
||
|
##### `devices.lookup(name)`
|
||
|
* _name_ `String`: The text name of a device
|
||
|
|
||
|
* Returns `Device`
|
||
|
|
||
|
|
||
|
|
||
|
### Class: Device
|
||
|
An object representing a specific card reader (device).
|
||
|
|
||
|
#### Methods
|
||
|
The following methods are available within the `device` class.
|
||
|
|
||
|
##### `device.getName()`
|
||
|
Returns the name of the attached device.
|
||
|
|
||
|
##### `device.transmit(data, res_len, protocol, cb)`
|
||
|
Sends a command to the connected device
|
||
|
* _data_ `Buffer`: data to be transmitted
|
||
|
* _res_len_ `Number`: Maximum length of the expected response, includes the 2 byte response (sw1 and sw2)
|
||
|
* _protocol_ `Number`: Protocol to be used in the transmission
|
||
|
* _cb(error,response)_ `Function`: Called when transmit function completes
|
||
|
* _error_ `Error`
|
||
|
* _output_ `Buffer`
|
||
|
|
||
|
#### Events
|
||
|
The `device` object emits the following events
|
||
|
|
||
|
##### Event: 'card-inserted'
|
||
|
Emitted when a smartcard is inserted into a card reader
|
||
|
|
||
|
Returns `Object`:
|
||
|
* _device_ `Device`
|
||
|
* _card_ `Card`
|
||
|
|
||
|
##### Event: 'card-removed'
|
||
|
Emitted when a smartcard is removed from a card reader
|
||
|
|
||
|
Returns `Object`:
|
||
|
* _name_ `String`
|
||
|
* _card_ `Card`
|
||
|
|
||
|
### Class: Card
|
||
|
An object representing an attached smart card.
|
||
|
|
||
|
#### Methods
|
||
|
The following methods are available within the `card` class.
|
||
|
|
||
|
##### `card.getAtr()`
|
||
|
Returns `String` containing the atr of the card
|
||
|
|
||
|
##### `card.issueCommand(commandApdu, callback)`
|
||
|
Sends a command to the card
|
||
|
* _commandApdu_: The command to be sent to the card
|
||
|
* `String`
|
||
|
* `Buffer`
|
||
|
* `Array`
|
||
|
* `CommandApdu`
|
||
|
* _callback(error,response)_: (optional) Function to call upon completion of the command
|
||
|
* _error_ `Error`
|
||
|
* _response_ `Buffer`
|
||
|
|
||
|
Returns `Promise`
|
||
|
* Resolves with _response_ `Buffer`
|
||
|
* Rejects with _error_ `Error`
|
||
|
|
||
|
If no callback is specified, returns a `Promise`
|
||
|
*
|
||
|
#### Events
|
||
|
The `card` object emits the following events
|
||
|
|
||
|
##### Event: 'command-issued'
|
||
|
Emitted when a command is sent to the smartcard.
|
||
|
|
||
|
Returns `Object`:
|
||
|
* _card_ `Card`
|
||
|
* _command_ `Buffer`
|
||
|
|
||
|
##### Event: 'response-received'
|
||
|
Emitted when a response is received from the card.
|
||
|
|
||
|
Returns `Object`:
|
||
|
* _card_ `Card`
|
||
|
* _command_ `Buffer`
|
||
|
* _response_ `ResponseApdu`
|
||
|
|
||
|
### Class: CommandApdu
|
||
|
An object representing a command to send to a smart card
|
||
|
|
||
|
#### Methods
|
||
|
The `CommandApdu` class has the following methods.
|
||
|
|
||
|
##### Constructor `CommandApdu(obj)`
|
||
|
Creates a new instance and sets the appropriate items
|
||
|
* _obj_ `Object`
|
||
|
* _cla_ `Number`: The class of the command, typically 0
|
||
|
* _ins_ `Number`: The instruction
|
||
|
* _p1_ `Number`: The value of p1
|
||
|
* _p2_ `Number`: The value of p2
|
||
|
* _data_ `Array` (optional): The value of data
|
||
|
* _le_ `Number` (optional): The value of le
|
||
|
|
||
|
OR
|
||
|
* _obj_ `Array`: Byte array representing the whole command
|
||
|
|
||
|
##### `CommandApdu.toBuffer()`
|
||
|
Converts the command to a Buffer
|
||
|
* Returns `Buffer`
|
||
|
|
||
|
##### `CommandApdu.toString()`
|
||
|
Converts the command to a hex string
|
||
|
* Returns `String`
|
||
|
|
||
|
##### `CommandApdu.toByteArray()`
|
||
|
Converts the command to a byte array
|
||
|
* Returns `Array`
|
||
|
|
||
|
##### `CommandApdu.setLe(le)`
|
||
|
Updates the le value of the command
|
||
|
* _le_ `Number`: The new le value
|
||
|
|
||
|
### Class: ResponseApdu
|
||
|
Class representing a response from the card
|
||
|
|
||
|
#### Methods
|
||
|
The `ResponseApdu` class has the following methods.
|
||
|
|
||
|
##### Constructor
|
||
|
|
||
|
##### `ResponseApdu.meaning()`
|
||
|
Interprets the return code and attempts to provide a text translation.
|
||
|
* Returns `String`
|
||
|
|
||
|
##### `ResponseApdu.getDataOnly()`
|
||
|
Returns the response data without including the status code
|
||
|
* Returns `String`
|
||
|
|
||
|
##### `ResponseApdu.getStatusCode()`
|
||
|
Returns only the status code
|
||
|
* Returns `String`
|
||
|
|
||
|
##### `ResponseApdu.isOk()`
|
||
|
Check if the status code is 9000
|
||
|
* Returns `Boolean`
|
||
|
|
||
|
##### `ResponseApdu.buffer()`
|
||
|
Returns the whole buffer, status code and data
|
||
|
* Returns `Buffer`
|
||
|
|
||
|
##### `ResponseApdu.hasMoreBytesAvailable()`
|
||
|
Reads the status code and looks for a 61 as sw1, meaning more data is available
|
||
|
* Returns `Boolean`
|
||
|
|
||
|
##### `ResponseApdu.numberOfBytesAvailable()`
|
||
|
Reads sw2 staus code to return number of bytes left, when sw1 is 61. A value of 0 means there are more than 256 bytes remaining.
|
||
|
* Returns `Number`
|
||
|
|
||
|
##### `ResponseApdu.isWrongLength()`
|
||
|
Checks status code for 6c as sw1
|
||
|
* Returns `Boolean`
|
||
|
|
||
|
##### `ResponseApdu.correctLength()`
|
||
|
If sw1 is 6c, returns the correct length from sw2. A value of 0 means there are more than 256 bytes remaining.
|
||
|
* Returns `Number`
|
||
|
|
||
|
### Class: Iso7816Application
|
||
|
An object offering general commands to most ISO7816 compliant smart cards.
|
||
|
|
||
|
#### Methods
|
||
|
|
||
|
##### Constructor `Iso7816Application(card)`
|
||
|
Sets up the `Iso7816Application` object
|
||
|
* _card_ `Card`: The card to communicate with using ISO7816 standards
|
||
|
|
||
|
##### `Iso7816Application.issueCommand(commandApdu)`
|
||
|
Sends the provided command to the card. Automatically retrieve the full response, even if it requires multiple GET_RESPONSE commands
|
||
|
* _commandApdu_ `CommandApdu`: Command to send to the card
|
||
|
|
||
|
Returns
|
||
|
* `ResponseApdu` Complete response from card
|
||
|
|
||
|
##### `Iso7816Application.selectFile(bytes, p1, p2)`
|
||
|
Sends the SELECT command to the card, often called selecting an application
|
||
|
* _bytes_ `Buffer`: The resource locater (AID, etc)
|
||
|
* _p1_ `Number`: Value to specify as the p1 value
|
||
|
* _p2_ `Number`: Value to specify as the p2 value
|
||
|
|
||
|
Returns
|
||
|
* `ResponseApdu` Complete response from card
|
||
|
|
||
|
##### `Iso7816Application.getResponse(length)`
|
||
|
Sends a single GET_RESPONSE command to the card
|
||
|
* _length_ `Number`: The length of the response expected, maximum is 0xFF
|
||
|
|
||
|
Returns
|
||
|
* `ResponseApdu` Complete response from card
|
||
|
|
||
|
##### `Iso7816Application.getResponse(sfi,record)`
|
||
|
Sends a READ_RECORD command to the card
|
||
|
* _sfi_ `Number`: The sfi
|
||
|
* _record_ `Number`: The record
|
||
|
|
||
|
Returns
|
||
|
* `ResponseApdu` Complete response from card
|
||
|
|
||
|
##### `Iso7816Application.getData(p1, p2)`
|
||
|
Sends a GET_DATA command to the card
|
||
|
* _p1_ `Number`: Value to specify as the p1 value
|
||
|
* _p2_ `Number`: Value to specify as the p2 value
|
||
|
|
||
|
Returns
|
||
|
* `ResponseApdu` Complete response from card
|
||
|
|
||
|
#### Events
|
||
|
The `Iso7816Application` class emits the following events
|
||
|
|
||
|
##### Event: 'application-selected'
|
||
|
Emitted when a successful reply to a `selectFile()` command is received.
|
||
|
|
||
|
Returns `Object`:
|
||
|
* _application_ `String`
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
|
||
|
### With event emitter
|
||
|
|
||
|
```javascript
|
||
|
'use strict';
|
||
|
|
||
|
const smartcard = require('smartcard');
|
||
|
const Devices = smartcard.Devices;
|
||
|
const devices = new Devices();
|
||
|
|
||
|
|
||
|
devices.on('device-activated', (event => {
|
||
|
console.log(`Device '${event.device}' activated`);
|
||
|
event.devices.map((device, index) => {
|
||
|
console.log(`Device #${index + 1}: '${device.name}'`);
|
||
|
});
|
||
|
}));
|
||
|
```
|
||
|
|
||
|
|
||
|
### Using promises
|
||
|
|
||
|
```javascript
|
||
|
'use strict';
|
||
|
|
||
|
const smartcard = require('smartcard');
|
||
|
const Devices = smartcard.Devices;
|
||
|
const devices = new Devices();
|
||
|
|
||
|
|
||
|
devices.onActivated().then(event => {
|
||
|
console.log(`Device '${event.device}' activated`);
|
||
|
event.devices.map((device, index) => {
|
||
|
console.log(`Device #${index + 1}: '${device.name}'`);
|
||
|
});
|
||
|
});
|
||
|
```
|
||
|
|
||
|
|
||
|
### Selecting the Payment Systems Environment on an EMV (Chip & Pin) card
|
||
|
|
||
|
|
||
|
```javascript
|
||
|
'use strict';
|
||
|
|
||
|
const smartcard = require('smartcard');
|
||
|
const Devices = smartcard.Devices;
|
||
|
const Iso7816Application = smartcard.Iso7816Application;
|
||
|
|
||
|
const devices = new Devices();
|
||
|
|
||
|
devices.on('device-activated', event => {
|
||
|
const currentDevices = event.devices;
|
||
|
let device = event.device;
|
||
|
console.log(`Device '${device}' activated, devices: ${currentDevices}`);
|
||
|
for (let prop in currentDevices) {
|
||
|
console.log("Devices: " + currentDevices[prop]);
|
||
|
}
|
||
|
|
||
|
device.on('card-inserted', event => {
|
||
|
let card = event.card;
|
||
|
console.log(`Card '${card.getAtr()}' inserted into '${event.device}'`);
|
||
|
|
||
|
card.on('command-issued', event => {
|
||
|
console.log(`Command '${event.command}' issued to '${event.card}' `);
|
||
|
});
|
||
|
|
||
|
card.on('response-received', event => {
|
||
|
console.log(`Response '${event.response}' received from '${event.card}' in response to '${event.command}'`);
|
||
|
});
|
||
|
|
||
|
const application = new Iso7816Application(card);
|
||
|
application.selectFile([0x31, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31])
|
||
|
.then(response => {
|
||
|
console.info(`Select PSE Response: '${response}' '${response.meaning()}'`);
|
||
|
}).catch(error => {
|
||
|
console.error('Error:', error, error.stack);
|
||
|
});
|
||
|
|
||
|
});
|
||
|
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}]`);
|
||
|
});
|
||
|
```
|