nfc-pcsc
Version:
Easy reading and writing NFC tags and cards
202 lines (133 loc) • 4.8 kB
JavaScript
// #############
// 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);
});
;