@bota-dev/react-native-sdk 0.0.6 → 0.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.
Files changed (50) hide show
  1. package/README.md +30 -2
  2. package/lib/commonjs/ble/constants.js +28 -2
  3. package/lib/commonjs/ble/constants.js.map +1 -1
  4. package/lib/commonjs/ble/parsers.js +128 -0
  5. package/lib/commonjs/ble/parsers.js.map +1 -1
  6. package/lib/commonjs/managers/DeviceManager.js +179 -0
  7. package/lib/commonjs/managers/DeviceManager.js.map +1 -1
  8. package/lib/commonjs/upload/UploadQueue.js +9 -2
  9. package/lib/commonjs/upload/UploadQueue.js.map +1 -1
  10. package/lib/commonjs/utils/crypto.js +169 -0
  11. package/lib/commonjs/utils/crypto.js.map +1 -0
  12. package/lib/commonjs/utils/index.js +12 -0
  13. package/lib/commonjs/utils/index.js.map +1 -1
  14. package/lib/module/ble/constants.js +26 -0
  15. package/lib/module/ble/constants.js.map +1 -1
  16. package/lib/module/ble/parsers.js +124 -1
  17. package/lib/module/ble/parsers.js.map +1 -1
  18. package/lib/module/managers/DeviceManager.js +181 -2
  19. package/lib/module/managers/DeviceManager.js.map +1 -1
  20. package/lib/module/upload/UploadQueue.js +9 -2
  21. package/lib/module/upload/UploadQueue.js.map +1 -1
  22. package/lib/module/utils/crypto.js +160 -0
  23. package/lib/module/utils/crypto.js.map +1 -0
  24. package/lib/module/utils/index.js +1 -0
  25. package/lib/module/utils/index.js.map +1 -1
  26. package/lib/typescript/src/ble/constants.d.ts +18 -0
  27. package/lib/typescript/src/ble/constants.d.ts.map +1 -1
  28. package/lib/typescript/src/ble/parsers.d.ts +34 -1
  29. package/lib/typescript/src/ble/parsers.d.ts.map +1 -1
  30. package/lib/typescript/src/managers/DeviceManager.d.ts +73 -1
  31. package/lib/typescript/src/managers/DeviceManager.d.ts.map +1 -1
  32. package/lib/typescript/src/models/Device.d.ts +65 -0
  33. package/lib/typescript/src/models/Device.d.ts.map +1 -1
  34. package/lib/typescript/src/models/Recording.d.ts +9 -9
  35. package/lib/typescript/src/models/Recording.d.ts.map +1 -1
  36. package/lib/typescript/src/upload/UploadQueue.d.ts +2 -2
  37. package/lib/typescript/src/upload/UploadQueue.d.ts.map +1 -1
  38. package/lib/typescript/src/utils/crypto.d.ts +83 -0
  39. package/lib/typescript/src/utils/crypto.d.ts.map +1 -0
  40. package/lib/typescript/src/utils/index.d.ts +1 -0
  41. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  42. package/package.json +6 -2
  43. package/src/ble/constants.ts +26 -0
  44. package/src/ble/parsers.ts +131 -0
  45. package/src/managers/DeviceManager.ts +244 -0
  46. package/src/models/Device.ts +76 -0
  47. package/src/models/Recording.ts +9 -9
  48. package/src/upload/UploadQueue.ts +15 -8
  49. package/src/utils/crypto.ts +221 -0
  50. package/src/utils/index.ts +1 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Cryptographic utilities for WiFi Upload configuration
