Skip to content

Commit

Permalink
Led control support (#6)
Browse files Browse the repository at this point in the history
Add support for controlling led and buzzer
Add support for direct connection to the reader
Add support for direct transmit
Update dependencies
Add yarn.lock
Move APDU commands specific to ACR122 to new class
Fix typos in README
Add Discord badge in README
Move and add examples to new folder
Update README (add note about usage in Electron)
  • Loading branch information
pokusew committed Jan 28, 2017
1 parent 39f2f5f commit 1477d23
Show file tree
Hide file tree
Showing 12 changed files with 2,192 additions and 78 deletions.
10 changes: 0 additions & 10 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# nfc-pcsc

[![npm](https://img.shields.io/npm/v/nfc-pcsc.svg)](https://www.npmjs.com/package/nfc-pcsc)
[![npm](https://img.shields.io/npm/v/nfc-pcsc.svg?maxAge=2592000)](https://www.npmjs.com/package/nfc-pcsc)
[![nfc-pcsc channel on discord](https://img.shields.io/badge/discord-join%20chat-61dafb.svg)](https://discord.gg/bg3yazg)

A simple wrapper around [pokusew/node-pcsclite](https://github.com/pokusew/node-pcsclite) to work easier with NFC tags.

Built-in support for reading **card UIDs** and reading tags emulated with [**Android HCE**](https://developer.android.com/guide/topics/connectivity/nfc/hce.html).

> **NOTE:** Reading tag UID and methods for writing and reading tag content **depend on NFC reader commands support**.
It is tested to work with **ARC122 USB reader** but it can work with others too.
It is tested to work with **ACR122 USB reader** but it can work with others too.
When detecting tags does not work see [Alternative usage](#alternative-usage).

## Content
Expand All @@ -22,6 +23,8 @@ When detecting tags does not work see [Alternative usage](#alternative-usage).
- [Running examples locally](#running-examples-locally)
- [Alternative usage](#alternative-usage)
- [Reading and writing data](#reading-and-writing-data)
- [FAQ](#faq)
- [Can I use this library in my Electron app?](#can-i-use-this-library-in-my-electron-app)
- [LICENSE](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -62,11 +65,11 @@ When a NFC tag (card) is attached to the reader, the following is done:
## Basic usage

> ### Running examples locally
> If you want see it in action, clone this repository, install dependencies with npm and run `npm run test`.
> If you want see it in action, clone this repository, install dependencies with npm and run `npm run example`.
> ```bash
> git clone https://github.com/pokusew/nfc-pcsc.git
> npm install
> npm run test
> npm run example
> ```

Expand Down Expand Up @@ -113,7 +116,7 @@ nfc.on('error', err => {
## Alternative usage

You can **disable auto processing of tags** and process them yourself.
It may be useful when you are using other than ARC122 USB reader or non-standard tags.
It may be useful when you are using other than ACR122 USB reader or non-standard tags.

```javascript
import NFC from 'nfc-pcsc';
Expand Down Expand Up @@ -242,6 +245,23 @@ reader.on('card', async card => {
});
```


## FAQ

### Can I use this library in my [Electron](http:https://electron.atom.io/) app?

Yes, you can! It works well.

**But please note**, that this library uses [Node Native Modules](https://nodejs.org/api/addons.html) (underlying library [pokusew/node-pcsclite](https://github.com/pokusew/node-pcsclite) which provides access to PC/SC API).

Read carefully **[Using Native Node Modules](http:https://electron.atom.io/docs/tutorial/using-native-node-modules/) guide in Electron documentation** to fully understand the problematic.

**Note**, that because of Node Native Modules, you must build your app on target platform (you must run Windows build on Windows machine, etc.).
You can use CI/CD server to build your app for certain platforms.
For Windows, I recommend you to use [AppVeyor](https://appveyor.com/).
For macOS and Linux build, there are plenty of services to choose from, for example [CircleCI](https://circleci.com/), [Travis CI](https://travis-ci.com/) [CodeShip](https://codeship.com/).


## LICENSE

The nfc node module, documentation, tests, and build scripts are licensed
Expand Down
212 changes: 212 additions & 0 deletions examples/desfire.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
"use strict";

// #############
// Example accessing and authenticating Mifare DESFIRE cards
// #############

import winston from 'winston';
import NFC, { TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B } from '../src/NFC';
import pretty from './pretty';
import crypto from 'crypto';


// config
const desfire = {
key: '00000000000000000000000000000000',
appId: [0x00, 0x00, 0x00],
keyId: [0x00],
read: {
fileId: [0x02],
offset: [0x00, 0x00, 0x00],
length: [14, 0x00, 0x00]
}
};


function decrypt(key, data, iv = Buffer.alloc(8).fill(0)) {

const decipher = crypto.createDecipheriv('DES-EDE-CBC', key, iv);
decipher.setAutoPadding(false);

return Buffer.concat([decipher.update(data), decipher.final()]);

}

function encrypt(key, data, iv = Buffer.alloc(8).fill(0)) {

const decipher = crypto.createCipheriv('DES-EDE-CBC', key, iv);
decipher.setAutoPadding(false);

return Buffer.concat([decipher.update(data), decipher.final()]);

}


const nfc = new NFC();

let readers = [];

nfc.on('reader', async reader => {

pretty.info(`device attached`, { reader: reader.name });

readers.push(reader);

// we have to handle Mifare DESFIRE
reader.autoProcessing = false;

// just handy shortcut to send data
const send = async(cmd, comment = null, responseMaxLength = 40) => {

const b = Buffer.from(cmd);

console.log((comment ? `[${comment}] ` : '') + `sending`, b);

const data = await reader.transmit(b, responseMaxLength);

console.log((comment ? `[${comment}] ` : '') + `received data`, data);

return data;

};

const wrap = (cmd, dataIn) => ([0x90, cmd, 0x00, 0x00, dataIn.length, ...dataIn, 0x00]);

reader.on('card', async card => {

pretty.info(`card detected`, { reader: reader.name, card });

const selectApplication = async() => {

// 1: [0x5A] SelectApplication(appId) [4 bytes] - Selects one specific application for further access
// DataIn: appId (3 bytes)
const res = await send(wrap(0x5a, desfire.appId), 'step 1 - select app');

// something went wrong
if (res.slice(-1)[0] !== 0x00) {
throw new Error('error in step 1');
}


};

const authenticate = async(key) => {

// 2: [0x0a] Authenticate(keyId) [2bytes]
// DataIn: keyId (1 byte)
const res1 = await send(wrap(0x0a, desfire.keyId), 'step 2 - authenticate');

// something went wrong
if (res1.slice(-1)[0] !== 0xaf) {
throw new Error('error in step 2 - authenticate');
}

// encrypted RndB from reader
// cut out status code (last 2 bytes)
const ecRndB = res1.slice(0, -2);

// decrypt it
const RndB = decrypt(key, ecRndB);

// rotate RndB
const RndBp = Buffer.concat([RndB.slice(1, 8), RndB.slice(0, 1)]);

// generate a 8 byte Random Number A
const RndA = crypto.randomBytes(8);

// concat RndA and RndBp
const msg = encrypt(key, Buffer.concat([RndA, RndBp]));

// send it back to the reader
const res2 = await send(wrap(0xaf, msg), 'step 2 - set up RndA');

// something went wrong
if (res2.slice(-1)[0] !== 0x00) {
throw new Error('error in step 2 - set up RndA');
}

// encrypted RndAp from reader
// cut out status code (last 2 bytes)
const ecRndAp = res2.slice(0, -2);

// decrypt to get rotated value of RndA2
const RndAp = decrypt(key, ecRndAp);

// rotate
const RndA2 = Buffer.concat([RndAp.slice(7, 8), RndAp.slice(0, 7)]);

// compare decrypted RndA2 response from reader with our RndA
// if it equals authentication process was successful
if (!RndA.equals(RndA2)) {
throw new Error('error in step 2 - match RndA random bytes');
}

return {
RndA,
RndB
};

};

const readData = async() => {

// 3: [0xBD] ReadData(FileNo,Offset,Length) [8bytes] - Reads data from Standard Data Files or Backup Data Files
const res = await send(wrap(0xbd, [desfire.read.fileId, ...desfire.read.offset, ...desfire.read.length]), 'step 3 - read', 255);

// something went wrong
if (res.slice(-1)[0] !== 0x00) {
throw new Error('error in step 3 - read');
}

console.log('data', res);

};


try {

// step 1
await selectApplication();

// step 2
const key = new Buffer(desfire.key, 'hex');
await authenticate(key);

// step 3
await readData();


} catch (err) {

pretty.error(`error occurred during processing steps`, { reader: reader.name });
console.log(err);

}


});

reader.on('error', err => {

pretty.error(`an error occurred`, { reader: reader.name, err });

});

reader.on('end', () => {

pretty.info(`device removed`, { reader: reader.name });

delete readers[readers.indexOf(reader)];

console.log(readers);

});


});

nfc.on('error', err => {

pretty.error(`an error occurred`, err);

});
8 changes: 8 additions & 0 deletions test/index.js → examples/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
"use strict";

// #############
// Basic example
// - example reading and writing data on from/to card
// - should work well with any compatible PC/SC card reader
// - tested with Mifare Ultralight cards but should work with many others
// - example authentication for Mifare Classic cards
// #############

import winston from 'winston';
import NFC, { TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B } from '../src/NFC';
import pretty from './pretty';
Expand Down
Loading

0 comments on commit 1477d23

Please sign in to comment.