UNPKG

nfc-pcsc

Version:

Easy reading and writing NFC tags and cards

202 lines (133 loc) 4.8 kB
"use strict"; // ############# // Example: MIFARE DESFire // - what is covered: // - 3DES authentication // - reading data files // - known issue: // - [mac0S Sierra and greater] when an error occurs during the authentication process, // the NFC must be reinitialized or the reader reconnected // in order to allow subsequent successful operations (TODO: add appropriate links, fix and test) // ############# import { NFC } from '../src/index'; import pretty from './pretty-logger'; import crypto from 'crypto'; // config const desfire = { key: '00000000000000000000000000000000', appId: [0x00, 0x00, 0x00], keyId: [0x00], read: { // supply location of an existing data 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(); nfc.on('reader', async reader => { pretty.info(`device attached`, 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, 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 = Buffer.from(desfire.key, 'hex'); await authenticate(key); // step 3 await readData(); } catch (err) { pretty.error(`error occurred during processing steps`, reader, err); } }); reader.on('error', err => { pretty.error(`an error occurred`, reader, err); }); reader.on('end', () => { pretty.info(`device removed`, reader); }); }); nfc.on('error', err => { pretty.error(`an error occurred`, err); });