-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathmifare-desfire.js
201 lines (133 loc) · 4.68 KB
/
mifare-desfire.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"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);
});