-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathmifare-ultralight-ntag.js
272 lines (200 loc) · 7.79 KB
/
mifare-ultralight-ntag.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
'use strict';
// #############
// Example: MIFARE Ultralight EV1 and NTAG 213/215/216 – implementation of card's specific commands
// - note: for instructions on reading and writing the data,
// please see read-write.js, which is common for all ISO/IEC 14443-3 tags
// - note: this guide applies to NTAG 213/215/216 cards as-well
// (the commands and configuration pages structure is same or very similar,
// the only difference is the location of the pages due to different user memory size)
// - docs (descriptions of the commands and data structure):
// - MIFARE Ultralight EV1 – see https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf
// - NTAG 213/215/216 – https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf
// - note: works ONLY on ACR122U reader or possibly any reader which uses NXP PN533 and similar NFC frontends
// - what is covered:
// - password authentication – PWD_AUTH command
// - fast read – FAST_READ command
// - setting card configuration pages – set custom password and access conditions
// #############
import { NFC, TAG_ISO_14443_3, TAG_ISO_14443_4, KEY_TYPE_A, KEY_TYPE_B, TransmitError } from '../src/index';
import pretty from './pretty-logger';
export class MifareUltralightPasswordAuthenticationError extends TransmitError {
constructor(code, message, previousError) {
super(code, message, previousError);
this.name = 'MifareUltralightPasswordAuthenticationError';
}
}
export class MifareUltralightFastReadError extends TransmitError {
constructor(code, message, previousError) {
super(code, message, previousError);
this.name = 'MifareUltralightFastReadError';
}
}
const parseBytes = (name, data, length) => {
if (!(data instanceof Buffer) && typeof data !== 'string') {
throw new Error(`${name} must an instance of Buffer or a HEX string.`);
}
if (Buffer.isBuffer(data)) {
if (data.length !== length) {
throw new Error(`${name} must be ${length} bytes long.`);
}
return data;
}
if (typeof data === 'string') {
if (data.length !== length * 2) {
throw new Error(`${name} must be a ${length * 2} char HEX string.`);
}
return Buffer.from(data, 'hex');
}
throw new Error(`${name} must an instance of Buffer or a HEX string.`);
};
class MifareUltralight {
constructor(reader) {
this.reader = reader;
}
// PWD_AUTH
async passwordAuthenticate(password, pack) {
// PASSWORD (4 bytes) (stored on card in page 18)
// PACK (2 bytes) (stored in page 19 as first two bytes)
// PACK is the response from card in case of successful PWD_AUTH cmd
password = parseBytes('Password', password, 4);
pack = parseBytes('Pack', pack, 2);
// CMD: PWD_AUTH via Direct Transmit (ACR122U) and Data Exchange (PN533)
const cmd = Buffer.from([
0xff, // Class
0x00, // Direct Transmit (see ACR122U docs)
0x00, // ...
0x00, // ...
0x07, // Length of Direct Transmit payload
// Payload (7 bytes)
0xd4, // Data Exchange Command (see PN533 docs)
0x42, // InCommunicateThru
0x1b, // PWD_AUTH
...password,
]);
this.reader.logger.debug('pwd_auth cmd', cmd);
const response = await this.reader.transmit(cmd, 7);
this.reader.logger.debug('pwd_auth response', response);
// pwd_auth response should look like the following (7 bytes)
// d5 43 00 ab cd 90 00
// byte 0: d5 prefix for response of Data Exchange Command (see PN533 docs)
// byte 1: 43 prefix for response of Data Exchange Command (see PN533 docs)
// byte 2: Data Exchange Command Status 0x00 is success (see PN533 docs, Table 15. Error code list)
// bytes 3-4: Data Exchange Command Response – our PACK (set on card in page 19, in bytes 0-1) from card
// bytes 5-6: ACR122U success code
if (response.length < 5) {
throw new MifareUltralightPasswordAuthenticationError('invalid_response_length', `Invalid response length ${response.length}. Expected minimal length was 2 bytes.`)
}
if (response[2] !== 0x00 || response.length < 7) {
throw new MifareUltralightPasswordAuthenticationError('invalid_password', `Authentication failed. Might be invalid password or unsupported card.`);
}
if (!response.slice(3, 5).equals(pack)) {
throw new MifareUltralightPasswordAuthenticationError('pack_mismatch', `Pack mismatch.`)
}
return;
}
// FAST_READ
async fastRead(startPage, endPage) {
// CMD: PWD_AUTH via Direct Transmit (ACR122U) and Data Exchange (PN533)
const cmd = Buffer.from([
0xff, // Class
0x00, // Direct Transmit (see ACR122U docs)
0x00, // ...
0x00, // ...
0x07, // Length of Direct Transmit payload
// Payload (7 bytes)
0xd4, // Data Exchange Command (see PN533 docs)
0x42, // InCommunicateThru
0x3a, // PWD_AUTH
startPage,
endPage,
]);
const length = 3 + ((endPage - startPage + 1) * 4) + 2;
const response = await this.reader.transmit(cmd, length);
if (response < length) {
throw new MifareUltralightFastReadError('invalid_response_length', `Invalid response length ${response.length}. Expected length was ${length} bytes.`)
}
return response.slice(3, -2);
}
}
const nfc = new NFC(pretty); // const nfc = new NFC(pretty); // optionally you can pass logger to see internal debug logs
nfc.on('reader', async reader => {
pretty.info(`device attached`, reader);
const ultralight = new MifareUltralight(reader);
reader.on('card', async card => {
pretty.info('card detected', reader, card);
const password = 'FFFFFFFF'; // default password
const pack = '0000'; // default pack
try {
await ultralight.passwordAuthenticate(password, pack);
pretty.info('passwordAuthenticate: successfully authenticated');
} catch (err) {
pretty.error('passwordAuthenticate error:', err);
}
try {
const data = await ultralight.fastRead(16, 19);
pretty.info('fastRead data:', data);
} catch (err) {
pretty.error('fastRead error:', err);
return;
}
// Note! UPDATE locations of configuration pages according to the version of your card!
// (see memory layout in your card's docs)
// try {
//
// // set custom PASSWORD (4 bytes) (stored in page 18)
// await reader.write(19, password);
//
// // set custom PACK (2 bytes) (stored in page 19 as first two bytes
// const packPage = await reader.read(19, 4);
// packPage[0] = pack[0];
// packPage[1] = pack[1];
// await reader.write(19, packPage);
//
// // read current configuration
// const config = await reader.read(16, 8);
//
// // Configuration page 16
// console.log(config[0]);
// console.log(config[1]);
// console.log(config[2]);
// console.log(config[3]); // AUTH0 (default: 0xff)
//
// // Configuration page 17
// console.log(config[4]); // ACCESS
// console.log(config[5]); // VCTID (default: 0x05)
// console.log(config[6]);
// console.log(config[7]);
//
// // Protect everything (start with first data page)
// config[3] = 0x04;
//
// // set ACCESS bits
// // bit 7: PROT One bit inside the ACCESS byte defining the memory protection
// // 0b ... write access is protected by the password verification
// // 1b ... read and write access is protected by the password verification
// // bit 6: CFGLCK Write locking bit for the user configuration
// // - 0b ... user configuration open to write access
// // - 1b ... user configuration permanently locked against write access
// // bits 5-3: reserved
// // bits 2-0: AUTHLIM
// // bit number-76543210
// // ||||||||
// config[4] = 0b10000000;
//
// // set custom access rules
// await reader.write(16, config);
//
// } catch (err) {
// pretty.error('configuration write error:', 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);
});