3
+ *
4
+ * NOTE: This module requires react-native-quick-crypto to be installed.
5
+ * Add to your app: npm install react-native-quick-crypto
6
+ */
7
+
8
+ import { Buffer } from 'buffer';
9
+
10
+ // Type-only import to avoid runtime errors if crypto is not installed
11
+ type CryptoModule = typeof import('crypto');
12
+
13
+ /**
14
+ * Get crypto module (lazy loaded to avoid errors if not installed)
15
+ */
16
+ function getCrypto(): CryptoModule {
17
+ try {
18
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
19
+ return require('react-native-quick-crypto') as CryptoModule;
20
+ } catch {
21
+ throw new Error(
22
+ 'WiFi Upload requires react-native-quick-crypto. ' +
23
+ 'Install it with: npm install react-native-quick-crypto'
24
+ );
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Derive K_session from WiFi config grant.
30
+ * K_session is used by device and app to encrypt/decrypt WiFi credentials in transit.
31
+ *
32
+ * Derivation: K_session = HMAC-SHA256(grant_blob, "BOTA_WIFI_SESSION_V1")
33
+ *
34
+ * @param grantBlob - Base64-encoded JWT grant from backend
35
+ * @returns 32-byte K_session as hex string
36
+ */
37
+ export function deriveSessionKey(grantBlob: string): string {
38
+ const crypto = getCrypto();
39
+
40
+ return crypto
41
+ .createHmac('sha256', grantBlob)
42
+ .update('BOTA_WIFI_SESSION_V1')
43
+ .digest('hex');
44
+ }
45
+
46
+ /**
47
+ * Encrypt WiFi credentials for BLE transmission using ChaCha20-Poly1305.
48
+ *
49
+ * Format sent to device:
50
+ * - 12 bytes: nonce (random)
51
+ * - N bytes: encrypted SSID
52
+ * - 16 bytes: auth tag for SSID
53
+ * - M bytes: encrypted password
54
+ * - 16 bytes: auth tag for password
55
+ *
56
+ * @param ssid - WiFi network SSID
57
+ * @param password - WiFi password
58
+ * @param sessionKey - K_session derived from grant (hex string)
59
+ * @returns Encrypted payload ready for BLE transmission
60
+ */
61
+ export function encryptWiFiCredentials(
62
+ ssid: string,
63
+ password: string,
64
+ sessionKey: string
65
+ ): {
66
+ nonce: Buffer;
67
+ ssidEncrypted: Buffer;
68
+ ssidAuthTag: Buffer;
69
+ passwordEncrypted: Buffer;
70
+ passwordAuthTag: Buffer;
71
+ } {
72
+ const crypto = getCrypto();
73
+
74
+ // Convert session key from hex to Buffer
75
+ const keyBuffer = Buffer.from(sessionKey, 'hex');
76
+
77
+ // Generate random nonce (12 bytes for ChaCha20-Poly1305)
78
+ const nonce = crypto.randomBytes(12);
79
+
80
+ // Encrypt SSID
81
+ const ssidCipher = crypto.createCipheriv('chacha20-poly1305', keyBuffer, nonce, {
82
+ authTagLength: 16,
83
+ });
84
+ const ssidEncrypted = Buffer.concat([
85
+ ssidCipher.update(ssid, 'utf-8'),
86
+ ssidCipher.final(),
87
+ ]);
88
+ const ssidAuthTag = ssidCipher.getAuthTag();
89
+
90
+ // Encrypt password (reuse same nonce with different additional data)
91
+ const passwordCipher = crypto.createCipheriv('chacha20-poly1305', keyBuffer, nonce, {
92
+ authTagLength: 16,
93
+ });
94
+ // Use different AAD to prevent nonce reuse issues
95
+ passwordCipher.setAAD(Buffer.from('password'), { plaintextLength: Buffer.byteLength(password, 'utf-8') });
96
+ const passwordEncrypted = Buffer.concat([
97
+ passwordCipher.update(password, 'utf-8'),
98
+ passwordCipher.final(),
99
+ ]);
100
+ const passwordAuthTag = passwordCipher.getAuthTag();
101
+
102
+ return {
103
+ nonce,
104
+ ssidEncrypted,
105
+ ssidAuthTag,
106
+ passwordEncrypted,
107
+ passwordAuthTag,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Decrypt WiFi credentials received from device (if needed for verification).
113
+ *
114
+ * @param encrypted - Encrypted SSID or password
115
+ * @param nonce - 12-byte nonce
116
+ * @param authTag - 16-byte authentication tag
117
+ * @param sessionKey - K_session derived from grant (hex string)
118
+ * @param aad - Additional authenticated data (optional, use 'password' for password field)
119
+ * @returns Decrypted plaintext
120
+ */
121
+ export function decryptWiFiCredential(
122
+ encrypted: Buffer,
123
+ nonce: Buffer,
124
+ authTag: Buffer,
125
+ sessionKey: string,
126
+ aad?: string
127
+ ): string {
128
+ const crypto = getCrypto();
129
+
130
+ // Convert session key from hex to Buffer
131
+ const keyBuffer = Buffer.from(sessionKey, 'hex');
132
+
133
+ // Decrypt with ChaCha20-Poly1305
134
+ const decipher = crypto.createDecipheriv('chacha20-poly1305', keyBuffer, nonce, {
135
+ authTagLength: 16,
136
+ });
137
+
138
+ decipher.setAuthTag(authTag);
139
+
140
+ if (aad) {
141
+ decipher.setAAD(Buffer.from(aad), { plaintextLength: encrypted.length });
142
+ }
143
+
144
+ const decrypted = Buffer.concat([
145
+ decipher.update(encrypted),
146
+ decipher.final(),
147
+ ]);
148
+
149
+ return decrypted.toString('utf-8');
150
+ }
151
+
152
+ /**
153
+ * Format encrypted WiFi credentials for BLE transmission.
154
+ *
155
+ * Packet format:
156
+ * [nonce (12 bytes)][ssid_encrypted (N bytes)][ssid_tag (16 bytes)]
157
+ * [password_encrypted (M bytes)][password_tag (16 bytes)]
158
+ *
159
+ * @param encrypted - Result from encryptWiFiCredentials
160
+ * @returns Single buffer ready for BLE write
161
+ */
162
+ export function formatWiFiCredentialPacket(encrypted: {
163
+ nonce: Buffer;
164
+ ssidEncrypted: Buffer;
165
+ ssidAuthTag: Buffer;
166
+ passwordEncrypted: Buffer;
167
+ passwordAuthTag: Buffer;
168
+ }): Buffer {
169
+ return Buffer.concat([
170
+ encrypted.nonce,
171
+ encrypted.ssidEncrypted,
172
+ encrypted.ssidAuthTag,
173
+ encrypted.passwordEncrypted,
174
+ encrypted.passwordAuthTag,
175
+ ]);
176
+ }
177
+
178
+ /**
179
+ * Parse encrypted WiFi credential packet received from device.
180
+ *
181
+ * @param packet - Buffer received from BLE
182
+ * @param ssidLength - Expected SSID encrypted length
183
+ * @param passwordLength - Expected password encrypted length
184
+ * @returns Parsed components
185
+ */
186
+ export function parseWiFiCredentialPacket(
187
+ packet: Buffer,
188
+ ssidLength: number,
189
+ passwordLength: number
190
+ ): {
191
+ nonce: Buffer;
192
+ ssidEncrypted: Buffer;
193
+ ssidAuthTag: Buffer;
194
+ passwordEncrypted: Buffer;
195
+ passwordAuthTag: Buffer;
196
+ } {
197
+ let offset = 0;
198
+
199
+ const nonce = packet.subarray(offset, offset + 12);
200
+ offset += 12;
201
+
202
+ const ssidEncrypted = packet.subarray(offset, offset + ssidLength);
203
+ offset += ssidLength;
204
+
205
+ const ssidAuthTag = packet.subarray(offset, offset + 16);
206
+ offset += 16;
207
+
208
+ const passwordEncrypted = packet.subarray(offset, offset + passwordLength);
209
+ offset += passwordLength;
210
+
211
+ const passwordAuthTag = packet.subarray(offset, offset + 16);
212
+ offset += 16;
213
+
214
+ return {
215
+ nonce,
216
+ ssidEncrypted,
217
+ ssidAuthTag,
218
+ passwordEncrypted,
219
+ passwordAuthTag,
220
+ };
221
+ }
@@ -5,3 +5,4 @@
5
5
  export * from './errors';
6
6
  export { logger } from './logger';
7
7
  export * from './retry';
8
+ export * from './crypto';