@ukeyfe/react-native-nfc-litecard 1.0.1 → 1.0.3
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/README.md +277 -238
- package/README.zh.md +499 -0
- package/dist/constants.d.ts +30 -0
- package/dist/constants.js +40 -1
- package/dist/crypto.d.ts +22 -0
- package/dist/crypto.js +165 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -3
- package/dist/nfc-core.d.ts +9 -0
- package/dist/nfc-core.js +97 -9
- package/dist/reader.d.ts +13 -2
- package/dist/reader.js +153 -66
- package/dist/types.d.ts +16 -4
- package/dist/types.js +26 -24
- package/dist/utils.d.ts +16 -0
- package/dist/utils.js +36 -28
- package/dist/writer.d.ts +13 -6
- package/dist/writer.js +116 -94
- package/package.json +2 -2
- package/README.en.md +0 -461
package/dist/writer.d.ts
CHANGED
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Based on MIFARE Ultralight AES (MF0AES(H)20) datasheet.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { NfcStatusCode, type NfcResult } from './types';
|
|
14
14
|
import { isNfcOperationLocked, releaseNfcOperationLock } from './nfc-core';
|
|
15
|
-
export {
|
|
15
|
+
export { NfcStatusCode, type NfcResult, isNfcOperationLocked, releaseNfcOperationLock };
|
|
16
16
|
/**
|
|
17
|
-
* Initialize a
|
|
17
|
+
* Initialize a card: authenticate with the default password, then write
|
|
18
|
+
* mnemonic and set a new password.
|
|
19
|
+
*
|
|
20
|
+
* @param mnemonic BIP-39 mnemonic to write.
|
|
21
|
+
* @param newPassword Password to protect the card with after initialization.
|
|
22
|
+
* @param defaultPassword Current (factory-default) password on the card.
|
|
18
23
|
*/
|
|
19
|
-
export declare function initializeCard(mnemonic: string,
|
|
24
|
+
export declare function initializeCard(mnemonic: string, newPassword: string, defaultPassword: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
20
25
|
/**
|
|
21
26
|
* Update card: authenticate with old password, then write new mnemonic + new password.
|
|
22
27
|
*
|
|
@@ -33,7 +38,9 @@ export declare function updatePassword(oldPassword: string, newPassword: string,
|
|
|
33
38
|
/** Write a user nickname (password required for authentication). */
|
|
34
39
|
export declare function writeUserNickname(password: string, nickname: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
|
35
40
|
/**
|
|
36
|
-
* Reset card: wipe
|
|
41
|
+
* Reset card: wipe mnemonic data and set a new password.
|
|
37
42
|
* @param password – current card password (required if protection is enabled).
|
|
43
|
+
* @param newPassword – password to set after reset.
|
|
44
|
+
* @param onCardIdentified – callback when card is identified.
|
|
38
45
|
*/
|
|
39
|
-
export declare function resetCard(password
|
|
46
|
+
export declare function resetCard(password: string | undefined, newPassword: string, onCardIdentified?: () => void): Promise<NfcResult>;
|
package/dist/writer.js
CHANGED
|
@@ -48,7 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
48
48
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
49
|
};
|
|
50
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
-
exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.
|
|
51
|
+
exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.NfcStatusCode = void 0;
|
|
52
52
|
exports.initializeCard = initializeCard;
|
|
53
53
|
exports.updateCard = updateCard;
|
|
54
54
|
exports.updatePassword = updatePassword;
|
|
@@ -59,7 +59,7 @@ const react_native_1 = require("react-native");
|
|
|
59
59
|
const bip39 = __importStar(require("bip39"));
|
|
60
60
|
const constants_1 = require("./constants");
|
|
61
61
|
const types_1 = require("./types");
|
|
62
|
-
Object.defineProperty(exports, "
|
|
62
|
+
Object.defineProperty(exports, "NfcStatusCode", { enumerable: true, get: function () { return types_1.NfcStatusCode; } });
|
|
63
63
|
const utils_1 = require("./utils");
|
|
64
64
|
const crypto_1 = require("./crypto");
|
|
65
65
|
const nfc_core_1 = require("./nfc-core");
|
|
@@ -77,22 +77,23 @@ async function writePage(page, data) {
|
|
|
77
77
|
await (0, nfc_core_1.transceive)([constants_1.CMD_WRITE, page, ...data]);
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* Write data to
|
|
80
|
+
* Write data to mnemonic area (pages 0x08–0x24).
|
|
81
|
+
* Does NOT touch the nickname area (pages 0x25–0x27).
|
|
81
82
|
* iOS: inserts small delays and periodic keep-alive reads.
|
|
82
83
|
*/
|
|
83
84
|
async function writeUserMemory(data) {
|
|
84
|
-
const buffer = new Uint8Array(constants_1.
|
|
85
|
-
buffer.set(data, 0);
|
|
86
|
-
const totalPages = constants_1.
|
|
85
|
+
const buffer = new Uint8Array(constants_1.MNEMONIC_MEMORY_SIZE);
|
|
86
|
+
buffer.set(data.slice(0, constants_1.MNEMONIC_MEMORY_SIZE), 0);
|
|
87
|
+
const totalPages = constants_1.MNEMONIC_PAGE_END - constants_1.USER_PAGE_START + 1;
|
|
87
88
|
for (let i = 0; i < totalPages; i++) {
|
|
88
89
|
const page = constants_1.USER_PAGE_START + i;
|
|
89
90
|
const offset = i * constants_1.PAGE_SIZE;
|
|
90
91
|
const pageData = Array.from(buffer.slice(offset, offset + constants_1.PAGE_SIZE));
|
|
91
92
|
await writePage(page, pageData);
|
|
92
93
|
if (react_native_1.Platform.OS === 'ios' && i < totalPages - 1) {
|
|
93
|
-
await new Promise(r => setTimeout(r,
|
|
94
|
+
await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_PAGE_WRITE));
|
|
94
95
|
// Keep-alive every 4 pages to prevent iOS session timeout
|
|
95
|
-
if ((i + 1) %
|
|
96
|
+
if ((i + 1) % constants_1.DELAY.KEEPALIVE_FREQ === 0) {
|
|
96
97
|
try {
|
|
97
98
|
await react_native_nfc_manager_1.default.sendMifareCommandIOS([0x30, 0x00]);
|
|
98
99
|
}
|
|
@@ -101,13 +102,6 @@ async function writeUserMemory(data) {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
|
-
/** FAST_READ pages 0x08–0x27 (user memory). */
|
|
105
|
-
async function readUserMemory() {
|
|
106
|
-
const response = await (0, nfc_core_1.transceive)([constants_1.CMD_FAST_READ, constants_1.USER_PAGE_START, constants_1.USER_PAGE_END]);
|
|
107
|
-
if (!response || response.length < constants_1.USER_MEMORY_SIZE)
|
|
108
|
-
throw new Error('READ_FAILED');
|
|
109
|
-
return new Uint8Array(response.slice(0, constants_1.USER_MEMORY_SIZE));
|
|
110
|
-
}
|
|
111
105
|
/**
|
|
112
106
|
* Write a 16-byte AES key to AES_KEY0 (pages 0x30–0x33).
|
|
113
107
|
* Byte order is reversed per the datasheet.
|
|
@@ -119,7 +113,7 @@ async function writeAesKey(key) {
|
|
|
119
113
|
const pageData = [key[off + 3], key[off + 2], key[off + 1], key[off + 0]];
|
|
120
114
|
await writePage(page, pageData);
|
|
121
115
|
if (react_native_1.Platform.OS === 'ios' && i < 3)
|
|
122
|
-
await new Promise(r => setTimeout(r,
|
|
116
|
+
await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_KEY_WRITE));
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
/** Write a UTF-8 nickname into the last 3 pages of user memory. */
|
|
@@ -138,19 +132,20 @@ async function writeNicknameToCard(nickname) {
|
|
|
138
132
|
const offset = i * constants_1.PAGE_SIZE;
|
|
139
133
|
await writePage(page, Array.from(buffer.slice(offset, offset + constants_1.PAGE_SIZE)));
|
|
140
134
|
if (react_native_1.Platform.OS === 'ios' && i < totalPages - 1)
|
|
141
|
-
await new Promise(r => setTimeout(r,
|
|
135
|
+
await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_NICKNAME_WRITE));
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
138
|
// ===========================================================================
|
|
145
139
|
// Auth configuration
|
|
146
140
|
// ===========================================================================
|
|
147
|
-
/** Enable read+write protection starting from USER_PAGE_START. */
|
|
141
|
+
/** Enable read+write protection and secure messaging starting from USER_PAGE_START. */
|
|
148
142
|
async function configureAuth() {
|
|
149
143
|
const cfg0 = await readPage(constants_1.PAGE_CFG0);
|
|
144
|
+
cfg0[0] = cfg0[0] | constants_1.SEC_MSG_ACT_MASK; // Enable CMAC secure messaging
|
|
150
145
|
cfg0[3] = constants_1.USER_PAGE_START;
|
|
151
146
|
await writePage(constants_1.PAGE_CFG0, cfg0.slice(0, 4));
|
|
152
147
|
if (react_native_1.Platform.OS === 'ios')
|
|
153
|
-
await new Promise(r => setTimeout(r,
|
|
148
|
+
await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_CONFIG));
|
|
154
149
|
const cfg1 = await readPage(constants_1.PAGE_CFG1);
|
|
155
150
|
cfg1[0] = cfg1[0] | 0x80; // PROT bit = 1
|
|
156
151
|
await writePage(constants_1.PAGE_CFG1, cfg1.slice(0, 4));
|
|
@@ -167,7 +162,7 @@ async function disableAuth() {
|
|
|
167
162
|
cfg0[3] = 0x3c;
|
|
168
163
|
await writePage(constants_1.PAGE_CFG0, cfg0.slice(0, 4));
|
|
169
164
|
if (react_native_1.Platform.OS === 'ios')
|
|
170
|
-
await new Promise(r => setTimeout(r,
|
|
165
|
+
await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_CONFIG));
|
|
171
166
|
const cfg1 = await readPage(constants_1.PAGE_CFG1);
|
|
172
167
|
cfg1[0] = cfg1[0] & 0x7f; // Clear PROT bit
|
|
173
168
|
await writePage(constants_1.PAGE_CFG1, cfg1.slice(0, 4));
|
|
@@ -184,31 +179,7 @@ function mnemonicToEntropyWithCRC(mnemonic) {
|
|
|
184
179
|
throw new Error('INVALID_MNEMONIC');
|
|
185
180
|
const entropyHex = bip39.mnemonicToEntropy(mnemonic);
|
|
186
181
|
const entropyBytes = (0, utils_1.hexToBytes)(entropyHex);
|
|
187
|
-
|
|
188
|
-
let typeStr;
|
|
189
|
-
switch (entropyBytes.length) {
|
|
190
|
-
case 16:
|
|
191
|
-
typeId = constants_1.MNEMONIC_TYPE_12;
|
|
192
|
-
typeStr = '12 words (128-bit)';
|
|
193
|
-
break;
|
|
194
|
-
case 20:
|
|
195
|
-
typeId = constants_1.MNEMONIC_TYPE_15;
|
|
196
|
-
typeStr = '15 words (160-bit)';
|
|
197
|
-
break;
|
|
198
|
-
case 24:
|
|
199
|
-
typeId = constants_1.MNEMONIC_TYPE_18;
|
|
200
|
-
typeStr = '18 words (192-bit)';
|
|
201
|
-
break;
|
|
202
|
-
case 28:
|
|
203
|
-
typeId = constants_1.MNEMONIC_TYPE_21;
|
|
204
|
-
typeStr = '21 words (224-bit)';
|
|
205
|
-
break;
|
|
206
|
-
case 32:
|
|
207
|
-
typeId = constants_1.MNEMONIC_TYPE_24;
|
|
208
|
-
typeStr = '24 words (256-bit)';
|
|
209
|
-
break;
|
|
210
|
-
default: throw new Error('UNSUPPORTED_MNEMONIC_LENGTH');
|
|
211
|
-
}
|
|
182
|
+
const { typeId, description: typeStr } = (0, utils_1.getMnemonicTypeByEntropyLength)(entropyBytes.length);
|
|
212
183
|
const dataBlock = new Uint8Array(1 + entropyBytes.length);
|
|
213
184
|
dataBlock[0] = typeId;
|
|
214
185
|
dataBlock.set(entropyBytes, 1);
|
|
@@ -222,46 +193,72 @@ function mnemonicToEntropyWithCRC(mnemonic) {
|
|
|
222
193
|
// Public API
|
|
223
194
|
// ===========================================================================
|
|
224
195
|
/**
|
|
225
|
-
* Initialize a
|
|
196
|
+
* Initialize a card: authenticate with the default password, then write
|
|
197
|
+
* mnemonic and set a new password.
|
|
198
|
+
*
|
|
199
|
+
* @param mnemonic BIP-39 mnemonic to write.
|
|
200
|
+
* @param newPassword Password to protect the card with after initialization.
|
|
201
|
+
* @param defaultPassword Current (factory-default) password on the card.
|
|
226
202
|
*/
|
|
227
|
-
async function initializeCard(mnemonic,
|
|
203
|
+
async function initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified) {
|
|
228
204
|
try {
|
|
229
205
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
230
206
|
}
|
|
231
207
|
catch {
|
|
232
|
-
return { code: types_1.
|
|
208
|
+
return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
|
|
233
209
|
}
|
|
210
|
+
let retryCountAfterPreDecrement;
|
|
234
211
|
try {
|
|
235
212
|
try {
|
|
236
213
|
await (0, nfc_core_1.requestNfcTech)();
|
|
237
214
|
}
|
|
238
215
|
catch {
|
|
239
216
|
(0, nfc_core_1.releaseNfcLock)();
|
|
240
|
-
return { code: types_1.
|
|
217
|
+
return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
|
|
241
218
|
}
|
|
242
219
|
try {
|
|
243
220
|
const entropyResult = mnemonicToEntropyWithCRC(mnemonic);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
221
|
+
// Decrement retry counter before auth
|
|
222
|
+
try {
|
|
223
|
+
const n = await (0, nfc_core_1.decrementRetryCountInSession)();
|
|
224
|
+
if (typeof n === 'number')
|
|
225
|
+
retryCountAfterPreDecrement = n;
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
const code = (0, types_1.errorToCode)(error);
|
|
229
|
+
if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
|
|
230
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
231
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
232
|
+
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Authenticate with factory-default password
|
|
236
|
+
const oldKey = (0, crypto_1.passwordToAesKey)(defaultPassword);
|
|
237
|
+
await (0, nfc_core_1.authenticate)(oldKey);
|
|
238
|
+
// Auth succeeded – reset retry counter
|
|
250
239
|
try {
|
|
251
240
|
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
241
|
+
retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
|
|
252
242
|
}
|
|
253
243
|
catch { /* non-fatal */ }
|
|
244
|
+
onCardIdentified?.();
|
|
245
|
+
// Write mnemonic (while still authenticated)
|
|
246
|
+
await writeUserMemory(entropyResult.data);
|
|
247
|
+
// Switch to new password
|
|
248
|
+
const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
|
|
249
|
+
await disableAuth();
|
|
250
|
+
await writeAesKey(newKey);
|
|
251
|
+
await configureAuth();
|
|
254
252
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
255
253
|
(0, nfc_core_1.releaseNfcLock)();
|
|
256
254
|
return {
|
|
257
|
-
code: types_1.
|
|
255
|
+
code: types_1.NfcStatusCode.INIT_SUCCESS,
|
|
258
256
|
success: true,
|
|
259
257
|
data: {
|
|
260
258
|
type: entropyResult.type,
|
|
261
259
|
entropyHex: entropyResult.entropyHex,
|
|
262
260
|
rawBytes: entropyResult.rawBytes,
|
|
263
261
|
crc16: entropyResult.crc16,
|
|
264
|
-
aesKeyHex,
|
|
265
262
|
retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT,
|
|
266
263
|
},
|
|
267
264
|
};
|
|
@@ -270,7 +267,11 @@ async function initializeCard(mnemonic, password, onCardIdentified) {
|
|
|
270
267
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
271
268
|
(0, nfc_core_1.releaseNfcLock)();
|
|
272
269
|
const code = (0, types_1.errorToCode)(error);
|
|
273
|
-
return {
|
|
270
|
+
return {
|
|
271
|
+
code,
|
|
272
|
+
success: false,
|
|
273
|
+
data: typeof retryCountAfterPreDecrement === 'number' ? { retryCount: retryCountAfterPreDecrement } : undefined,
|
|
274
|
+
};
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
277
|
catch (error) {
|
|
@@ -291,7 +292,7 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
291
292
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
292
293
|
}
|
|
293
294
|
catch {
|
|
294
|
-
return { code: types_1.
|
|
295
|
+
return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
|
|
295
296
|
}
|
|
296
297
|
let retryCountAfterPreDecrement;
|
|
297
298
|
try {
|
|
@@ -300,10 +301,9 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
300
301
|
}
|
|
301
302
|
catch {
|
|
302
303
|
(0, nfc_core_1.releaseNfcLock)();
|
|
303
|
-
return { code: types_1.
|
|
304
|
+
return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
|
|
304
305
|
}
|
|
305
306
|
try {
|
|
306
|
-
const entropyResult = mnemonicToEntropyWithCRC(newMnemonic);
|
|
307
307
|
try {
|
|
308
308
|
const n = await (0, nfc_core_1.decrementRetryCountInSession)();
|
|
309
309
|
if (typeof n === 'number')
|
|
@@ -311,36 +311,38 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
311
311
|
}
|
|
312
312
|
catch (error) {
|
|
313
313
|
const code = (0, types_1.errorToCode)(error);
|
|
314
|
-
if (code === types_1.
|
|
314
|
+
if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
|
|
315
315
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
316
316
|
(0, nfc_core_1.releaseNfcLock)();
|
|
317
317
|
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
318
318
|
}
|
|
319
|
+
// Communication failure — counter was not decremented, continue to auth
|
|
319
320
|
}
|
|
320
321
|
const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
|
|
321
322
|
await (0, nfc_core_1.authenticate)(oldKey);
|
|
322
323
|
try {
|
|
323
324
|
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
325
|
+
retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
|
|
324
326
|
}
|
|
325
327
|
catch { /* non-fatal */ }
|
|
326
328
|
onCardIdentified?.();
|
|
327
329
|
if (options?.precheckExistingMnemonic) {
|
|
328
330
|
try {
|
|
329
|
-
const data = await readUserMemory();
|
|
331
|
+
const data = await (0, nfc_core_1.readUserMemory)();
|
|
330
332
|
const decoded = (0, utils_1.validateMnemonicPayload)(data);
|
|
331
333
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
332
334
|
(0, nfc_core_1.releaseNfcLock)();
|
|
333
335
|
return {
|
|
334
|
-
code: types_1.
|
|
336
|
+
code: types_1.NfcStatusCode.PRECHECK_HAS_BACKUP,
|
|
335
337
|
success: true,
|
|
336
338
|
data: { type: decoded.type, retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
|
|
337
339
|
};
|
|
338
340
|
}
|
|
339
341
|
catch (e) {
|
|
340
342
|
const c = (0, types_1.errorToCode)(e);
|
|
341
|
-
if (c === types_1.
|
|
342
|
-
c === types_1.
|
|
343
|
-
c === types_1.
|
|
343
|
+
if (c === types_1.NfcStatusCode.CHECK_EMPTY ||
|
|
344
|
+
c === types_1.NfcStatusCode.CRC16_CHECK_FAILED ||
|
|
345
|
+
c === types_1.NfcStatusCode.INVALID_CARD_DATA) {
|
|
344
346
|
// No valid mnemonic on card — fall through to write
|
|
345
347
|
}
|
|
346
348
|
else {
|
|
@@ -350,8 +352,9 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
350
352
|
}
|
|
351
353
|
}
|
|
352
354
|
}
|
|
353
|
-
|
|
355
|
+
const entropyResult = mnemonicToEntropyWithCRC(newMnemonic);
|
|
354
356
|
await writeUserMemory(entropyResult.data);
|
|
357
|
+
await disableAuth();
|
|
355
358
|
const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
|
|
356
359
|
const aesKeyHex = (0, utils_1.bytesToHex)(newKey);
|
|
357
360
|
await writeAesKey(newKey);
|
|
@@ -359,7 +362,7 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
|
|
|
359
362
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
360
363
|
(0, nfc_core_1.releaseNfcLock)();
|
|
361
364
|
return {
|
|
362
|
-
code: types_1.
|
|
365
|
+
code: types_1.NfcStatusCode.WRITE_SUCCESS,
|
|
363
366
|
success: true,
|
|
364
367
|
data: {
|
|
365
368
|
type: entropyResult.type,
|
|
@@ -394,7 +397,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
|
|
|
394
397
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
395
398
|
}
|
|
396
399
|
catch {
|
|
397
|
-
return { code: types_1.
|
|
400
|
+
return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
|
|
398
401
|
}
|
|
399
402
|
let retryCountAfterPreDecrement;
|
|
400
403
|
try {
|
|
@@ -403,7 +406,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
|
|
|
403
406
|
}
|
|
404
407
|
catch {
|
|
405
408
|
(0, nfc_core_1.releaseNfcLock)();
|
|
406
|
-
return { code: types_1.
|
|
409
|
+
return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
|
|
407
410
|
}
|
|
408
411
|
try {
|
|
409
412
|
try {
|
|
@@ -413,16 +416,18 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
|
|
|
413
416
|
}
|
|
414
417
|
catch (error) {
|
|
415
418
|
const code = (0, types_1.errorToCode)(error);
|
|
416
|
-
if (code === types_1.
|
|
419
|
+
if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
|
|
417
420
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
418
421
|
(0, nfc_core_1.releaseNfcLock)();
|
|
419
422
|
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
420
423
|
}
|
|
424
|
+
// Communication failure — counter was not decremented, continue to auth
|
|
421
425
|
}
|
|
422
426
|
const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
|
|
423
427
|
await (0, nfc_core_1.authenticate)(oldKey);
|
|
424
428
|
try {
|
|
425
429
|
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
430
|
+
retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
|
|
426
431
|
}
|
|
427
432
|
catch { /* non-fatal */ }
|
|
428
433
|
onCardIdentified?.();
|
|
@@ -434,7 +439,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
|
|
|
434
439
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
435
440
|
(0, nfc_core_1.releaseNfcLock)();
|
|
436
441
|
return {
|
|
437
|
-
code: types_1.
|
|
442
|
+
code: types_1.NfcStatusCode.UPDATE_PASSWORD_SUCCESS,
|
|
438
443
|
success: true,
|
|
439
444
|
data: { aesKeyHex, retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
|
|
440
445
|
};
|
|
@@ -461,7 +466,7 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
|
|
|
461
466
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
462
467
|
}
|
|
463
468
|
catch {
|
|
464
|
-
return { code: types_1.
|
|
469
|
+
return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
|
|
465
470
|
}
|
|
466
471
|
try {
|
|
467
472
|
try {
|
|
@@ -469,11 +474,28 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
|
|
|
469
474
|
}
|
|
470
475
|
catch {
|
|
471
476
|
(0, nfc_core_1.releaseNfcLock)();
|
|
472
|
-
return { code: types_1.
|
|
477
|
+
return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
|
|
473
478
|
}
|
|
474
479
|
try {
|
|
480
|
+
// Pre-decrement retry counter before auth
|
|
481
|
+
try {
|
|
482
|
+
await (0, nfc_core_1.decrementRetryCountInSession)();
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
const code = (0, types_1.errorToCode)(error);
|
|
486
|
+
if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
|
|
487
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
488
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
489
|
+
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
475
492
|
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
476
493
|
await (0, nfc_core_1.authenticate)(aesKey);
|
|
494
|
+
// Auth succeeded – reset retry counter
|
|
495
|
+
try {
|
|
496
|
+
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
497
|
+
}
|
|
498
|
+
catch { /* non-fatal */ }
|
|
477
499
|
onCardIdentified?.();
|
|
478
500
|
await disableAuth();
|
|
479
501
|
await writeNicknameToCard(nickname);
|
|
@@ -481,7 +503,7 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
|
|
|
481
503
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
482
504
|
(0, nfc_core_1.releaseNfcLock)();
|
|
483
505
|
return {
|
|
484
|
-
code: types_1.
|
|
506
|
+
code: types_1.NfcStatusCode.WRITE_NICKNAME_SUCCESS,
|
|
485
507
|
success: true,
|
|
486
508
|
};
|
|
487
509
|
}
|
|
@@ -498,15 +520,17 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
|
|
|
498
520
|
}
|
|
499
521
|
}
|
|
500
522
|
/**
|
|
501
|
-
* Reset card: wipe
|
|
523
|
+
* Reset card: wipe mnemonic data and set a new password.
|
|
502
524
|
* @param password – current card password (required if protection is enabled).
|
|
525
|
+
* @param newPassword – password to set after reset.
|
|
526
|
+
* @param onCardIdentified – callback when card is identified.
|
|
503
527
|
*/
|
|
504
|
-
async function resetCard(password, onCardIdentified) {
|
|
528
|
+
async function resetCard(password, newPassword, onCardIdentified) {
|
|
505
529
|
try {
|
|
506
530
|
await (0, nfc_core_1.acquireNfcLock)();
|
|
507
531
|
}
|
|
508
532
|
catch {
|
|
509
|
-
return { code: types_1.
|
|
533
|
+
return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
|
|
510
534
|
}
|
|
511
535
|
let retryCountAfterPreDecrement;
|
|
512
536
|
try {
|
|
@@ -515,7 +539,7 @@ async function resetCard(password, onCardIdentified) {
|
|
|
515
539
|
}
|
|
516
540
|
catch {
|
|
517
541
|
(0, nfc_core_1.releaseNfcLock)();
|
|
518
|
-
return { code: types_1.
|
|
542
|
+
return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
|
|
519
543
|
}
|
|
520
544
|
try {
|
|
521
545
|
if (password) {
|
|
@@ -526,17 +550,19 @@ async function resetCard(password, onCardIdentified) {
|
|
|
526
550
|
}
|
|
527
551
|
catch (error) {
|
|
528
552
|
const code = (0, types_1.errorToCode)(error);
|
|
529
|
-
if (code === types_1.
|
|
553
|
+
if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
|
|
530
554
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
531
555
|
(0, nfc_core_1.releaseNfcLock)();
|
|
532
556
|
return (0, types_1.nfcResultRetryCountExhausted)();
|
|
533
557
|
}
|
|
558
|
+
// Communication failure — counter was not decremented, continue to auth
|
|
534
559
|
}
|
|
535
560
|
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
536
561
|
try {
|
|
537
562
|
await (0, nfc_core_1.authenticate)(aesKey);
|
|
538
563
|
try {
|
|
539
564
|
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
565
|
+
retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
|
|
540
566
|
}
|
|
541
567
|
catch { /* non-fatal */ }
|
|
542
568
|
}
|
|
@@ -556,31 +582,27 @@ async function resetCard(password, onCardIdentified) {
|
|
|
556
582
|
}
|
|
557
583
|
catch { /* non-fatal */ }
|
|
558
584
|
}
|
|
559
|
-
await disableAuth();
|
|
560
585
|
}
|
|
561
586
|
else {
|
|
562
587
|
try {
|
|
563
|
-
await
|
|
588
|
+
await (0, nfc_core_1.authenticate)((0, crypto_1.passwordToAesKey)(newPassword));
|
|
564
589
|
}
|
|
565
590
|
catch {
|
|
566
591
|
throw new Error('AUTH_WRONG_PASSWORD');
|
|
567
592
|
}
|
|
568
593
|
onCardIdentified?.();
|
|
569
594
|
}
|
|
570
|
-
// Wipe
|
|
571
|
-
await writeUserMemory(new Uint8Array(constants_1.
|
|
572
|
-
// Set
|
|
573
|
-
const
|
|
574
|
-
await
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
await disableAuth();
|
|
578
|
-
}
|
|
579
|
-
catch { /* may already be off */ }
|
|
595
|
+
// Wipe mnemonic data (nickname is preserved)
|
|
596
|
+
await writeUserMemory(new Uint8Array(constants_1.MNEMONIC_MEMORY_SIZE));
|
|
597
|
+
// Set new password and keep protection enabled
|
|
598
|
+
const resetKey = (0, crypto_1.passwordToAesKey)(newPassword);
|
|
599
|
+
await disableAuth();
|
|
600
|
+
await writeAesKey(resetKey);
|
|
601
|
+
await configureAuth();
|
|
580
602
|
await (0, nfc_core_1.releaseNfcTech)();
|
|
581
603
|
(0, nfc_core_1.releaseNfcLock)();
|
|
582
604
|
return {
|
|
583
|
-
code: types_1.
|
|
605
|
+
code: types_1.NfcStatusCode.RESET_SUCCESS,
|
|
584
606
|
success: true,
|
|
585
607
|
data: { retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
|
|
586
608
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ukeyfe/react-native-nfc-litecard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "NFC read/write for MIFARE Ultralight AES (LiteCard mnemonic storage)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
30
|
"README.md",
|
|
31
|
-
"README.
|
|
31
|
+
"README.zh.md"
|
|
32
32
|
]
|
|
33
33
|
}
|