@ukeyfe/react-native-nfc-litecard 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nfc-core.js DELETED
@@ -1,375 +0,0 @@
1
- "use strict";
2
- /**
3
- * Core NFC communication layer – shared by reader and writer.
4
- *
5
- * Provides:
6
- * - A single global operation lock (prevents reader/writer concurrency)
7
- * - Page-cleanup cancellation flags
8
- * - Low-level transceive with platform-aware retries
9
- * - NFC technology request / release
10
- * - AES 3-pass mutual authentication (MF0AES(H)20 §8.6.1)
11
- * - PIN retry-counter helpers (session-level read/write)
12
- */
13
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- var desc = Object.getOwnPropertyDescriptor(m, k);
16
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
- desc = { enumerable: true, get: function() { return m[k]; } };
18
- }
19
- Object.defineProperty(o, k2, desc);
20
- }) : (function(o, m, k, k2) {
21
- if (k2 === undefined) k2 = k;
22
- o[k2] = m[k];
23
- }));
24
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
- Object.defineProperty(o, "default", { enumerable: true, value: v });
26
- }) : function(o, v) {
27
- o["default"] = v;
28
- });
29
- var __importStar = (this && this.__importStar) || (function () {
30
- var ownKeys = function(o) {
31
- ownKeys = Object.getOwnPropertyNames || function (o) {
32
- var ar = [];
33
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
- return ar;
35
- };
36
- return ownKeys(o);
37
- };
38
- return function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
45
- })();
46
- Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.passwordToAesKey = void 0;
48
- exports.acquireNfcLock = acquireNfcLock;
49
- exports.releaseNfcLock = releaseNfcLock;
50
- exports.isNfcOperationLocked = isNfcOperationLocked;
51
- exports.releaseNfcOperationLock = releaseNfcOperationLock;
52
- exports.markNfcOperationCancelledByCleanup = markNfcOperationCancelledByCleanup;
53
- exports.consumeNfcOperationCancelledByCleanup = consumeNfcOperationCancelledByCleanup;
54
- exports.getNfcOperationCancelledByCleanupTimestamp = getNfcOperationCancelledByCleanupTimestamp;
55
- exports.clearNfcOperationCancelledByCleanup = clearNfcOperationCancelledByCleanup;
56
- exports.clearCmacSession = clearCmacSession;
57
- exports.transceive = transceive;
58
- exports.requestNfcTech = requestNfcTech;
59
- exports.releaseNfcTech = releaseNfcTech;
60
- exports.authenticate = authenticate;
61
- exports.readUserMemory = readUserMemory;
62
- exports.decrementRetryCountInSession = decrementRetryCountInSession;
63
- exports.writeRetryCountInSession = writeRetryCountInSession;
64
- const react_native_nfc_manager_1 = __importStar(require("react-native-nfc-manager"));
65
- const react_native_1 = require("react-native");
66
- const constants_1 = require("./constants");
67
- const crypto_1 = require("./crypto");
68
- Object.defineProperty(exports, "passwordToAesKey", { enumerable: true, get: function () { return crypto_1.passwordToAesKey; } });
69
- // ===========================================================================
70
- // Global NFC operation lock (single instance for both reader & writer)
71
- // ===========================================================================
72
- let nfcOperationLock = false;
73
- /**
74
- * Acquire the NFC operation lock.
75
- * If another operation is in progress, polls every 100 ms for up to 10 s.
76
- */
77
- async function acquireNfcLock() {
78
- const maxWait = 10000;
79
- const interval = 100;
80
- let waited = 0;
81
- while (nfcOperationLock) {
82
- if (waited >= maxWait)
83
- throw new Error('NFC_LOCK_TIMEOUT');
84
- await new Promise(r => setTimeout(r, interval));
85
- waited += interval;
86
- }
87
- nfcOperationLock = true;
88
- clearNfcOperationCancelledByCleanup();
89
- }
90
- function releaseNfcLock() {
91
- nfcOperationLock = false;
92
- }
93
- function isNfcOperationLocked() {
94
- return nfcOperationLock;
95
- }
96
- function releaseNfcOperationLock() {
97
- releaseNfcLock();
98
- }
99
- // ===========================================================================
100
- // Page-cleanup cancellation flag
101
- // ===========================================================================
102
- let nfcCancelledByCleanup = false;
103
- let nfcCancelledByCleanupTimestamp = 0;
104
- function markNfcOperationCancelledByCleanup() {
105
- nfcCancelledByCleanup = true;
106
- nfcCancelledByCleanupTimestamp = Date.now();
107
- }
108
- function consumeNfcOperationCancelledByCleanup() {
109
- const cancelled = nfcCancelledByCleanup;
110
- nfcCancelledByCleanup = false;
111
- nfcCancelledByCleanupTimestamp = 0;
112
- return cancelled;
113
- }
114
- function getNfcOperationCancelledByCleanupTimestamp() {
115
- return nfcCancelledByCleanupTimestamp;
116
- }
117
- function clearNfcOperationCancelledByCleanup() {
118
- nfcCancelledByCleanup = false;
119
- nfcCancelledByCleanupTimestamp = 0;
120
- }
121
- // ===========================================================================
122
- // CMAC secure messaging session state
123
- // ===========================================================================
124
- let cmacSessionKey = null;
125
- let cmacCmdCtr = 0;
126
- /** Clear CMAC session state. Called on NFC release. */
127
- function clearCmacSession() {
128
- cmacSessionKey = null;
129
- cmacCmdCtr = 0;
130
- }
131
- /**
132
- * Compute the 8-byte truncated CMAC for an NFC command.
133
- * Input: CmdCtr (2 bytes LE) || CmdCode (1 byte) || Arguments
134
- */
135
- function computeCommandCmac(sessionKey, cmdCtr, command) {
136
- const ctrLo = cmdCtr & 0xff;
137
- const ctrHi = (cmdCtr >> 8) & 0xff;
138
- const input = new Uint8Array(2 + command.length);
139
- input[0] = ctrLo;
140
- input[1] = ctrHi;
141
- input.set(command, 2);
142
- const mac = (0, crypto_1.aesCmac)(sessionKey, input);
143
- return truncateCmac(mac);
144
- }
145
- /**
146
- * Truncate a 16-byte CMAC to 8 bytes using the NXP MIFARE convention:
147
- * select odd-indexed bytes (0-based): MAC[1], MAC[3], MAC[5], ..., MAC[15].
148
- * NXP docs refer to these as "even-numbered bytes" using 1-based numbering.
149
- * (First introduced in MIFARE Plus, see NXP AN13452.)
150
- */
151
- function truncateCmac(mac) {
152
- return new Uint8Array([mac[1], mac[3], mac[5], mac[7], mac[9], mac[11], mac[13], mac[15]]);
153
- }
154
- /**
155
- * Compute the 8-byte truncated CMAC for an NFC response.
156
- * Input: CmdCtr (2 bytes LE) || ResponseData
157
- */
158
- function computeResponseCmac(sessionKey, cmdCtr, responseData) {
159
- const ctrLo = cmdCtr & 0xff;
160
- const ctrHi = (cmdCtr >> 8) & 0xff;
161
- const input = new Uint8Array(2 + responseData.length);
162
- input[0] = ctrLo;
163
- input[1] = ctrHi;
164
- input.set(responseData, 2);
165
- const mac = (0, crypto_1.aesCmac)(sessionKey, input);
166
- return truncateCmac(mac);
167
- }
168
- // ===========================================================================
169
- // Low-level NFC transceive
170
- // ===========================================================================
171
- /**
172
- * Send a command to the NFC tag and return the response.
173
- *
174
- * When CMAC session is active:
175
- * - Appends 8-byte CMAC to outgoing commands
176
- * - Verifies and strips 8-byte CMAC from responses
177
- * - Increments CmdCtr after each command-response pair
178
- *
179
- * Retries: Android up to 2 retries (3 total), iOS up to 1 retry (2 total).
180
- */
181
- async function transceive(command, _timeoutMs = 2000, retryCount = 0) {
182
- // Build the actual command to send
183
- let cmdToSend = command;
184
- if (cmacSessionKey) {
185
- const mac = computeCommandCmac(cmacSessionKey, cmacCmdCtr, command);
186
- cmdToSend = [...command, ...Array.from(mac)];
187
- }
188
- try {
189
- const result = react_native_1.Platform.OS === 'ios'
190
- ? await react_native_nfc_manager_1.default.sendMifareCommandIOS(cmdToSend)
191
- : await react_native_nfc_manager_1.default.nfcAHandler.transceive(cmdToSend);
192
- if (cmacSessionKey) {
193
- // CmdCtr increments between command and response (datasheet §8.8.3)
194
- cmacCmdCtr++;
195
- }
196
- if (cmacSessionKey && result && result.length >= 8) {
197
- // Response has CMAC appended: split data and MAC
198
- const dataLen = result.length - 8;
199
- const responseData = result.slice(0, dataLen);
200
- const responseMac = result.slice(dataLen);
201
- // Verify response CMAC with incremented CmdCtr
202
- const expectedMac = computeResponseCmac(cmacSessionKey, cmacCmdCtr, responseData);
203
- let diff = 0;
204
- for (let i = 0; i < 8; i++) {
205
- diff |= responseMac[i] ^ expectedMac[i];
206
- }
207
- if (diff !== 0) {
208
- throw new Error('CMAC_VERIFY_FAILED');
209
- }
210
- return responseData;
211
- }
212
- return result;
213
- }
214
- catch (error) {
215
- const maxRetries = react_native_1.Platform.OS === 'ios' ? 1 : 2;
216
- if (retryCount < maxRetries && error?.message !== 'CMAC_VERIFY_FAILED') {
217
- const wait = 200 * (retryCount + 1);
218
- await new Promise(r => setTimeout(r, wait));
219
- return transceive(command, _timeoutMs, retryCount + 1);
220
- }
221
- throw error;
222
- }
223
- }
224
- // ===========================================================================
225
- // NFC technology connect / disconnect
226
- // ===========================================================================
227
- /**
228
- * Request an NFC technology session (MifareIOS on iOS, NfcA on Android).
229
- *
230
- * All prior cleanup must be done by the caller BEFORE invoking this.
231
- */
232
- async function requestNfcTech() {
233
- await new Promise(r => setTimeout(r, 100));
234
- if (react_native_1.Platform.OS === 'ios') {
235
- await react_native_nfc_manager_1.default.requestTechnology(react_native_nfc_manager_1.NfcTech.MifareIOS);
236
- }
237
- else {
238
- await react_native_nfc_manager_1.default.requestTechnology(react_native_nfc_manager_1.NfcTech.NfcA);
239
- }
240
- if (react_native_1.Platform.OS === 'android') {
241
- await new Promise(r => setTimeout(r, constants_1.DELAY.ANDROID_POST_TECH));
242
- }
243
- else {
244
- // iOS: probe connection availability
245
- try {
246
- await react_native_nfc_manager_1.default.sendMifareCommandIOS([constants_1.CMD_READ, 0x00]);
247
- }
248
- catch (_) {
249
- // Non-fatal – continue even if the probe fails
250
- }
251
- }
252
- }
253
- /**
254
- * Release the NFC technology session.
255
- * @param forceLongDelay Use a longer post-release delay (e.g. after timeout / tag-lost).
256
- */
257
- async function releaseNfcTech(forceLongDelay = false) {
258
- clearCmacSession();
259
- try {
260
- await react_native_nfc_manager_1.default.cancelTechnologyRequest();
261
- const delay = react_native_1.Platform.OS === 'ios'
262
- ? (forceLongDelay ? constants_1.DELAY.IOS_POST_RELEASE_FORCED : constants_1.DELAY.IOS_POST_RELEASE)
263
- : (forceLongDelay ? constants_1.DELAY.ANDROID_POST_RELEASE_FORCED : constants_1.DELAY.ANDROID_POST_RELEASE);
264
- await new Promise(r => setTimeout(r, delay));
265
- }
266
- catch (error) {
267
- console.warn('[nfc-core] releaseNfcTech failed:', error?.message);
268
- }
269
- }
270
- // ===========================================================================
271
- // AES 3-pass mutual authentication
272
- // ===========================================================================
273
- /**
274
- * Perform AES 3-pass mutual authentication (MF0AES(H)20 §8.6.1).
275
- *
276
- * 1. PCD → PICC : AUTH_PART1 (0x1A) + KeyNo
277
- * 2. PICC → PCD : 0xAF + ek(RndB)
278
- * 3. PCD decrypts RndB, generates RndA, computes RndB' = rotateLeft8(RndB)
279
- * 4. PCD → PICC : AUTH_PART2 (0xAF) + ek(RndA ‖ RndB')
280
- * 5. PICC → PCD : 0x00 + ek(RndA')
281
- * 6. PCD verifies RndA' === rotateLeft8(RndA)
282
- *
283
- * @throws AUTH_INVALID_RESPONSE / AUTH_WRONG_PASSWORD / AUTH_VERIFY_FAILED
284
- */
285
- async function authenticate(key) {
286
- // Clear any previous CMAC session before starting auth
287
- // (auth commands themselves are never CMAC-protected)
288
- clearCmacSession();
289
- const iv0 = new Uint8Array(16);
290
- // Step 1
291
- const response1 = await transceive([constants_1.CMD_AUTH_PART1, constants_1.KEY_NO_DATA_PROT]);
292
- if (!response1 || response1.length < 17 || response1[0] !== 0xaf) {
293
- throw new Error('AUTH_INVALID_RESPONSE');
294
- }
295
- // Step 2 – decrypt RndB
296
- const ekRndB = new Uint8Array(response1.slice(1, 17));
297
- const rndB = (0, crypto_1.aesDecrypt)(key, ekRndB, iv0);
298
- // Step 3
299
- const rndBRot = (0, crypto_1.rotateLeft8)(rndB);
300
- const rndA = (0, crypto_1.generateRandom16)();
301
- // Step 4 – encrypt RndA ‖ RndB' (CBC, two 16-byte blocks)
302
- const rndAB = new Uint8Array(32);
303
- rndAB.set(rndA, 0);
304
- rndAB.set(rndBRot, 16);
305
- const ekPart1 = (0, crypto_1.aesEncrypt)(key, rndAB.slice(0, 16), iv0);
306
- const ekPart2 = (0, crypto_1.aesEncrypt)(key, rndAB.slice(16, 32), ekPart1);
307
- const ekRndAB = new Uint8Array(32);
308
- ekRndAB.set(ekPart1, 0);
309
- ekRndAB.set(ekPart2, 16);
310
- // Step 5
311
- let response2;
312
- try {
313
- response2 = await transceive([constants_1.CMD_AUTH_PART2, ...Array.from(ekRndAB)]);
314
- }
315
- catch (_) {
316
- throw new Error('AUTH_WRONG_PASSWORD');
317
- }
318
- if (response2.length < 17 || response2[0] !== 0x00) {
319
- throw new Error('AUTH_WRONG_PASSWORD');
320
- }
321
- // Step 6 – verify RndA'
322
- const ekRndARot = new Uint8Array(response2.slice(1, 17));
323
- const rndARot = (0, crypto_1.aesDecrypt)(key, ekRndARot, iv0);
324
- const expectedRndARot = (0, crypto_1.rotateLeft8)(rndA);
325
- if (!(0, crypto_1.arraysEqual)(rndARot, expectedRndARot)) {
326
- throw new Error('AUTH_VERIFY_FAILED');
327
- }
328
- // Step 7 – derive CMAC session key and enable secure messaging
329
- cmacSessionKey = (0, crypto_1.deriveSessionKey)(key, rndA, rndB);
330
- cmacCmdCtr = 0;
331
- }
332
- // ===========================================================================
333
- // Shared memory read helpers
334
- // ===========================================================================
335
- /** FAST_READ pages 0x08–0x27 (user memory). */
336
- async function readUserMemory() {
337
- const response = await transceive([constants_1.CMD_FAST_READ, constants_1.USER_PAGE_START, constants_1.USER_PAGE_END]);
338
- if (!response || response.length < constants_1.USER_MEMORY_SIZE)
339
- throw new Error('READ_FAILED');
340
- return new Uint8Array(response.slice(0, constants_1.USER_MEMORY_SIZE));
341
- }
342
- // ===========================================================================
343
- // PIN retry-counter session helpers
344
- // ===========================================================================
345
- /**
346
- * Decrement the on-card retry counter by 1 (clamped to 0).
347
- * Must be called within an active NFC session (between requestNfcTech and releaseNfcTech).
348
- */
349
- async function decrementRetryCountInSession() {
350
- const pageBlock = await transceive([constants_1.CMD_READ, constants_1.RETRY_COUNTER_PAGE]);
351
- if (!pageBlock || pageBlock.length < constants_1.PAGE_SIZE)
352
- return undefined;
353
- const page = pageBlock.slice(0, constants_1.PAGE_SIZE);
354
- const raw = page[constants_1.RETRY_COUNTER_OFFSET];
355
- const current = typeof raw === 'number' ? raw & 0xff : 0;
356
- if (current <= 0)
357
- throw new Error('RETRY_COUNT_EXHAUSTED');
358
- const next = current - 1;
359
- page[constants_1.RETRY_COUNTER_OFFSET] = next & 0xff;
360
- await transceive([constants_1.CMD_WRITE, constants_1.RETRY_COUNTER_PAGE, ...page]);
361
- return next;
362
- }
363
- /**
364
- * Write a specific retry count to the on-card counter.
365
- * Must be called within an active NFC session.
366
- */
367
- async function writeRetryCountInSession(count) {
368
- const safeCount = Math.max(0, Math.min(0xff, count | 0));
369
- const pageBlock = await transceive([constants_1.CMD_READ, constants_1.RETRY_COUNTER_PAGE]);
370
- if (!pageBlock || pageBlock.length < constants_1.PAGE_SIZE)
371
- throw new Error('READ_FAILED');
372
- const page = pageBlock.slice(0, constants_1.PAGE_SIZE);
373
- page[constants_1.RETRY_COUNTER_OFFSET] = safeCount & 0xff;
374
- await transceive([constants_1.CMD_WRITE, constants_1.RETRY_COUNTER_PAGE, ...page]);
375
- }
package/dist/reader.d.ts DELETED
@@ -1,98 +0,0 @@
1
- /**
2
- * NFC Reader Module
3
- *
4
- * Public API:
5
- * - checkCard() – detect whether a card is empty or contains data
6
- * - readMnemonic() – read mnemonic (password required)
7
- * - readUserNickname() – read user nickname (password optional)
8
- * - readMnemonicRetryCount() – read the PIN retry counter
9
- * - resetRetryCountTo10() – reset the retry counter to default
10
- * - cardInfoToJson() – serialise card-info to JSON
11
- *
12
- * Based on MIFARE Ultralight AES (MF0AES(H)20) datasheet.
13
- */
14
- import { DEFAULT_PIN_RETRY_COUNT } from './constants';
15
- import { NfcStatusCode, type NfcResult } from './types';
16
- import { isNfcOperationLocked, releaseNfcOperationLock, markNfcOperationCancelledByCleanup, consumeNfcOperationCancelledByCleanup, getNfcOperationCancelledByCleanupTimestamp, clearNfcOperationCancelledByCleanup } from './nfc-core';
17
- export { NfcStatusCode, type NfcResult, isNfcOperationLocked, releaseNfcOperationLock, markNfcOperationCancelledByCleanup, consumeNfcOperationCancelledByCleanup, getNfcOperationCancelledByCleanupTimestamp, clearNfcOperationCancelledByCleanup, DEFAULT_PIN_RETRY_COUNT, };
18
- declare function parseCardInfo(data: Uint8Array): {
19
- version: number;
20
- cardType: number;
21
- rawBytes: string;
22
- infoBytes: Uint8Array<ArrayBuffer>;
23
- json: {
24
- pageInfo: {
25
- startPage: number;
26
- endPage: number;
27
- totalPages: number;
28
- totalBytes: number;
29
- };
30
- version: {
31
- value: number;
32
- hex: string;
33
- };
34
- cardType: {
35
- value: number;
36
- hex: string;
37
- description: string;
38
- known: boolean;
39
- };
40
- additionalInfo: {
41
- hex: string;
42
- bytes: number[];
43
- };
44
- rawData: {
45
- hex: string;
46
- bytes: number[];
47
- length: number;
48
- };
49
- pageBreakdown: {
50
- page: number;
51
- bytes: number[];
52
- hex: string;
53
- }[];
54
- };
55
- };
56
- /** Serialise parseCardInfo result to JSON. */
57
- export declare function cardInfoToJson(cardInfo: ReturnType<typeof parseCardInfo>, pretty?: boolean): string;
58
- /**
59
- * Detect whether the card is empty or already contains data.
60
- *
61
- * Without password: tries to read without auth.
62
- * - Read succeeds & first byte is a valid mnemonic type → HAS_DATA
63
- * - Read succeeds & first byte is other → EMPTY
64
- * - Read fails (auth required) → HAS_DATA (read-protection is on, cannot determine)
65
- *
66
- * With password: authenticates first, then reads and validates with CRC16.
67
- * - Valid mnemonic payload → HAS_DATA (with type info)
68
- * - Empty or invalid data → EMPTY
69
- * - Auth failure → AUTH_WRONG_PASSWORD
70
- */
71
- export declare function checkCard(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
72
- /**
73
- * Read the mnemonic from a password-protected card.
74
- *
75
- * Flow: connect → pre-decrement retry counter → authenticate → read →
76
- * decode entropy → read nickname → reset retry counter → release
77
- */
78
- export declare function readMnemonic(password: string, onCardIdentified?: () => void): Promise<NfcResult>;
79
- /**
80
- * Read the user nickname from the card.
81
- * @param password – supply if the card has read-protection enabled.
82
- */
83
- export declare function readUserNickname(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
84
- /** Read the current PIN retry counter from the card. */
85
- export declare function readMnemonicRetryCount(onCardIdentified?: () => void): Promise<NfcResult>;
86
- /** Reset the PIN retry counter to the default value (10). */
87
- export declare function resetRetryCountTo10(onCardIdentified?: () => void): Promise<NfcResult>;
88
- /**
89
- * Read card product version info (GET_VERSION command).
90
- * No authentication required.
91
- */
92
- export declare function getCardVersion(onCardIdentified?: () => void): Promise<NfcResult>;
93
- /**
94
- * Read the ECC originality signature (READ_SIG command).
95
- * Verifies the card is a genuine NXP product.
96
- * No authentication required (unless Random ID is enabled).
97
- */
98
- export declare function readOriginality(onCardIdentified?: () => void): Promise<NfcResult>;