react-native-ble-mesh 1.1.1 → 2.1.0
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 +288 -172
- package/docs/IOS-BACKGROUND-BLE.md +231 -0
- package/docs/OPTIMIZATION.md +183 -0
- package/docs/SPEC-v2.1.md +308 -0
- package/package.json +1 -1
- package/src/MeshNetwork.js +667 -465
- package/src/constants/index.js +1 -0
- package/src/crypto/AutoCrypto.js +90 -0
- package/src/crypto/CryptoProvider.js +99 -0
- package/src/crypto/index.js +15 -63
- package/src/crypto/providers/ExpoCryptoProvider.js +126 -0
- package/src/crypto/providers/QuickCryptoProvider.js +158 -0
- package/src/crypto/providers/TweetNaClProvider.js +124 -0
- package/src/crypto/providers/index.js +11 -0
- package/src/errors/MeshError.js +2 -1
- package/src/expo/withBLEMesh.js +102 -0
- package/src/hooks/AppStateManager.js +9 -1
- package/src/hooks/useMesh.js +47 -13
- package/src/hooks/useMessages.js +6 -4
- package/src/hooks/usePeers.js +13 -9
- package/src/index.js +23 -8
- package/src/mesh/dedup/BloomFilter.js +44 -57
- package/src/mesh/dedup/DedupManager.js +67 -10
- package/src/mesh/fragment/Assembler.js +5 -0
- package/src/mesh/fragment/Fragmenter.js +1 -1
- package/src/mesh/index.js +1 -1
- package/src/mesh/monitor/ConnectionQuality.js +433 -0
- package/src/mesh/monitor/NetworkMonitor.js +376 -320
- package/src/mesh/monitor/index.js +7 -3
- package/src/mesh/peer/Peer.js +5 -2
- package/src/mesh/peer/PeerManager.js +21 -4
- package/src/mesh/router/MessageRouter.js +38 -19
- package/src/mesh/router/RouteTable.js +24 -8
- package/src/mesh/store/StoreAndForwardManager.js +305 -296
- package/src/mesh/store/index.js +1 -1
- package/src/protocol/deserializer.js +9 -10
- package/src/protocol/header.js +13 -7
- package/src/protocol/message.js +15 -3
- package/src/protocol/serializer.js +7 -10
- package/src/protocol/validator.js +23 -5
- package/src/service/BatteryOptimizer.js +285 -278
- package/src/service/EmergencyManager.js +224 -214
- package/src/service/HandshakeManager.js +163 -13
- package/src/service/MeshService.js +72 -6
- package/src/service/SessionManager.js +79 -2
- package/src/service/audio/AudioManager.js +8 -2
- package/src/service/file/FileAssembler.js +106 -0
- package/src/service/file/FileChunker.js +79 -0
- package/src/service/file/FileManager.js +307 -0
- package/src/service/file/FileMessage.js +122 -0
- package/src/service/file/index.js +15 -0
- package/src/service/text/TextManager.js +21 -15
- package/src/service/text/broadcast/BroadcastManager.js +16 -0
- package/src/storage/MessageStore.js +55 -2
- package/src/transport/BLETransport.js +141 -10
- package/src/transport/MockTransport.js +1 -1
- package/src/transport/MultiTransport.js +330 -0
- package/src/transport/WiFiDirectTransport.js +296 -0
- package/src/transport/adapters/NodeBLEAdapter.js +34 -0
- package/src/transport/adapters/RNBLEAdapter.js +56 -1
- package/src/transport/index.js +6 -0
- package/src/utils/EventEmitter.js +6 -9
- package/src/utils/bytes.js +12 -10
- package/src/utils/compression.js +293 -291
- package/src/utils/encoding.js +33 -8
- package/src/crypto/aead.js +0 -189
- package/src/crypto/chacha20.js +0 -181
- package/src/crypto/hkdf.js +0 -187
- package/src/crypto/hmac.js +0 -143
- package/src/crypto/keys/KeyManager.js +0 -271
- package/src/crypto/keys/KeyPair.js +0 -216
- package/src/crypto/keys/SecureStorage.js +0 -219
- package/src/crypto/keys/index.js +0 -32
- package/src/crypto/noise/handshake.js +0 -410
- package/src/crypto/noise/index.js +0 -27
- package/src/crypto/noise/session.js +0 -253
- package/src/crypto/noise/state.js +0 -268
- package/src/crypto/poly1305.js +0 -113
- package/src/crypto/sha256.js +0 -240
- package/src/crypto/x25519.js +0 -154
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SecureStorage - Abstract storage interface for sensitive key material.
|
|
5
|
-
* Implementations should use platform-specific secure storage mechanisms.
|
|
6
|
-
* @module crypto/keys/SecureStorage
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* SecureStorage defines the interface for secure key storage.
|
|
11
|
-
* This is an abstract class - use platform-specific implementations.
|
|
12
|
-
*
|
|
13
|
-
* Platform implementations:
|
|
14
|
-
* - React Native: Use react-native-keychain or expo-secure-store
|
|
15
|
-
* - Node.js: Use encrypted file storage or keytar
|
|
16
|
-
* - Browser: Use IndexedDB with encryption
|
|
17
|
-
*
|
|
18
|
-
* @abstract
|
|
19
|
-
* @class
|
|
20
|
-
*/
|
|
21
|
-
class SecureStorage {
|
|
22
|
-
/**
|
|
23
|
-
* Stores a value securely.
|
|
24
|
-
* @param {string} key - Storage key
|
|
25
|
-
* @param {string} value - Value to store (should be serialized JSON)
|
|
26
|
-
* @returns {Promise<void>}
|
|
27
|
-
* @abstract
|
|
28
|
-
*/
|
|
29
|
-
async set(_key, _value) {
|
|
30
|
-
throw new Error('SecureStorage.set() must be implemented');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Retrieves a stored value.
|
|
35
|
-
* @param {string} key - Storage key
|
|
36
|
-
* @returns {Promise<string|null>} Stored value or null if not found
|
|
37
|
-
* @abstract
|
|
38
|
-
*/
|
|
39
|
-
async get(_key) {
|
|
40
|
-
throw new Error('SecureStorage.get() must be implemented');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Deletes a stored value.
|
|
45
|
-
* @param {string} key - Storage key
|
|
46
|
-
* @returns {Promise<void>}
|
|
47
|
-
* @abstract
|
|
48
|
-
*/
|
|
49
|
-
async delete(_key) {
|
|
50
|
-
throw new Error('SecureStorage.delete() must be implemented');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Checks if a key exists in storage.
|
|
55
|
-
* @param {string} key - Storage key
|
|
56
|
-
* @returns {Promise<boolean>} True if key exists
|
|
57
|
-
*/
|
|
58
|
-
async has(key) {
|
|
59
|
-
const value = await this.get(key);
|
|
60
|
-
return value !== null && value !== undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Clears all stored values.
|
|
65
|
-
* @returns {Promise<void>}
|
|
66
|
-
* @abstract
|
|
67
|
-
*/
|
|
68
|
-
async clear() {
|
|
69
|
-
throw new Error('SecureStorage.clear() must be implemented');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* MemorySecureStorage - In-memory implementation for testing.
|
|
75
|
-
* WARNING: Not secure for production use - keys are not persisted
|
|
76
|
-
* and are stored in plain memory.
|
|
77
|
-
*
|
|
78
|
-
* @class
|
|
79
|
-
* @extends SecureStorage
|
|
80
|
-
*/
|
|
81
|
-
class MemorySecureStorage extends SecureStorage {
|
|
82
|
-
constructor() {
|
|
83
|
-
super();
|
|
84
|
-
/** @type {Map<string, string>} */
|
|
85
|
-
this._store = new Map();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Stores a value in memory.
|
|
90
|
-
* @param {string} key - Storage key
|
|
91
|
-
* @param {string} value - Value to store
|
|
92
|
-
* @returns {Promise<void>}
|
|
93
|
-
*/
|
|
94
|
-
async set(key, value) {
|
|
95
|
-
if (typeof key !== 'string') {
|
|
96
|
-
throw new Error('Key must be a string');
|
|
97
|
-
}
|
|
98
|
-
if (typeof value !== 'string') {
|
|
99
|
-
throw new Error('Value must be a string');
|
|
100
|
-
}
|
|
101
|
-
this._store.set(key, value);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Retrieves a value from memory.
|
|
106
|
-
* @param {string} key - Storage key
|
|
107
|
-
* @returns {Promise<string|null>} Stored value or null
|
|
108
|
-
*/
|
|
109
|
-
async get(key) {
|
|
110
|
-
if (typeof key !== 'string') {
|
|
111
|
-
throw new Error('Key must be a string');
|
|
112
|
-
}
|
|
113
|
-
return this._store.get(key) || null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Deletes a value from memory.
|
|
118
|
-
* @param {string} key - Storage key
|
|
119
|
-
* @returns {Promise<void>}
|
|
120
|
-
*/
|
|
121
|
-
async delete(key) {
|
|
122
|
-
if (typeof key !== 'string') {
|
|
123
|
-
throw new Error('Key must be a string');
|
|
124
|
-
}
|
|
125
|
-
this._store.delete(key);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Clears all stored values.
|
|
130
|
-
* @returns {Promise<void>}
|
|
131
|
-
*/
|
|
132
|
-
async clear() {
|
|
133
|
-
this._store.clear();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Gets the number of stored items.
|
|
138
|
-
* @returns {number} Number of items
|
|
139
|
-
*/
|
|
140
|
-
get size() {
|
|
141
|
-
return this._store.size;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Creates a SecureStorage adapter for React Native AsyncStorage.
|
|
147
|
-
* Note: AsyncStorage is NOT secure - consider using expo-secure-store
|
|
148
|
-
* or react-native-keychain for production.
|
|
149
|
-
*
|
|
150
|
-
* @param {object} asyncStorage - AsyncStorage instance
|
|
151
|
-
* @returns {SecureStorage} Storage adapter
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
155
|
-
* const storage = createAsyncStorageAdapter(AsyncStorage);
|
|
156
|
-
*/
|
|
157
|
-
function createAsyncStorageAdapter(asyncStorage) {
|
|
158
|
-
return {
|
|
159
|
-
async set(key, value) {
|
|
160
|
-
await asyncStorage.setItem(key, value);
|
|
161
|
-
},
|
|
162
|
-
async get(key) {
|
|
163
|
-
return await asyncStorage.getItem(key);
|
|
164
|
-
},
|
|
165
|
-
async delete(key) {
|
|
166
|
-
await asyncStorage.removeItem(key);
|
|
167
|
-
},
|
|
168
|
-
async has(key) {
|
|
169
|
-
const value = await asyncStorage.getItem(key);
|
|
170
|
-
return value !== null;
|
|
171
|
-
},
|
|
172
|
-
async clear() {
|
|
173
|
-
// Only clear mesh-related keys
|
|
174
|
-
const keys = await asyncStorage.getAllKeys();
|
|
175
|
-
const meshKeys = keys.filter(k => k.startsWith('mesh_'));
|
|
176
|
-
await asyncStorage.multiRemove(meshKeys);
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Creates a SecureStorage adapter for expo-secure-store.
|
|
183
|
-
* This provides actual secure storage on iOS/Android.
|
|
184
|
-
*
|
|
185
|
-
* @param {object} secureStore - SecureStore module from expo-secure-store
|
|
186
|
-
* @returns {SecureStorage} Storage adapter
|
|
187
|
-
*
|
|
188
|
-
* @example
|
|
189
|
-
* import * as SecureStore from 'expo-secure-store';
|
|
190
|
-
* const storage = createExpoSecureStoreAdapter(SecureStore);
|
|
191
|
-
*/
|
|
192
|
-
function createExpoSecureStoreAdapter(secureStore) {
|
|
193
|
-
return {
|
|
194
|
-
async set(key, value) {
|
|
195
|
-
await secureStore.setItemAsync(key, value);
|
|
196
|
-
},
|
|
197
|
-
async get(key) {
|
|
198
|
-
return await secureStore.getItemAsync(key);
|
|
199
|
-
},
|
|
200
|
-
async delete(key) {
|
|
201
|
-
await secureStore.deleteItemAsync(key);
|
|
202
|
-
},
|
|
203
|
-
async has(key) {
|
|
204
|
-
const value = await secureStore.getItemAsync(key);
|
|
205
|
-
return value !== null;
|
|
206
|
-
},
|
|
207
|
-
async clear() {
|
|
208
|
-
// expo-secure-store doesn't have getAllKeys, so we track known keys
|
|
209
|
-
console.warn('SecureStore clear() requires manual key tracking');
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
module.exports = {
|
|
215
|
-
SecureStorage,
|
|
216
|
-
MemorySecureStorage,
|
|
217
|
-
createAsyncStorageAdapter,
|
|
218
|
-
createExpoSecureStoreAdapter
|
|
219
|
-
};
|
package/src/crypto/keys/index.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Keys Module
|
|
5
|
-
* Re-exports all key management components.
|
|
6
|
-
* @module crypto/keys
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { KeyPair, KEY_SIZE } = require('./KeyPair');
|
|
10
|
-
const { KeyManager, DEFAULT_IDENTITY_KEY } = require('./KeyManager');
|
|
11
|
-
const {
|
|
12
|
-
SecureStorage,
|
|
13
|
-
MemorySecureStorage,
|
|
14
|
-
createAsyncStorageAdapter,
|
|
15
|
-
createExpoSecureStoreAdapter
|
|
16
|
-
} = require('./SecureStorage');
|
|
17
|
-
|
|
18
|
-
module.exports = {
|
|
19
|
-
// KeyPair
|
|
20
|
-
KeyPair,
|
|
21
|
-
KEY_SIZE,
|
|
22
|
-
|
|
23
|
-
// KeyManager
|
|
24
|
-
KeyManager,
|
|
25
|
-
DEFAULT_IDENTITY_KEY,
|
|
26
|
-
|
|
27
|
-
// SecureStorage
|
|
28
|
-
SecureStorage,
|
|
29
|
-
MemorySecureStorage,
|
|
30
|
-
createAsyncStorageAdapter,
|
|
31
|
-
createExpoSecureStoreAdapter
|
|
32
|
-
};
|
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Noise Protocol XX Handshake Implementation
|
|
5
|
-
*
|
|
6
|
-
* XX Pattern (mutual authentication with transmitted static keys):
|
|
7
|
-
* -> e (initiator sends ephemeral public key)
|
|
8
|
-
* <- e, ee, s, es (responder: ephemeral, DH, encrypted static)
|
|
9
|
-
* -> s, se (initiator: encrypted static, final DH)
|
|
10
|
-
*
|
|
11
|
-
* @module crypto/noise/handshake
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const { SymmetricState, PROTOCOL_NAME } = require('./state');
|
|
15
|
-
const { generateKeyPair, scalarMult } = require('../x25519');
|
|
16
|
-
const { concat } = require('../../utils/bytes');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Handshake state machine states
|
|
20
|
-
* @enum {string}
|
|
21
|
-
*/
|
|
22
|
-
const HandshakeState = {
|
|
23
|
-
INITIAL: 'INITIAL',
|
|
24
|
-
MSG1_WRITTEN: 'MSG1_WRITTEN',
|
|
25
|
-
MSG1_READ: 'MSG1_READ',
|
|
26
|
-
MSG2_WRITTEN: 'MSG2_WRITTEN',
|
|
27
|
-
MSG2_READ: 'MSG2_READ',
|
|
28
|
-
MSG3_WRITTEN: 'MSG3_WRITTEN',
|
|
29
|
-
MSG3_READ: 'MSG3_READ',
|
|
30
|
-
COMPLETE: 'COMPLETE',
|
|
31
|
-
ERROR: 'ERROR'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Role in the handshake
|
|
36
|
-
* @enum {string}
|
|
37
|
-
*/
|
|
38
|
-
const Role = {
|
|
39
|
-
INITIATOR: 'INITIATOR',
|
|
40
|
-
RESPONDER: 'RESPONDER'
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Public key size in bytes
|
|
45
|
-
* @constant {number}
|
|
46
|
-
*/
|
|
47
|
-
const PUBLIC_KEY_SIZE = 32;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* AEAD tag size in bytes
|
|
51
|
-
* @constant {number}
|
|
52
|
-
*/
|
|
53
|
-
const TAG_SIZE = 16;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* NoiseHandshake implements the XX handshake pattern.
|
|
57
|
-
* @class
|
|
58
|
-
*/
|
|
59
|
-
class NoiseHandshake {
|
|
60
|
-
constructor() {
|
|
61
|
-
/** @type {SymmetricState|null} */
|
|
62
|
-
this._symmetricState = null;
|
|
63
|
-
|
|
64
|
-
/** @type {string} */
|
|
65
|
-
this._state = HandshakeState.INITIAL;
|
|
66
|
-
|
|
67
|
-
/** @type {string|null} */
|
|
68
|
-
this._role = null;
|
|
69
|
-
|
|
70
|
-
/** @type {{publicKey: Uint8Array, secretKey: Uint8Array}|null} */
|
|
71
|
-
this._staticKeyPair = null;
|
|
72
|
-
|
|
73
|
-
/** @type {{publicKey: Uint8Array, secretKey: Uint8Array}|null} */
|
|
74
|
-
this._ephemeralKeyPair = null;
|
|
75
|
-
|
|
76
|
-
/** @type {Uint8Array|null} */
|
|
77
|
-
this._remoteStaticPublicKey = null;
|
|
78
|
-
|
|
79
|
-
/** @type {Uint8Array|null} */
|
|
80
|
-
this._remoteEphemeralPublicKey = null;
|
|
81
|
-
|
|
82
|
-
/** @type {{sendKey: Uint8Array, receiveKey: Uint8Array}|null} */
|
|
83
|
-
this._transportKeys = null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Initializes the handshake as the initiator.
|
|
88
|
-
* @param {{publicKey: Uint8Array, secretKey: Uint8Array}} staticKeyPair
|
|
89
|
-
*/
|
|
90
|
-
initializeInitiator(staticKeyPair) {
|
|
91
|
-
if (this._role !== null) {
|
|
92
|
-
throw new Error('Invalid state: handshake already initialized');
|
|
93
|
-
}
|
|
94
|
-
this._validateState(HandshakeState.INITIAL, 'initializeInitiator');
|
|
95
|
-
this._validateKeyPair(staticKeyPair, 'static');
|
|
96
|
-
|
|
97
|
-
this._role = Role.INITIATOR;
|
|
98
|
-
this._staticKeyPair = staticKeyPair;
|
|
99
|
-
this._symmetricState = new SymmetricState(PROTOCOL_NAME);
|
|
100
|
-
|
|
101
|
-
// Prologue can be empty for basic use
|
|
102
|
-
this._symmetricState.mixHash(new Uint8Array(0));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Initializes the handshake as the responder.
|
|
107
|
-
* @param {{publicKey: Uint8Array, secretKey: Uint8Array}} staticKeyPair
|
|
108
|
-
*/
|
|
109
|
-
initializeResponder(staticKeyPair) {
|
|
110
|
-
if (this._role !== null) {
|
|
111
|
-
throw new Error('Invalid state: handshake already initialized');
|
|
112
|
-
}
|
|
113
|
-
this._validateState(HandshakeState.INITIAL, 'initializeResponder');
|
|
114
|
-
this._validateKeyPair(staticKeyPair, 'static');
|
|
115
|
-
|
|
116
|
-
this._role = Role.RESPONDER;
|
|
117
|
-
this._staticKeyPair = staticKeyPair;
|
|
118
|
-
this._symmetricState = new SymmetricState(PROTOCOL_NAME);
|
|
119
|
-
|
|
120
|
-
// Prologue can be empty for basic use
|
|
121
|
-
this._symmetricState.mixHash(new Uint8Array(0));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Writes message 1: -> e
|
|
126
|
-
* Initiator sends ephemeral public key.
|
|
127
|
-
* @returns {Uint8Array} Message 1 data
|
|
128
|
-
*/
|
|
129
|
-
writeMessage1() {
|
|
130
|
-
this._validateRole(Role.INITIATOR, 'writeMessage1');
|
|
131
|
-
this._validateState(HandshakeState.INITIAL, 'writeMessage1');
|
|
132
|
-
|
|
133
|
-
// Generate ephemeral key pair
|
|
134
|
-
this._ephemeralKeyPair = generateKeyPair();
|
|
135
|
-
|
|
136
|
-
// e: Mix ephemeral public key into hash
|
|
137
|
-
this._symmetricState.mixHash(this._ephemeralKeyPair.publicKey);
|
|
138
|
-
|
|
139
|
-
this._state = HandshakeState.MSG1_WRITTEN;
|
|
140
|
-
return new Uint8Array(this._ephemeralKeyPair.publicKey);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Reads message 1: -> e
|
|
145
|
-
* Responder receives initiator's ephemeral public key.
|
|
146
|
-
* @param {Uint8Array} data - Message 1 data
|
|
147
|
-
*/
|
|
148
|
-
readMessage1(data) {
|
|
149
|
-
this._validateRole(Role.RESPONDER, 'readMessage1');
|
|
150
|
-
this._validateState(HandshakeState.INITIAL, 'readMessage1');
|
|
151
|
-
|
|
152
|
-
if (data.length < PUBLIC_KEY_SIZE) {
|
|
153
|
-
throw new Error('Message 1 too short');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Extract remote ephemeral public key
|
|
157
|
-
this._remoteEphemeralPublicKey = data.subarray(0, PUBLIC_KEY_SIZE);
|
|
158
|
-
|
|
159
|
-
// e: Mix remote ephemeral into hash
|
|
160
|
-
this._symmetricState.mixHash(this._remoteEphemeralPublicKey);
|
|
161
|
-
|
|
162
|
-
this._state = HandshakeState.MSG1_READ;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Writes message 2: <- e, ee, s, es
|
|
167
|
-
* Responder sends ephemeral, performs DH, sends encrypted static.
|
|
168
|
-
* @returns {Uint8Array} Message 2 data
|
|
169
|
-
*/
|
|
170
|
-
writeMessage2() {
|
|
171
|
-
this._validateRole(Role.RESPONDER, 'writeMessage2');
|
|
172
|
-
this._validateState(HandshakeState.MSG1_READ, 'writeMessage2');
|
|
173
|
-
|
|
174
|
-
// Generate ephemeral key pair
|
|
175
|
-
this._ephemeralKeyPair = generateKeyPair();
|
|
176
|
-
|
|
177
|
-
// e: Mix our ephemeral public key into hash
|
|
178
|
-
this._symmetricState.mixHash(this._ephemeralKeyPair.publicKey);
|
|
179
|
-
|
|
180
|
-
// ee: DH(ephemeral, remoteEphemeral)
|
|
181
|
-
const ee = scalarMult(
|
|
182
|
-
this._ephemeralKeyPair.secretKey,
|
|
183
|
-
this._remoteEphemeralPublicKey
|
|
184
|
-
);
|
|
185
|
-
this._symmetricState.mixKey(ee);
|
|
186
|
-
|
|
187
|
-
// s: Encrypt and send static public key
|
|
188
|
-
const encryptedStatic = this._symmetricState.encryptAndHash(
|
|
189
|
-
this._staticKeyPair.publicKey
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
// es: DH(ephemeral, remoteStatic) - but we dont have it yet in XX
|
|
193
|
-
// Actually in XX responder pattern: es = DH(s, re)
|
|
194
|
-
const es = scalarMult(
|
|
195
|
-
this._staticKeyPair.secretKey,
|
|
196
|
-
this._remoteEphemeralPublicKey
|
|
197
|
-
);
|
|
198
|
-
this._symmetricState.mixKey(es);
|
|
199
|
-
|
|
200
|
-
// Combine: e || encrypted_s
|
|
201
|
-
const message = concat(this._ephemeralKeyPair.publicKey, encryptedStatic);
|
|
202
|
-
|
|
203
|
-
this._state = HandshakeState.MSG2_WRITTEN;
|
|
204
|
-
return message;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Reads message 2: <- e, ee, s, es
|
|
209
|
-
* Initiator receives and processes responder's message 2.
|
|
210
|
-
* @param {Uint8Array} data - Message 2 data
|
|
211
|
-
*/
|
|
212
|
-
readMessage2(data) {
|
|
213
|
-
this._validateRole(Role.INITIATOR, 'readMessage2');
|
|
214
|
-
this._validateState(HandshakeState.MSG1_WRITTEN, 'readMessage2');
|
|
215
|
-
|
|
216
|
-
const expectedSize = PUBLIC_KEY_SIZE + PUBLIC_KEY_SIZE + TAG_SIZE;
|
|
217
|
-
if (data.length < expectedSize) {
|
|
218
|
-
throw new Error('Message 2 too short');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// e: Extract and mix remote ephemeral
|
|
222
|
-
this._remoteEphemeralPublicKey = data.subarray(0, PUBLIC_KEY_SIZE);
|
|
223
|
-
this._symmetricState.mixHash(this._remoteEphemeralPublicKey);
|
|
224
|
-
|
|
225
|
-
// ee: DH(ephemeral, remoteEphemeral)
|
|
226
|
-
const ee = scalarMult(
|
|
227
|
-
this._ephemeralKeyPair.secretKey,
|
|
228
|
-
this._remoteEphemeralPublicKey
|
|
229
|
-
);
|
|
230
|
-
this._symmetricState.mixKey(ee);
|
|
231
|
-
|
|
232
|
-
// s: Decrypt remote static public key
|
|
233
|
-
const encryptedStatic = data.subarray(
|
|
234
|
-
PUBLIC_KEY_SIZE,
|
|
235
|
-
PUBLIC_KEY_SIZE + PUBLIC_KEY_SIZE + TAG_SIZE
|
|
236
|
-
);
|
|
237
|
-
this._remoteStaticPublicKey = this._symmetricState.decryptAndHash(
|
|
238
|
-
encryptedStatic
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
// es: DH(e, rs)
|
|
242
|
-
const es = scalarMult(
|
|
243
|
-
this._ephemeralKeyPair.secretKey,
|
|
244
|
-
this._remoteStaticPublicKey
|
|
245
|
-
);
|
|
246
|
-
this._symmetricState.mixKey(es);
|
|
247
|
-
|
|
248
|
-
this._state = HandshakeState.MSG2_READ;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Writes message 3: -> s, se
|
|
253
|
-
* Initiator sends encrypted static and performs final DH.
|
|
254
|
-
* @returns {Uint8Array} Message 3 data
|
|
255
|
-
*/
|
|
256
|
-
writeMessage3() {
|
|
257
|
-
this._validateRole(Role.INITIATOR, 'writeMessage3');
|
|
258
|
-
this._validateState(HandshakeState.MSG2_READ, 'writeMessage3');
|
|
259
|
-
|
|
260
|
-
// s: Encrypt and send static public key
|
|
261
|
-
const encryptedStatic = this._symmetricState.encryptAndHash(
|
|
262
|
-
this._staticKeyPair.publicKey
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
// se: DH(s, re)
|
|
266
|
-
const se = scalarMult(
|
|
267
|
-
this._staticKeyPair.secretKey,
|
|
268
|
-
this._remoteEphemeralPublicKey
|
|
269
|
-
);
|
|
270
|
-
this._symmetricState.mixKey(se);
|
|
271
|
-
|
|
272
|
-
// Split to get transport keys
|
|
273
|
-
const { sendKey, receiveKey } = this._symmetricState.split();
|
|
274
|
-
this._transportKeys = { sendKey, receiveKey };
|
|
275
|
-
|
|
276
|
-
this._state = HandshakeState.MSG3_WRITTEN;
|
|
277
|
-
return encryptedStatic;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Reads message 3: -> s, se
|
|
282
|
-
* Responder receives and processes initiator's message 3.
|
|
283
|
-
* @param {Uint8Array} data - Message 3 data
|
|
284
|
-
*/
|
|
285
|
-
readMessage3(data) {
|
|
286
|
-
this._validateRole(Role.RESPONDER, 'readMessage3');
|
|
287
|
-
this._validateState(HandshakeState.MSG2_WRITTEN, 'readMessage3');
|
|
288
|
-
|
|
289
|
-
const expectedSize = PUBLIC_KEY_SIZE + TAG_SIZE;
|
|
290
|
-
if (data.length < expectedSize) {
|
|
291
|
-
throw new Error('Message 3 too short');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// s: Decrypt remote static public key
|
|
295
|
-
this._remoteStaticPublicKey = this._symmetricState.decryptAndHash(data);
|
|
296
|
-
|
|
297
|
-
// se: DH(e, rs)
|
|
298
|
-
const se = scalarMult(
|
|
299
|
-
this._ephemeralKeyPair.secretKey,
|
|
300
|
-
this._remoteStaticPublicKey
|
|
301
|
-
);
|
|
302
|
-
this._symmetricState.mixKey(se);
|
|
303
|
-
|
|
304
|
-
// Split to get transport keys (reversed for responder)
|
|
305
|
-
const { sendKey, receiveKey } = this._symmetricState.split();
|
|
306
|
-
this._transportKeys = { sendKey: receiveKey, receiveKey: sendKey };
|
|
307
|
-
|
|
308
|
-
this._state = HandshakeState.MSG3_READ;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Checks if the handshake is complete.
|
|
313
|
-
* @returns {boolean} True if handshake is complete
|
|
314
|
-
*/
|
|
315
|
-
isComplete() {
|
|
316
|
-
return this._state === HandshakeState.MSG3_WRITTEN ||
|
|
317
|
-
this._state === HandshakeState.MSG3_READ;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Gets the NoiseSession for transport encryption.
|
|
322
|
-
* @returns {NoiseSession} Transport session
|
|
323
|
-
* @throws {Error} If handshake is not complete
|
|
324
|
-
*/
|
|
325
|
-
getSession() {
|
|
326
|
-
if (!this.isComplete()) {
|
|
327
|
-
throw new Error('Handshake not complete');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const { NoiseSession } = require('./session');
|
|
331
|
-
return new NoiseSession(
|
|
332
|
-
this._transportKeys.sendKey,
|
|
333
|
-
this._transportKeys.receiveKey,
|
|
334
|
-
this._role === Role.INITIATOR
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Gets the remote peer's static public key.
|
|
340
|
-
* @returns {Uint8Array|null} Remote static public key or null
|
|
341
|
-
*/
|
|
342
|
-
getRemotePublicKey() {
|
|
343
|
-
return this._remoteStaticPublicKey
|
|
344
|
-
? new Uint8Array(this._remoteStaticPublicKey)
|
|
345
|
-
: null;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Gets the current handshake hash (channel binding).
|
|
350
|
-
* @returns {Uint8Array} Handshake hash
|
|
351
|
-
*/
|
|
352
|
-
getHandshakeHash() {
|
|
353
|
-
if (!this._symmetricState) {
|
|
354
|
-
throw new Error('Handshake not initialized');
|
|
355
|
-
}
|
|
356
|
-
return this._symmetricState.getHandshakeHash();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Validates that the current state matches expected.
|
|
361
|
-
* @param {string} expected - Expected state
|
|
362
|
-
* @param {string} operation - Operation name for error message
|
|
363
|
-
* @private
|
|
364
|
-
*/
|
|
365
|
-
_validateState(expected, operation) {
|
|
366
|
-
if (this._state !== expected) {
|
|
367
|
-
throw new Error(
|
|
368
|
-
`Invalid state for ${operation}: expected ${expected}, got ${this._state}`
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Validates that the role matches expected.
|
|
375
|
-
* @param {string} expected - Expected role
|
|
376
|
-
* @param {string} operation - Operation name for error message
|
|
377
|
-
* @private
|
|
378
|
-
*/
|
|
379
|
-
_validateRole(expected, operation) {
|
|
380
|
-
if (this._role !== expected) {
|
|
381
|
-
throw new Error(
|
|
382
|
-
`Invalid role for ${operation}: expected ${expected}, got ${this._role}`
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Validates a key pair.
|
|
389
|
-
* @param {object} keyPair - Key pair to validate
|
|
390
|
-
* @param {string} name - Name for error message
|
|
391
|
-
* @private
|
|
392
|
-
*/
|
|
393
|
-
_validateKeyPair(keyPair, name) {
|
|
394
|
-
if (!keyPair || !keyPair.publicKey || !keyPair.secretKey) {
|
|
395
|
-
throw new Error(`Invalid ${name} key pair`);
|
|
396
|
-
}
|
|
397
|
-
if (keyPair.publicKey.length !== PUBLIC_KEY_SIZE) {
|
|
398
|
-
throw new Error(`${name} public key must be ${PUBLIC_KEY_SIZE} bytes`);
|
|
399
|
-
}
|
|
400
|
-
if (keyPair.secretKey.length !== PUBLIC_KEY_SIZE) {
|
|
401
|
-
throw new Error(`${name} secret key must be ${PUBLIC_KEY_SIZE} bytes`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
module.exports = {
|
|
407
|
-
NoiseHandshake,
|
|
408
|
-
HandshakeState,
|
|
409
|
-
Role
|
|
410
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Noise Protocol Module
|
|
5
|
-
* Re-exports all Noise Protocol components.
|
|
6
|
-
* @module crypto/noise
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { SymmetricState, PROTOCOL_NAME } = require('./state');
|
|
10
|
-
const { NoiseHandshake, HandshakeState, Role } = require('./handshake');
|
|
11
|
-
const { NoiseSession, MAX_NONCE, REKEY_THRESHOLD } = require('./session');
|
|
12
|
-
|
|
13
|
-
module.exports = {
|
|
14
|
-
// State management
|
|
15
|
-
SymmetricState,
|
|
16
|
-
PROTOCOL_NAME,
|
|
17
|
-
|
|
18
|
-
// Handshake
|
|
19
|
-
NoiseHandshake,
|
|
20
|
-
HandshakeState,
|
|
21
|
-
Role,
|
|
22
|
-
|
|
23
|
-
// Transport session
|
|
24
|
-
NoiseSession,
|
|
25
|
-
MAX_NONCE,
|
|
26
|
-
REKEY_THRESHOLD
|
|
27
|
-
};
|