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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukeyfe/react-native-nfc-litecard",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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",
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/crypto-js": "^4",
18
+ "tsup": "^8.5.1",
18
19
  "typescript": "^5.0.0"
19
20
  },
20
21
  "peerDependencies": {
@@ -22,7 +23,8 @@
22
23
  "react-native-nfc-manager": ">=3.0.0"
23
24
  },
24
25
  "scripts": {
25
- "build": "tsc",
26
+ "build": "tsup",
27
+ "build:dev": "tsup --env.NODE_ENV development",
26
28
  "prepublishOnly": "npm run build"
27
29
  },
28
30
  "files": [
@@ -1,87 +0,0 @@
1
- /**
2
- * Shared constants for MIFARE Ultralight AES (MF0AES(H)20) NFC operations.
3
- */
4
- /** READ command - reads 4 pages (16 bytes) starting from the specified page */
5
- export declare const CMD_READ = 48;
6
- /** WRITE command - writes a single page (4 bytes) */
7
- export declare const CMD_WRITE = 162;
8
- /** FAST_READ command - reads a range of pages in one shot */
9
- export declare const CMD_FAST_READ = 58;
10
- /** AES authentication part 1 */
11
- export declare const CMD_AUTH_PART1 = 26;
12
- /** AES authentication part 2 */
13
- export declare const CMD_AUTH_PART2 = 175;
14
- /** GET_VERSION command - retrieve product version info */
15
- export declare const CMD_GET_VERSION = 96;
16
- /** READ_SIG command - read ECC originality signature */
17
- export declare const CMD_READ_SIG = 60;
18
- /** Data protection key slot (Key0) */
19
- export declare const KEY_NO_DATA_PROT = 0;
20
- /** Bytes per page */
21
- export declare const PAGE_SIZE = 4;
22
- /** User memory start page (page 8) */
23
- export declare const USER_PAGE_START = 8;
24
- /** User memory end page (page 39) */
25
- export declare const USER_PAGE_END = 39;
26
- /** Card info start page – first usable page in user memory */
27
- export declare const USER_CARD_INFO_PAGE_START = 4;
28
- /** Card info end page (4 pages, 16 bytes) */
29
- export declare const USER_CARD_INFO_PAGE_END = 7;
30
- /** Configuration page 0 – contains AUTH0 */
31
- export declare const PAGE_CFG0 = 41;
32
- /** Configuration page 1 – contains PROT bit */
33
- export declare const PAGE_CFG1 = 42;
34
- /** AES Key0 start page */
35
- export declare const PAGE_AES_KEY0_START = 48;
36
- /** Total user memory: (0x27 - 0x08 + 1) * 4 = 128 bytes */
37
- export declare const USER_MEMORY_SIZE: number;
38
- /** Card info area size: (0x07 - 0x04 + 1) * 4 = 16 bytes */
39
- export declare const USER_CARD_INFO_SIZE: number;
40
- /** 12-word mnemonic (128-bit entropy, 16 bytes) */
41
- export declare const MNEMONIC_TYPE_12 = 1;
42
- /** 15-word mnemonic (160-bit entropy, 20 bytes) */
43
- export declare const MNEMONIC_TYPE_15 = 2;
44
- /** 18-word mnemonic (192-bit entropy, 24 bytes) */
45
- export declare const MNEMONIC_TYPE_18 = 3;
46
- /** 21-word mnemonic (224-bit entropy, 28 bytes) */
47
- export declare const MNEMONIC_TYPE_21 = 4;
48
- /** 24-word mnemonic (256-bit entropy, 32 bytes) */
49
- export declare const MNEMONIC_TYPE_24 = 5;
50
- /** Mnemonic data end page (stops before nickname area) */
51
- export declare const MNEMONIC_PAGE_END: number;
52
- /** Mnemonic data size: (0x24 - 0x08 + 1) * 4 = 116 bytes */
53
- export declare const MNEMONIC_MEMORY_SIZE: number;
54
- /** Nickname start page */
55
- export declare const USER_NICKNAME_PAGE_START: number;
56
- /** Nickname end page */
57
- export declare const USER_NICKNAME_PAGE_END = 39;
58
- /** Max nickname length in bytes (3 pages × 4 bytes) */
59
- export declare const USER_NICKNAME_MAX_LENGTH = 12;
60
- /** Retry counter page (one page before mnemonic data) */
61
- export declare const RETRY_COUNTER_PAGE: number;
62
- /** Byte offset of the counter within the page (last byte) */
63
- export declare const RETRY_COUNTER_OFFSET: number;
64
- /** Default retry limit, restored after a successful authentication */
65
- export declare const DEFAULT_PIN_RETRY_COUNT = 10;
66
- /** SEC_MSG_ACT bit mask in CFG0 byte 0 (bit 1) */
67
- export declare const SEC_MSG_ACT_MASK = 2;
68
- export declare const DELAY: {
69
- /** Delay after requestNfcTech on Android */
70
- readonly ANDROID_POST_TECH: 300;
71
- /** Delay after releaseNfcTech (normal) */
72
- readonly IOS_POST_RELEASE: 100;
73
- readonly ANDROID_POST_RELEASE: 800;
74
- /** Delay after releaseNfcTech (forced, e.g. after timeout) */
75
- readonly IOS_POST_RELEASE_FORCED: 300;
76
- readonly ANDROID_POST_RELEASE_FORCED: 1000;
77
- /** Delay between page writes on iOS */
78
- readonly IOS_PAGE_WRITE: 20;
79
- /** Delay between AES key page writes on iOS */
80
- readonly IOS_KEY_WRITE: 10;
81
- /** Delay between nickname page writes on iOS */
82
- readonly IOS_NICKNAME_WRITE: 100;
83
- /** Delay between config operations on iOS */
84
- readonly IOS_CONFIG: 80;
85
- /** Keep-alive frequency (every N pages) during writes */
86
- readonly KEEPALIVE_FREQ: 4;
87
- };
package/dist/constants.js DELETED
@@ -1,117 +0,0 @@
1
- "use strict";
2
- /**
3
- * Shared constants for MIFARE Ultralight AES (MF0AES(H)20) NFC operations.
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DELAY = exports.SEC_MSG_ACT_MASK = exports.DEFAULT_PIN_RETRY_COUNT = exports.RETRY_COUNTER_OFFSET = exports.RETRY_COUNTER_PAGE = exports.USER_NICKNAME_MAX_LENGTH = exports.USER_NICKNAME_PAGE_END = exports.USER_NICKNAME_PAGE_START = exports.MNEMONIC_MEMORY_SIZE = exports.MNEMONIC_PAGE_END = exports.MNEMONIC_TYPE_24 = exports.MNEMONIC_TYPE_21 = exports.MNEMONIC_TYPE_18 = exports.MNEMONIC_TYPE_15 = exports.MNEMONIC_TYPE_12 = exports.USER_CARD_INFO_SIZE = exports.USER_MEMORY_SIZE = exports.PAGE_AES_KEY0_START = exports.PAGE_CFG1 = exports.PAGE_CFG0 = exports.USER_CARD_INFO_PAGE_END = exports.USER_CARD_INFO_PAGE_START = exports.USER_PAGE_END = exports.USER_PAGE_START = exports.PAGE_SIZE = exports.KEY_NO_DATA_PROT = exports.CMD_READ_SIG = exports.CMD_GET_VERSION = exports.CMD_AUTH_PART2 = exports.CMD_AUTH_PART1 = exports.CMD_FAST_READ = exports.CMD_WRITE = exports.CMD_READ = void 0;
7
- // ---------------------------------------------------------------------------
8
- // NFC command codes
9
- // ---------------------------------------------------------------------------
10
- /** READ command - reads 4 pages (16 bytes) starting from the specified page */
11
- exports.CMD_READ = 0x30;
12
- /** WRITE command - writes a single page (4 bytes) */
13
- exports.CMD_WRITE = 0xa2;
14
- /** FAST_READ command - reads a range of pages in one shot */
15
- exports.CMD_FAST_READ = 0x3a;
16
- /** AES authentication part 1 */
17
- exports.CMD_AUTH_PART1 = 0x1a;
18
- /** AES authentication part 2 */
19
- exports.CMD_AUTH_PART2 = 0xaf;
20
- /** GET_VERSION command - retrieve product version info */
21
- exports.CMD_GET_VERSION = 0x60;
22
- /** READ_SIG command - read ECC originality signature */
23
- exports.CMD_READ_SIG = 0x3c;
24
- /** Data protection key slot (Key0) */
25
- exports.KEY_NO_DATA_PROT = 0x00;
26
- // ---------------------------------------------------------------------------
27
- // Page addresses
28
- // ---------------------------------------------------------------------------
29
- /** Bytes per page */
30
- exports.PAGE_SIZE = 4;
31
- /** User memory start page (page 8) */
32
- exports.USER_PAGE_START = 0x08;
33
- /** User memory end page (page 39) */
34
- exports.USER_PAGE_END = 0x27;
35
- /** Card info start page – first usable page in user memory */
36
- exports.USER_CARD_INFO_PAGE_START = 0x04;
37
- /** Card info end page (4 pages, 16 bytes) */
38
- exports.USER_CARD_INFO_PAGE_END = 0x07;
39
- /** Configuration page 0 – contains AUTH0 */
40
- exports.PAGE_CFG0 = 0x29;
41
- /** Configuration page 1 – contains PROT bit */
42
- exports.PAGE_CFG1 = 0x2a;
43
- /** AES Key0 start page */
44
- exports.PAGE_AES_KEY0_START = 0x30;
45
- // ---------------------------------------------------------------------------
46
- // Computed sizes
47
- // ---------------------------------------------------------------------------
48
- /** Total user memory: (0x27 - 0x08 + 1) * 4 = 128 bytes */
49
- exports.USER_MEMORY_SIZE = (exports.USER_PAGE_END - exports.USER_PAGE_START + 1) * exports.PAGE_SIZE;
50
- /** Card info area size: (0x07 - 0x04 + 1) * 4 = 16 bytes */
51
- exports.USER_CARD_INFO_SIZE = (exports.USER_CARD_INFO_PAGE_END - exports.USER_CARD_INFO_PAGE_START + 1) * exports.PAGE_SIZE;
52
- // ---------------------------------------------------------------------------
53
- // Mnemonic type identifiers
54
- // ---------------------------------------------------------------------------
55
- /** 12-word mnemonic (128-bit entropy, 16 bytes) */
56
- exports.MNEMONIC_TYPE_12 = 0x01;
57
- /** 15-word mnemonic (160-bit entropy, 20 bytes) */
58
- exports.MNEMONIC_TYPE_15 = 0x02;
59
- /** 18-word mnemonic (192-bit entropy, 24 bytes) */
60
- exports.MNEMONIC_TYPE_18 = 0x03;
61
- /** 21-word mnemonic (224-bit entropy, 28 bytes) */
62
- exports.MNEMONIC_TYPE_21 = 0x04;
63
- /** 24-word mnemonic (256-bit entropy, 32 bytes) */
64
- exports.MNEMONIC_TYPE_24 = 0x05;
65
- // ---------------------------------------------------------------------------
66
- // Mnemonic data area (excludes nickname pages)
67
- // ---------------------------------------------------------------------------
68
- /** Mnemonic data end page (stops before nickname area) */
69
- exports.MNEMONIC_PAGE_END = exports.USER_PAGE_END - 3; // 0x24
70
- /** Mnemonic data size: (0x24 - 0x08 + 1) * 4 = 116 bytes */
71
- exports.MNEMONIC_MEMORY_SIZE = (exports.MNEMONIC_PAGE_END - exports.USER_PAGE_START + 1) * exports.PAGE_SIZE;
72
- // ---------------------------------------------------------------------------
73
- // User nickname area (last 3 pages of user memory)
74
- // ---------------------------------------------------------------------------
75
- /** Nickname start page */
76
- exports.USER_NICKNAME_PAGE_START = exports.USER_PAGE_END - 2; // 0x25
77
- /** Nickname end page */
78
- exports.USER_NICKNAME_PAGE_END = exports.USER_PAGE_END; // 0x27
79
- /** Max nickname length in bytes (3 pages × 4 bytes) */
80
- exports.USER_NICKNAME_MAX_LENGTH = 12;
81
- // ---------------------------------------------------------------------------
82
- // PIN retry counter
83
- // ---------------------------------------------------------------------------
84
- /** Retry counter page (one page before mnemonic data) */
85
- exports.RETRY_COUNTER_PAGE = exports.USER_PAGE_START - 1; // 0x07
86
- /** Byte offset of the counter within the page (last byte) */
87
- exports.RETRY_COUNTER_OFFSET = exports.PAGE_SIZE - 1;
88
- /** Default retry limit, restored after a successful authentication */
89
- exports.DEFAULT_PIN_RETRY_COUNT = 10;
90
- // ---------------------------------------------------------------------------
91
- // Secure messaging (CMAC)
92
- // ---------------------------------------------------------------------------
93
- /** SEC_MSG_ACT bit mask in CFG0 byte 0 (bit 1) */
94
- exports.SEC_MSG_ACT_MASK = 0x02;
95
- // ---------------------------------------------------------------------------
96
- // Platform-specific delays (ms)
97
- // ---------------------------------------------------------------------------
98
- exports.DELAY = {
99
- /** Delay after requestNfcTech on Android */
100
- ANDROID_POST_TECH: 300,
101
- /** Delay after releaseNfcTech (normal) */
102
- IOS_POST_RELEASE: 100,
103
- ANDROID_POST_RELEASE: 800,
104
- /** Delay after releaseNfcTech (forced, e.g. after timeout) */
105
- IOS_POST_RELEASE_FORCED: 300,
106
- ANDROID_POST_RELEASE_FORCED: 1000,
107
- /** Delay between page writes on iOS */
108
- IOS_PAGE_WRITE: 20,
109
- /** Delay between AES key page writes on iOS */
110
- IOS_KEY_WRITE: 10,
111
- /** Delay between nickname page writes on iOS */
112
- IOS_NICKNAME_WRITE: 100,
113
- /** Delay between config operations on iOS */
114
- IOS_CONFIG: 80,
115
- /** Keep-alive frequency (every N pages) during writes */
116
- KEEPALIVE_FREQ: 4,
117
- };
package/dist/crypto.d.ts DELETED
@@ -1,55 +0,0 @@
1
- /**
2
- * Cryptographic helpers for MIFARE Ultralight AES authentication.
3
- *
4
- * - AES-128 CBC encrypt / decrypt (via crypto-js)
5
- * - Password → AES key derivation (SHA-256, first 16 bytes)
6
- * - Cryptographically-secure 16-byte random generation
7
- * - Byte-array comparison & rotation
8
- */
9
- /**
10
- * Derive a 16-byte AES-128 key from a password string.
11
- * SHA-256 the password, then take the first 16 bytes.
12
- */
13
- export declare function passwordToAesKey(password: string): Uint8Array;
14
- /** AES-128 CBC decrypt (no padding). Both key, data, iv must be 16 bytes. */
15
- export declare function aesDecrypt(key: Uint8Array, data: Uint8Array, iv: Uint8Array): Uint8Array;
16
- /** AES-128 CBC encrypt (no padding). Both key, data, iv must be 16 bytes. */
17
- export declare function aesEncrypt(key: Uint8Array, data: Uint8Array, iv: Uint8Array): Uint8Array;
18
- /**
19
- * Left-rotate a byte array by 8 bits (1 byte).
20
- * e.g. [1,2,3,4] → [2,3,4,1]
21
- * Used in AES 3-pass auth to compute RndB' and verify RndA'.
22
- */
23
- export declare function rotateLeft8(data: Uint8Array): Uint8Array;
24
- /** Compare two Uint8Arrays for equality. */
25
- export declare function arraysEqual(a: Uint8Array, b: Uint8Array): boolean;
26
- /** AES-128 ECB encrypt a single 16-byte block. */
27
- export declare function aesEcbEncrypt(key: Uint8Array, data: Uint8Array): Uint8Array;
28
- /**
29
- * Generate CMAC subkeys K1 and K2 (NIST 800-38B §6.1).
30
- */
31
- export declare function generateCmacSubkeys(key: Uint8Array): {
32
- k1: Uint8Array;
33
- k2: Uint8Array;
34
- };
35
- /**
36
- * Compute AES-CMAC (NIST 800-38B §6.2).
37
- * Returns full 16-byte MAC.
38
- */
39
- export declare function aesCmac(key: Uint8Array, message: Uint8Array): Uint8Array;
40
- /**
41
- * Derive SesAuthMACKey from authentication key and random numbers.
42
- * (MF0AES(H)20 §8.8.1)
43
- *
44
- * SV2 = 5Ah||A5h||00h||01h||00h||80h||RndA[15..14]||(RndA[13..8] XOR RndB[15..10])||RndB[9..0]||RndA[7..0]
45
- * SesAuthMACKey = AES-CMAC(Kx, SV2)
46
- */
47
- export declare function deriveSessionKey(authKey: Uint8Array, rndA: Uint8Array, rndB: Uint8Array): Uint8Array;
48
- /**
49
- * Generate a 16-byte cryptographically-secure random value.
50
- *
51
- * Uses `crypto.getRandomValues` when available (Hermes ≥ 0.72, or
52
- * the `react-native-get-random-values` polyfill). Falls back to
53
- * `Math.random()` with a console warning if the API is missing.
54
- */
55
- export declare function generateRandom16(): Uint8Array;
package/dist/crypto.js DELETED
@@ -1,271 +0,0 @@
1
- "use strict";
2
- /**
3
- * Cryptographic helpers for MIFARE Ultralight AES authentication.
4
- *
5
- * - AES-128 CBC encrypt / decrypt (via crypto-js)
6
- * - Password → AES key derivation (SHA-256, first 16 bytes)
7
- * - Cryptographically-secure 16-byte random generation
8
- * - Byte-array comparison & rotation
9
- */
10
- var __importDefault = (this && this.__importDefault) || function (mod) {
11
- return (mod && mod.__esModule) ? mod : { "default": mod };
12
- };
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.passwordToAesKey = passwordToAesKey;
15
- exports.aesDecrypt = aesDecrypt;
16
- exports.aesEncrypt = aesEncrypt;
17
- exports.rotateLeft8 = rotateLeft8;
18
- exports.arraysEqual = arraysEqual;
19
- exports.aesEcbEncrypt = aesEcbEncrypt;
20
- exports.generateCmacSubkeys = generateCmacSubkeys;
21
- exports.aesCmac = aesCmac;
22
- exports.deriveSessionKey = deriveSessionKey;
23
- exports.generateRandom16 = generateRandom16;
24
- const crypto_js_1 = __importDefault(require("crypto-js"));
25
- // ---------------------------------------------------------------------------
26
- // Key derivation
27
- // ---------------------------------------------------------------------------
28
- /**
29
- * Derive a 16-byte AES-128 key from a password string.
30
- * SHA-256 the password, then take the first 16 bytes.
31
- */
32
- function passwordToAesKey(password) {
33
- const safePassword = password || '';
34
- const hash = crypto_js_1.default.SHA256(String(safePassword)).toString();
35
- const key = new Uint8Array(16);
36
- for (let i = 0; i < 16; i++) {
37
- key[i] = parseInt(hash.substring(i * 2, i * 2 + 2), 16);
38
- }
39
- return key;
40
- }
41
- // ---------------------------------------------------------------------------
42
- // AES-128 CBC
43
- // ---------------------------------------------------------------------------
44
- /** AES-128 CBC decrypt (no padding). Both key, data, iv must be 16 bytes. */
45
- function aesDecrypt(key, data, iv) {
46
- const keyWords = crypto_js_1.default.lib.WordArray.create(key);
47
- const ivWords = crypto_js_1.default.lib.WordArray.create(iv);
48
- const dataWords = crypto_js_1.default.lib.WordArray.create(data);
49
- const decrypted = crypto_js_1.default.AES.decrypt({ ciphertext: dataWords }, keyWords, { iv: ivWords, mode: crypto_js_1.default.mode.CBC, padding: crypto_js_1.default.pad.NoPadding });
50
- const hex = decrypted.toString();
51
- const result = new Uint8Array(16);
52
- for (let i = 0; i < 16; i++) {
53
- result[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
54
- }
55
- return result;
56
- }
57
- /** AES-128 CBC encrypt (no padding). Both key, data, iv must be 16 bytes. */
58
- function aesEncrypt(key, data, iv) {
59
- const keyWords = crypto_js_1.default.lib.WordArray.create(key);
60
- const ivWords = crypto_js_1.default.lib.WordArray.create(iv);
61
- const dataWords = crypto_js_1.default.lib.WordArray.create(data);
62
- const encrypted = crypto_js_1.default.AES.encrypt(dataWords, keyWords, {
63
- iv: ivWords,
64
- mode: crypto_js_1.default.mode.CBC,
65
- padding: crypto_js_1.default.pad.NoPadding,
66
- });
67
- const hex = encrypted.ciphertext.toString();
68
- const result = new Uint8Array(hex.length / 2);
69
- for (let i = 0; i < result.length; i++) {
70
- result[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
71
- }
72
- return result;
73
- }
74
- // ---------------------------------------------------------------------------
75
- // Byte-array helpers
76
- // ---------------------------------------------------------------------------
77
- /**
78
- * Left-rotate a byte array by 8 bits (1 byte).
79
- * e.g. [1,2,3,4] → [2,3,4,1]
80
- * Used in AES 3-pass auth to compute RndB' and verify RndA'.
81
- */
82
- function rotateLeft8(data) {
83
- const result = new Uint8Array(data.length);
84
- for (let i = 0; i < data.length - 1; i++) {
85
- result[i] = data[i + 1];
86
- }
87
- result[data.length - 1] = data[0];
88
- return result;
89
- }
90
- /** Compare two Uint8Arrays for equality. */
91
- function arraysEqual(a, b) {
92
- if (a.length !== b.length)
93
- return false;
94
- for (let i = 0; i < a.length; i++) {
95
- if (a[i] !== b[i])
96
- return false;
97
- }
98
- return true;
99
- }
100
- // ---------------------------------------------------------------------------
101
- // AES-128 ECB (single block, for CMAC subkey generation)
102
- // ---------------------------------------------------------------------------
103
- /** AES-128 ECB encrypt a single 16-byte block. */
104
- function aesEcbEncrypt(key, data) {
105
- const keyWords = crypto_js_1.default.lib.WordArray.create(key);
106
- const dataWords = crypto_js_1.default.lib.WordArray.create(data);
107
- const encrypted = crypto_js_1.default.AES.encrypt(dataWords, keyWords, {
108
- mode: crypto_js_1.default.mode.ECB,
109
- padding: crypto_js_1.default.pad.NoPadding,
110
- });
111
- const hex = encrypted.ciphertext.toString();
112
- const result = new Uint8Array(16);
113
- for (let i = 0; i < 16; i++) {
114
- result[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
115
- }
116
- return result;
117
- }
118
- // ---------------------------------------------------------------------------
119
- // AES-CMAC (NIST SP 800-38B)
120
- // ---------------------------------------------------------------------------
121
- const CONST_RB = 0x87; // constant for 128-bit block cipher
122
- /** Left-shift a 16-byte array by 1 bit. */
123
- function shiftLeft1(data) {
124
- const result = new Uint8Array(16);
125
- let carry = 0;
126
- for (let i = 15; i >= 0; i--) {
127
- const shifted = (data[i] << 1) | carry;
128
- result[i] = shifted & 0xff;
129
- carry = (data[i] & 0x80) ? 1 : 0;
130
- }
131
- return result;
132
- }
133
- /** XOR two 16-byte arrays. */
134
- function xor16(a, b) {
135
- const result = new Uint8Array(16);
136
- for (let i = 0; i < 16; i++) {
137
- result[i] = a[i] ^ b[i];
138
- }
139
- return result;
140
- }
141
- /**
142
- * Generate CMAC subkeys K1 and K2 (NIST 800-38B §6.1).
143
- */
144
- function generateCmacSubkeys(key) {
145
- const zero = new Uint8Array(16);
146
- const L = aesEcbEncrypt(key, zero);
147
- let k1 = shiftLeft1(L);
148
- if (L[0] & 0x80) {
149
- k1[15] ^= CONST_RB;
150
- }
151
- let k2 = shiftLeft1(k1);
152
- if (k1[0] & 0x80) {
153
- k2[15] ^= CONST_RB;
154
- }
155
- return { k1, k2 };
156
- }
157
- /**
158
- * Compute AES-CMAC (NIST 800-38B §6.2).
159
- * Returns full 16-byte MAC.
160
- */
161
- function aesCmac(key, message) {
162
- const { k1, k2 } = generateCmacSubkeys(key);
163
- const n = message.length === 0 ? 1 : Math.ceil(message.length / 16);
164
- const lastBlockComplete = message.length > 0 && message.length % 16 === 0;
165
- // Prepare last block
166
- const lastBlock = new Uint8Array(16);
167
- if (lastBlockComplete) {
168
- // Complete: XOR with K1
169
- const offset = (n - 1) * 16;
170
- for (let i = 0; i < 16; i++) {
171
- lastBlock[i] = message[offset + i] ^ k1[i];
172
- }
173
- }
174
- else {
175
- // Incomplete: pad with 10* then XOR with K2
176
- const offset = (n - 1) * 16;
177
- const remaining = message.length - offset;
178
- for (let i = 0; i < 16; i++) {
179
- if (i < remaining) {
180
- lastBlock[i] = message[offset + i];
181
- }
182
- else if (i === remaining) {
183
- lastBlock[i] = 0x80;
184
- }
185
- else {
186
- lastBlock[i] = 0x00;
187
- }
188
- }
189
- for (let i = 0; i < 16; i++) {
190
- lastBlock[i] ^= k2[i];
191
- }
192
- }
193
- // CBC-MAC
194
- const iv0 = new Uint8Array(16);
195
- let x = iv0;
196
- for (let i = 0; i < n - 1; i++) {
197
- const block = new Uint8Array(message.slice(i * 16, (i + 1) * 16));
198
- const y = xor16(x, block);
199
- x = aesEcbEncrypt(key, y);
200
- }
201
- const y = xor16(x, lastBlock);
202
- return aesEcbEncrypt(key, y);
203
- }
204
- /**
205
- * Derive SesAuthMACKey from authentication key and random numbers.
206
- * (MF0AES(H)20 §8.8.1)
207
- *
208
- * SV2 = 5Ah||A5h||00h||01h||00h||80h||RndA[15..14]||(RndA[13..8] XOR RndB[15..10])||RndB[9..0]||RndA[7..0]
209
- * SesAuthMACKey = AES-CMAC(Kx, SV2)
210
- */
211
- function deriveSessionKey(authKey, rndA, rndB) {
212
- const sv2 = new Uint8Array(32);
213
- // Header: 5Ah A5h 00h 01h 00h 80h
214
- sv2[0] = 0x5a;
215
- sv2[1] = 0xa5;
216
- sv2[2] = 0x00;
217
- sv2[3] = 0x01;
218
- sv2[4] = 0x00;
219
- sv2[5] = 0x80;
220
- // RndA[15..14] (2 bytes)
221
- sv2[6] = rndA[15];
222
- sv2[7] = rndA[14];
223
- // RndA[13..8] XOR RndB[15..10] (6 bytes)
224
- sv2[8] = rndA[13] ^ rndB[15];
225
- sv2[9] = rndA[12] ^ rndB[14];
226
- sv2[10] = rndA[11] ^ rndB[13];
227
- sv2[11] = rndA[10] ^ rndB[12];
228
- sv2[12] = rndA[9] ^ rndB[11];
229
- sv2[13] = rndA[8] ^ rndB[10];
230
- // RndB[9..0] (10 bytes)
231
- sv2[14] = rndB[9];
232
- sv2[15] = rndB[8];
233
- sv2[16] = rndB[7];
234
- sv2[17] = rndB[6];
235
- sv2[18] = rndB[5];
236
- sv2[19] = rndB[4];
237
- sv2[20] = rndB[3];
238
- sv2[21] = rndB[2];
239
- sv2[22] = rndB[1];
240
- sv2[23] = rndB[0];
241
- // RndA[7..0] (8 bytes)
242
- sv2[24] = rndA[7];
243
- sv2[25] = rndA[6];
244
- sv2[26] = rndA[5];
245
- sv2[27] = rndA[4];
246
- sv2[28] = rndA[3];
247
- sv2[29] = rndA[2];
248
- sv2[30] = rndA[1];
249
- sv2[31] = rndA[0];
250
- return aesCmac(authKey, sv2);
251
- }
252
- // ---------------------------------------------------------------------------
253
- // Secure random
254
- // ---------------------------------------------------------------------------
255
- /**
256
- * Generate a 16-byte cryptographically-secure random value.
257
- *
258
- * Uses `crypto.getRandomValues` when available (Hermes ≥ 0.72, or
259
- * the `react-native-get-random-values` polyfill). Falls back to
260
- * `Math.random()` with a console warning if the API is missing.
261
- */
262
- function generateRandom16() {
263
- if (typeof globalThis.crypto === 'undefined' ||
264
- typeof globalThis.crypto.getRandomValues !== 'function') {
265
- throw new Error('[nfc-litecard] crypto.getRandomValues is not available. ' +
266
- 'Please add the react-native-get-random-values polyfill or use Hermes >= 0.72.');
267
- }
268
- const arr = new Uint8Array(16);
269
- globalThis.crypto.getRandomValues(arr);
270
- return arr;
271
- }
@@ -1,74 +0,0 @@
1
- /**
2
- * Core NFC communication layer – shared by reader and writer.
3
- *
4
- * Provides:
5
- * - A single global operation lock (prevents reader/writer concurrency)
6
- * - Page-cleanup cancellation flags
7
- * - Low-level transceive with platform-aware retries
8
- * - NFC technology request / release
9
- * - AES 3-pass mutual authentication (MF0AES(H)20 §8.6.1)
10
- * - PIN retry-counter helpers (session-level read/write)
11
- */
12
- import { passwordToAesKey } from './crypto';
13
- export { passwordToAesKey };
14
- /**
15
- * Acquire the NFC operation lock.
16
- * If another operation is in progress, polls every 100 ms for up to 10 s.
17
- */
18
- export declare function acquireNfcLock(): Promise<void>;
19
- export declare function releaseNfcLock(): void;
20
- export declare function isNfcOperationLocked(): boolean;
21
- export declare function releaseNfcOperationLock(): void;
22
- export declare function markNfcOperationCancelledByCleanup(): void;
23
- export declare function consumeNfcOperationCancelledByCleanup(): boolean;
24
- export declare function getNfcOperationCancelledByCleanupTimestamp(): number;
25
- export declare function clearNfcOperationCancelledByCleanup(): void;
26
- /** Clear CMAC session state. Called on NFC release. */
27
- export declare function clearCmacSession(): void;
28
- /**
29
- * Send a command to the NFC tag and return the response.
30
- *
31
- * When CMAC session is active:
32
- * - Appends 8-byte CMAC to outgoing commands
33
- * - Verifies and strips 8-byte CMAC from responses
34
- * - Increments CmdCtr after each command-response pair
35
- *
36
- * Retries: Android up to 2 retries (3 total), iOS up to 1 retry (2 total).
37
- */
38
- export declare function transceive(command: number[], _timeoutMs?: number, retryCount?: number): Promise<number[]>;
39
- /**
40
- * Request an NFC technology session (MifareIOS on iOS, NfcA on Android).
41
- *
42
- * All prior cleanup must be done by the caller BEFORE invoking this.
43
- */
44
- export declare function requestNfcTech(): Promise<void>;
45
- /**
46
- * Release the NFC technology session.
47
- * @param forceLongDelay Use a longer post-release delay (e.g. after timeout / tag-lost).
48
- */
49
- export declare function releaseNfcTech(forceLongDelay?: boolean): Promise<void>;
50
- /**
51
- * Perform AES 3-pass mutual authentication (MF0AES(H)20 §8.6.1).
52
- *
53
- * 1. PCD → PICC : AUTH_PART1 (0x1A) + KeyNo
54
- * 2. PICC → PCD : 0xAF + ek(RndB)
55
- * 3. PCD decrypts RndB, generates RndA, computes RndB' = rotateLeft8(RndB)
56
- * 4. PCD → PICC : AUTH_PART2 (0xAF) + ek(RndA ‖ RndB')
57
- * 5. PICC → PCD : 0x00 + ek(RndA')
58
- * 6. PCD verifies RndA' === rotateLeft8(RndA)
59
- *
60
- * @throws AUTH_INVALID_RESPONSE / AUTH_WRONG_PASSWORD / AUTH_VERIFY_FAILED
61
- */
62
- export declare function authenticate(key: Uint8Array): Promise<void>;
63
- /** FAST_READ pages 0x08–0x27 (user memory). */
64
- export declare function readUserMemory(): Promise<Uint8Array>;
65
- /**
66
- * Decrement the on-card retry counter by 1 (clamped to 0).
67
- * Must be called within an active NFC session (between requestNfcTech and releaseNfcTech).
68
- */
69
- export declare function decrementRetryCountInSession(): Promise<number | undefined>;
70
- /**
71
- * Write a specific retry count to the on-card counter.
72
- * Must be called within an active NFC session.
73
- */
74
- export declare function writeRetryCountInSession(count: number): Promise<void>;