@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.
- package/README.md +30 -2
- package/lib/commonjs/ble/constants.js +28 -2
- package/lib/commonjs/ble/constants.js.map +1 -1
- package/lib/commonjs/ble/parsers.js +128 -0
- package/lib/commonjs/ble/parsers.js.map +1 -1
- package/lib/commonjs/managers/DeviceManager.js +179 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -1
- package/lib/commonjs/upload/UploadQueue.js +9 -2
- package/lib/commonjs/upload/UploadQueue.js.map +1 -1
- package/lib/commonjs/utils/crypto.js +169 -0
- package/lib/commonjs/utils/crypto.js.map +1 -0
- package/lib/commonjs/utils/index.js +12 -0
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/ble/constants.js +26 -0
- package/lib/module/ble/constants.js.map +1 -1
- package/lib/module/ble/parsers.js +124 -1
- package/lib/module/ble/parsers.js.map +1 -1
- package/lib/module/managers/DeviceManager.js +181 -2
- package/lib/module/managers/DeviceManager.js.map +1 -1
- package/lib/module/upload/UploadQueue.js +9 -2
- package/lib/module/upload/UploadQueue.js.map +1 -1
- package/lib/module/utils/crypto.js +160 -0
- package/lib/module/utils/crypto.js.map +1 -0
- package/lib/module/utils/index.js +1 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/src/ble/constants.d.ts +18 -0
- package/lib/typescript/src/ble/constants.d.ts.map +1 -1
- package/lib/typescript/src/ble/parsers.d.ts +34 -1
- package/lib/typescript/src/ble/parsers.d.ts.map +1 -1
- package/lib/typescript/src/managers/DeviceManager.d.ts +73 -1
- package/lib/typescript/src/managers/DeviceManager.d.ts.map +1 -1
- package/lib/typescript/src/models/Device.d.ts +65 -0
- package/lib/typescript/src/models/Device.d.ts.map +1 -1
- package/lib/typescript/src/models/Recording.d.ts +9 -9
- package/lib/typescript/src/models/Recording.d.ts.map +1 -1
- package/lib/typescript/src/upload/UploadQueue.d.ts +2 -2
- package/lib/typescript/src/upload/UploadQueue.d.ts.map +1 -1
- package/lib/typescript/src/utils/crypto.d.ts +83 -0
- package/lib/typescript/src/utils/crypto.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +1 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/ble/constants.ts +26 -0
- package/src/ble/parsers.ts +131 -0
- package/src/managers/DeviceManager.ts +244 -0
- package/src/models/Device.ts +76 -0
- package/src/models/Recording.ts +9 -9
- package/src/upload/UploadQueue.ts +15 -8
- package/src/utils/crypto.ts +221 -0
- 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
|
+
}
|
package/src/utils/index.ts
CHANGED