@sideband/secure-relay 0.2.2 → 0.3.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 +6 -4
- package/dist/.tsbuildinfo +1 -0
- package/dist/constants.d.ts +49 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/crypto.d.ts +70 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +144 -0
- package/dist/crypto.js.map +1 -0
- package/dist/frame.d.ts +219 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +554 -0
- package/dist/frame.js.map +1 -0
- package/dist/handshake.d.ts +39 -0
- package/dist/handshake.d.ts.map +1 -0
- package/dist/handshake.js +93 -0
- package/dist/handshake.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/replay.d.ts +32 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +88 -0
- package/dist/replay.js.map +1 -0
- package/dist/session.d.ts +67 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +122 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
- package/src/constants.ts +3 -3
- package/src/crypto.test.ts +5 -5
- package/src/frame.test.ts +113 -47
- package/src/frame.ts +119 -86
- package/src/handshake.test.ts +29 -41
- package/src/handshake.ts +25 -27
- package/src/index.ts +4 -10
- package/src/integration.test.ts +97 -138
- package/src/session.test.ts +12 -10
- package/src/types.ts +4 -14
- /package/{dist/LICENSE → LICENSE} +0 -0
package/src/integration.test.ts
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
encryptClientToDaemon,
|
|
34
34
|
encryptDaemonToClient,
|
|
35
35
|
} from "./session.js";
|
|
36
|
-
import {
|
|
36
|
+
import { asClientId, asDaemonId, SbrpError } from "./types.js";
|
|
37
37
|
|
|
38
38
|
const textEncoder = new TextEncoder();
|
|
39
39
|
const textDecoder = new TextDecoder();
|
|
@@ -52,14 +52,14 @@ describe("SBRP E2EE integration", () => {
|
|
|
52
52
|
expect(initMessage.initPublicKey.length).toBe(32);
|
|
53
53
|
|
|
54
54
|
// Step 2: Daemon processes init and creates accept
|
|
55
|
-
const { message: acceptMessage,
|
|
55
|
+
const { message: acceptMessage, sessionKeys: daemonKeys } =
|
|
56
56
|
processHandshakeInit(initMessage, daemonId, daemonIdentity);
|
|
57
57
|
expect(acceptMessage.type).toBe("handshake.accept");
|
|
58
58
|
expect(acceptMessage.acceptPublicKey.length).toBe(32);
|
|
59
59
|
expect(acceptMessage.signature.length).toBe(64);
|
|
60
60
|
|
|
61
61
|
// Step 3: Client verifies signature and derives keys (TOFU - first connection)
|
|
62
|
-
const
|
|
62
|
+
const clientKeys = processHandshakeAccept(
|
|
63
63
|
acceptMessage,
|
|
64
64
|
daemonId,
|
|
65
65
|
daemonIdentity.publicKey, // Pinned identity key
|
|
@@ -67,20 +67,13 @@ describe("SBRP E2EE integration", () => {
|
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
// Verify both sides derived the same session keys
|
|
70
|
-
expect(
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
expect(clientResult.sessionKeys.daemonToClient).toEqual(
|
|
74
|
-
daemonResult.sessionKeys.daemonToClient,
|
|
75
|
-
);
|
|
70
|
+
expect(clientKeys.clientToDaemon).toEqual(daemonKeys.clientToDaemon);
|
|
71
|
+
expect(clientKeys.daemonToClient).toEqual(daemonKeys.daemonToClient);
|
|
76
72
|
|
|
77
73
|
// Step 4: Create sessions
|
|
78
74
|
const clientId = asClientId("client-session-001");
|
|
79
|
-
const clientSession = createClientSession(
|
|
80
|
-
|
|
81
|
-
daemonResult.sessionKeys,
|
|
82
|
-
);
|
|
83
|
-
const daemonSession = createDaemonSession(clientResult.sessionKeys);
|
|
75
|
+
const clientSession = createClientSession(clientId, daemonKeys);
|
|
76
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
84
77
|
|
|
85
78
|
// Step 5: Client encrypts message to daemon
|
|
86
79
|
const clientMessage = textEncoder.encode("Hello from client!");
|
|
@@ -123,20 +116,17 @@ describe("SBRP E2EE integration", () => {
|
|
|
123
116
|
// Complete handshake
|
|
124
117
|
const { message: initMessage, ephemeralKeyPair: clientEphemeral } =
|
|
125
118
|
createHandshakeInit();
|
|
126
|
-
const { message: acceptMessage,
|
|
119
|
+
const { message: acceptMessage, sessionKeys: daemonKeys } =
|
|
127
120
|
processHandshakeInit(initMessage, daemonId, daemonIdentity);
|
|
128
|
-
const
|
|
121
|
+
const clientKeys = processHandshakeAccept(
|
|
129
122
|
acceptMessage,
|
|
130
123
|
daemonId,
|
|
131
124
|
daemonIdentity.publicKey,
|
|
132
125
|
clientEphemeral,
|
|
133
126
|
);
|
|
134
127
|
|
|
135
|
-
const clientSession = createClientSession(
|
|
136
|
-
|
|
137
|
-
daemonResult.sessionKeys,
|
|
138
|
-
);
|
|
139
|
-
const daemonSession = createDaemonSession(clientResult.sessionKeys);
|
|
128
|
+
const clientSession = createClientSession(clientId, daemonKeys);
|
|
129
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
140
130
|
|
|
141
131
|
// Send multiple messages from client to daemon
|
|
142
132
|
for (let i = 0; i < 5; i++) {
|
|
@@ -166,20 +156,17 @@ describe("SBRP E2EE integration", () => {
|
|
|
166
156
|
|
|
167
157
|
const { message: initMessage, ephemeralKeyPair: clientEphemeral } =
|
|
168
158
|
createHandshakeInit();
|
|
169
|
-
const { message: acceptMessage,
|
|
159
|
+
const { message: acceptMessage, sessionKeys: daemonKeys } =
|
|
170
160
|
processHandshakeInit(initMessage, daemonId, daemonIdentity);
|
|
171
|
-
const
|
|
161
|
+
const clientKeys = processHandshakeAccept(
|
|
172
162
|
acceptMessage,
|
|
173
163
|
daemonId,
|
|
174
164
|
daemonIdentity.publicKey,
|
|
175
165
|
clientEphemeral,
|
|
176
166
|
);
|
|
177
167
|
|
|
178
|
-
const clientSession = createClientSession(
|
|
179
|
-
|
|
180
|
-
daemonResult.sessionKeys,
|
|
181
|
-
);
|
|
182
|
-
const daemonSession = createDaemonSession(clientResult.sessionKeys);
|
|
168
|
+
const clientSession = createClientSession(clientId, daemonKeys);
|
|
169
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
183
170
|
|
|
184
171
|
// Empty message from client
|
|
185
172
|
const emptyMessage = new Uint8Array(0);
|
|
@@ -195,20 +182,17 @@ describe("SBRP E2EE integration", () => {
|
|
|
195
182
|
|
|
196
183
|
const { message: initMessage, ephemeralKeyPair: clientEphemeral } =
|
|
197
184
|
createHandshakeInit();
|
|
198
|
-
const { message: acceptMessage,
|
|
185
|
+
const { message: acceptMessage, sessionKeys: daemonKeys } =
|
|
199
186
|
processHandshakeInit(initMessage, daemonId, daemonIdentity);
|
|
200
|
-
const
|
|
187
|
+
const clientKeys = processHandshakeAccept(
|
|
201
188
|
acceptMessage,
|
|
202
189
|
daemonId,
|
|
203
190
|
daemonIdentity.publicKey,
|
|
204
191
|
clientEphemeral,
|
|
205
192
|
);
|
|
206
193
|
|
|
207
|
-
const clientSession = createClientSession(
|
|
208
|
-
|
|
209
|
-
daemonResult.sessionKeys,
|
|
210
|
-
);
|
|
211
|
-
const daemonSession = createDaemonSession(clientResult.sessionKeys);
|
|
194
|
+
const clientSession = createClientSession(clientId, daemonKeys);
|
|
195
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
212
196
|
|
|
213
197
|
// 32KB message
|
|
214
198
|
const largeMessage = new Uint8Array(32 * 1024);
|
|
@@ -230,12 +214,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
230
214
|
// Client A initiates handshake
|
|
231
215
|
const { message: initA, ephemeralKeyPair: ephemeralA } =
|
|
232
216
|
createHandshakeInit();
|
|
233
|
-
const { message: acceptA,
|
|
234
|
-
initA,
|
|
235
|
-
|
|
236
|
-
daemonIdentity,
|
|
237
|
-
);
|
|
238
|
-
const clientResultA = processHandshakeAccept(
|
|
217
|
+
const { message: acceptA, sessionKeys: daemonKeysA } =
|
|
218
|
+
processHandshakeInit(initA, daemonId, daemonIdentity);
|
|
219
|
+
const clientKeysA = processHandshakeAccept(
|
|
239
220
|
acceptA,
|
|
240
221
|
daemonId,
|
|
241
222
|
daemonIdentity.publicKey,
|
|
@@ -245,12 +226,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
245
226
|
// Client B initiates handshake
|
|
246
227
|
const { message: initB, ephemeralKeyPair: ephemeralB } =
|
|
247
228
|
createHandshakeInit();
|
|
248
|
-
const { message: acceptB,
|
|
249
|
-
initB,
|
|
250
|
-
|
|
251
|
-
daemonIdentity,
|
|
252
|
-
);
|
|
253
|
-
const clientResultB = processHandshakeAccept(
|
|
229
|
+
const { message: acceptB, sessionKeys: daemonKeysB } =
|
|
230
|
+
processHandshakeInit(initB, daemonId, daemonIdentity);
|
|
231
|
+
const clientKeysB = processHandshakeAccept(
|
|
254
232
|
acceptB,
|
|
255
233
|
daemonId,
|
|
256
234
|
daemonIdentity.publicKey,
|
|
@@ -258,24 +236,24 @@ describe("SBRP E2EE integration", () => {
|
|
|
258
236
|
);
|
|
259
237
|
|
|
260
238
|
// Verify different session keys for each client
|
|
261
|
-
expect(
|
|
262
|
-
|
|
239
|
+
expect(clientKeysA.clientToDaemon).not.toEqual(
|
|
240
|
+
clientKeysB.clientToDaemon,
|
|
263
241
|
);
|
|
264
|
-
expect(
|
|
265
|
-
|
|
242
|
+
expect(clientKeysA.daemonToClient).not.toEqual(
|
|
243
|
+
clientKeysB.daemonToClient,
|
|
266
244
|
);
|
|
267
245
|
|
|
268
246
|
// Create sessions
|
|
269
247
|
const clientSessionA = createClientSession(
|
|
270
248
|
asClientId("client-A"),
|
|
271
|
-
|
|
249
|
+
daemonKeysA,
|
|
272
250
|
);
|
|
273
251
|
const clientSessionB = createClientSession(
|
|
274
252
|
asClientId("client-B"),
|
|
275
|
-
|
|
253
|
+
daemonKeysB,
|
|
276
254
|
);
|
|
277
|
-
const daemonSessionA = createDaemonSession(
|
|
278
|
-
const daemonSessionB = createDaemonSession(
|
|
255
|
+
const daemonSessionA = createDaemonSession(clientKeysA);
|
|
256
|
+
const daemonSessionB = createDaemonSession(clientKeysB);
|
|
279
257
|
|
|
280
258
|
// Client A sends message
|
|
281
259
|
const messageA = textEncoder.encode("From client A");
|
|
@@ -299,12 +277,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
299
277
|
|
|
300
278
|
// Two separate sessions
|
|
301
279
|
const { message: init1, ephemeralKeyPair: eph1 } = createHandshakeInit();
|
|
302
|
-
const { message: accept1,
|
|
303
|
-
init1,
|
|
304
|
-
|
|
305
|
-
daemonIdentity,
|
|
306
|
-
);
|
|
307
|
-
const clientResult1 = processHandshakeAccept(
|
|
280
|
+
const { message: accept1, sessionKeys: daemonKeys1 } =
|
|
281
|
+
processHandshakeInit(init1, daemonId, daemonIdentity);
|
|
282
|
+
const clientKeys1 = processHandshakeAccept(
|
|
308
283
|
accept1,
|
|
309
284
|
daemonId,
|
|
310
285
|
daemonIdentity.publicKey,
|
|
@@ -312,12 +287,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
312
287
|
);
|
|
313
288
|
|
|
314
289
|
const { message: init2, ephemeralKeyPair: eph2 } = createHandshakeInit();
|
|
315
|
-
const { message: accept2,
|
|
316
|
-
init2,
|
|
317
|
-
|
|
318
|
-
daemonIdentity,
|
|
319
|
-
);
|
|
320
|
-
const clientResult2 = processHandshakeAccept(
|
|
290
|
+
const { message: accept2, sessionKeys: daemonKeys2 } =
|
|
291
|
+
processHandshakeInit(init2, daemonId, daemonIdentity);
|
|
292
|
+
const clientKeys2 = processHandshakeAccept(
|
|
321
293
|
accept2,
|
|
322
294
|
daemonId,
|
|
323
295
|
daemonIdentity.publicKey,
|
|
@@ -326,13 +298,13 @@ describe("SBRP E2EE integration", () => {
|
|
|
326
298
|
|
|
327
299
|
const clientSession1 = createClientSession(
|
|
328
300
|
asClientId("session-1"),
|
|
329
|
-
|
|
301
|
+
daemonKeys1,
|
|
330
302
|
);
|
|
331
303
|
const clientSession2 = createClientSession(
|
|
332
304
|
asClientId("session-2"),
|
|
333
|
-
|
|
305
|
+
daemonKeys2,
|
|
334
306
|
);
|
|
335
|
-
const daemonSession1 = createDaemonSession(
|
|
307
|
+
const daemonSession1 = createDaemonSession(clientKeys1);
|
|
336
308
|
|
|
337
309
|
// Encrypt with session 1
|
|
338
310
|
const message = textEncoder.encode("Secret message");
|
|
@@ -365,7 +337,7 @@ describe("SBRP E2EE integration", () => {
|
|
|
365
337
|
expect(decodedInit.initPublicKey).toEqual(initMessage.initPublicKey);
|
|
366
338
|
|
|
367
339
|
// Step 2: Daemon receives wire frame, processes, creates accept
|
|
368
|
-
const { message: acceptMessage,
|
|
340
|
+
const { message: acceptMessage, sessionKeys: daemonKeys } =
|
|
369
341
|
processHandshakeInit(decodedInit, daemonId, daemonIdentity);
|
|
370
342
|
const acceptWireFrame = encodeHandshakeAccept(sessionId, acceptMessage);
|
|
371
343
|
|
|
@@ -377,7 +349,7 @@ describe("SBRP E2EE integration", () => {
|
|
|
377
349
|
const decodedAccept = decodeHandshakeAccept(acceptFrame);
|
|
378
350
|
|
|
379
351
|
// Step 3: Client receives accept, verifies, derives keys
|
|
380
|
-
const
|
|
352
|
+
const clientKeys = processHandshakeAccept(
|
|
381
353
|
decodedAccept,
|
|
382
354
|
daemonId,
|
|
383
355
|
daemonIdentity.publicKey,
|
|
@@ -387,9 +359,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
387
359
|
// Create sessions
|
|
388
360
|
const clientSession = createClientSession(
|
|
389
361
|
asClientId("wire-client"),
|
|
390
|
-
|
|
362
|
+
daemonKeys,
|
|
391
363
|
);
|
|
392
|
-
const daemonSession = createDaemonSession(
|
|
364
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
393
365
|
|
|
394
366
|
// Step 4: Client sends encrypted data frame
|
|
395
367
|
const clientMessage = textEncoder.encode("Wire format test!");
|
|
@@ -453,24 +425,24 @@ describe("SBRP E2EE integration", () => {
|
|
|
453
425
|
expect(initFrame1.sessionId).not.toBe(initFrame2.sessionId);
|
|
454
426
|
|
|
455
427
|
// Process both handshakes
|
|
456
|
-
const { message: accept1,
|
|
428
|
+
const { message: accept1, sessionKeys: dk1 } = processHandshakeInit(
|
|
457
429
|
decodeHandshakeInit(initFrame1),
|
|
458
430
|
daemonId,
|
|
459
431
|
daemonIdentity,
|
|
460
432
|
);
|
|
461
|
-
const { message: accept2,
|
|
433
|
+
const { message: accept2, sessionKeys: dk2 } = processHandshakeInit(
|
|
462
434
|
decodeHandshakeInit(initFrame2),
|
|
463
435
|
daemonId,
|
|
464
436
|
daemonIdentity,
|
|
465
437
|
);
|
|
466
438
|
|
|
467
|
-
const
|
|
439
|
+
const ck1 = processHandshakeAccept(
|
|
468
440
|
accept1,
|
|
469
441
|
daemonId,
|
|
470
442
|
daemonIdentity.publicKey,
|
|
471
443
|
eph1,
|
|
472
444
|
);
|
|
473
|
-
const
|
|
445
|
+
const ck2 = processHandshakeAccept(
|
|
474
446
|
accept2,
|
|
475
447
|
daemonId,
|
|
476
448
|
daemonIdentity.publicKey,
|
|
@@ -478,10 +450,10 @@ describe("SBRP E2EE integration", () => {
|
|
|
478
450
|
);
|
|
479
451
|
|
|
480
452
|
// Create sessions
|
|
481
|
-
const cs1 = createClientSession(asClientId("c1"),
|
|
482
|
-
const cs2 = createClientSession(asClientId("c2"),
|
|
483
|
-
const ds1 = createDaemonSession(
|
|
484
|
-
const ds2 = createDaemonSession(
|
|
453
|
+
const cs1 = createClientSession(asClientId("c1"), dk1);
|
|
454
|
+
const cs2 = createClientSession(asClientId("c2"), dk2);
|
|
455
|
+
const ds1 = createDaemonSession(ck1);
|
|
456
|
+
const ds2 = createDaemonSession(ck2);
|
|
485
457
|
|
|
486
458
|
// Messages on session 100
|
|
487
459
|
const msg1 = encryptClientToDaemon(
|
|
@@ -557,13 +529,8 @@ describe("SBRP E2EE integration", () => {
|
|
|
557
529
|
const pinnedKey = realDaemonIdentity.publicKey;
|
|
558
530
|
|
|
559
531
|
// Verify first connection succeeds
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
daemonId,
|
|
563
|
-
pinnedKey,
|
|
564
|
-
eph1,
|
|
565
|
-
);
|
|
566
|
-
expect(result1.sessionKeys).toBeDefined();
|
|
532
|
+
const keys1 = processHandshakeAccept(accept1, daemonId, pinnedKey, eph1);
|
|
533
|
+
expect(keys1).toBeDefined();
|
|
567
534
|
|
|
568
535
|
// Second connection: Attacker tries to impersonate daemon
|
|
569
536
|
const { message: init2, ephemeralKeyPair: eph2 } = createHandshakeInit();
|
|
@@ -579,7 +546,7 @@ describe("SBRP E2EE integration", () => {
|
|
|
579
546
|
).toThrow(SbrpError);
|
|
580
547
|
expect(() =>
|
|
581
548
|
processHandshakeAccept(attackerAccept, daemonId, pinnedKey, eph2),
|
|
582
|
-
).toThrow(/
|
|
549
|
+
).toThrow(/identity key does not match pinned key/);
|
|
583
550
|
});
|
|
584
551
|
|
|
585
552
|
it("detects identity key change scenario", () => {
|
|
@@ -621,12 +588,12 @@ describe("SBRP E2EE integration", () => {
|
|
|
621
588
|
const daemonIdentity = generateIdentityKeyPair();
|
|
622
589
|
|
|
623
590
|
const { message: init, ephemeralKeyPair } = createHandshakeInit();
|
|
624
|
-
const { message: accept,
|
|
591
|
+
const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
|
|
625
592
|
init,
|
|
626
593
|
daemonId,
|
|
627
594
|
daemonIdentity,
|
|
628
595
|
);
|
|
629
|
-
const
|
|
596
|
+
const clientKeys = processHandshakeAccept(
|
|
630
597
|
accept,
|
|
631
598
|
daemonId,
|
|
632
599
|
daemonIdentity.publicKey,
|
|
@@ -635,9 +602,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
635
602
|
|
|
636
603
|
const clientSession = createClientSession(
|
|
637
604
|
asClientId("clear-test"),
|
|
638
|
-
|
|
605
|
+
daemonKeys,
|
|
639
606
|
);
|
|
640
|
-
const daemonSession = createDaemonSession(
|
|
607
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
641
608
|
|
|
642
609
|
// Encrypt before clearing
|
|
643
610
|
const message = textEncoder.encode("Before clear");
|
|
@@ -668,12 +635,12 @@ describe("SBRP E2EE integration", () => {
|
|
|
668
635
|
|
|
669
636
|
// First session
|
|
670
637
|
const { message: init1, ephemeralKeyPair: eph1 } = createHandshakeInit();
|
|
671
|
-
const { message: accept1,
|
|
638
|
+
const { message: accept1, sessionKeys: dk1 } = processHandshakeInit(
|
|
672
639
|
init1,
|
|
673
640
|
daemonId,
|
|
674
641
|
daemonIdentity,
|
|
675
642
|
);
|
|
676
|
-
const
|
|
643
|
+
const ck1 = processHandshakeAccept(
|
|
677
644
|
accept1,
|
|
678
645
|
daemonId,
|
|
679
646
|
daemonIdentity.publicKey,
|
|
@@ -682,21 +649,21 @@ describe("SBRP E2EE integration", () => {
|
|
|
682
649
|
|
|
683
650
|
const clientSession1 = createClientSession(
|
|
684
651
|
asClientId("session-old"),
|
|
685
|
-
|
|
652
|
+
dk1,
|
|
686
653
|
);
|
|
687
|
-
const daemonSession1 = createDaemonSession(
|
|
654
|
+
const daemonSession1 = createDaemonSession(ck1);
|
|
688
655
|
|
|
689
656
|
// Clear sessions
|
|
690
657
|
clearDaemonSession(daemonSession1);
|
|
691
658
|
|
|
692
659
|
// New handshake creates new session with new keys
|
|
693
660
|
const { message: init2, ephemeralKeyPair: eph2 } = createHandshakeInit();
|
|
694
|
-
const { message: accept2,
|
|
661
|
+
const { message: accept2, sessionKeys: dk2 } = processHandshakeInit(
|
|
695
662
|
init2,
|
|
696
663
|
daemonId,
|
|
697
664
|
daemonIdentity,
|
|
698
665
|
);
|
|
699
|
-
const
|
|
666
|
+
const ck2 = processHandshakeAccept(
|
|
700
667
|
accept2,
|
|
701
668
|
daemonId,
|
|
702
669
|
daemonIdentity.publicKey,
|
|
@@ -705,9 +672,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
705
672
|
|
|
706
673
|
const clientSession2 = createClientSession(
|
|
707
674
|
asClientId("session-new"),
|
|
708
|
-
|
|
675
|
+
dk2,
|
|
709
676
|
);
|
|
710
|
-
const daemonSession2 = createDaemonSession(
|
|
677
|
+
const daemonSession2 = createDaemonSession(ck2);
|
|
711
678
|
|
|
712
679
|
// New session works
|
|
713
680
|
const message = textEncoder.encode("New session message");
|
|
@@ -791,12 +758,12 @@ describe("SBRP E2EE integration", () => {
|
|
|
791
758
|
const daemonIdentity = generateIdentityKeyPair();
|
|
792
759
|
|
|
793
760
|
const { message: init, ephemeralKeyPair } = createHandshakeInit();
|
|
794
|
-
const { message: accept,
|
|
761
|
+
const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
|
|
795
762
|
init,
|
|
796
763
|
daemonId,
|
|
797
764
|
daemonIdentity,
|
|
798
765
|
);
|
|
799
|
-
const
|
|
766
|
+
const clientKeys = processHandshakeAccept(
|
|
800
767
|
accept,
|
|
801
768
|
daemonId,
|
|
802
769
|
daemonIdentity.publicKey,
|
|
@@ -805,9 +772,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
805
772
|
|
|
806
773
|
const clientSession = createClientSession(
|
|
807
774
|
asClientId("replay-test"),
|
|
808
|
-
|
|
775
|
+
daemonKeys,
|
|
809
776
|
);
|
|
810
|
-
const daemonSession = createDaemonSession(
|
|
777
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
811
778
|
|
|
812
779
|
// Send legitimate message
|
|
813
780
|
const message = textEncoder.encode("Original message");
|
|
@@ -832,12 +799,12 @@ describe("SBRP E2EE integration", () => {
|
|
|
832
799
|
|
|
833
800
|
// Session A
|
|
834
801
|
const { message: initA, ephemeralKeyPair: ephA } = createHandshakeInit();
|
|
835
|
-
const { message: acceptA,
|
|
802
|
+
const { message: acceptA, sessionKeys: dkA } = processHandshakeInit(
|
|
836
803
|
initA,
|
|
837
804
|
daemonId,
|
|
838
805
|
daemonIdentity,
|
|
839
806
|
);
|
|
840
|
-
const
|
|
807
|
+
const ckA = processHandshakeAccept(
|
|
841
808
|
acceptA,
|
|
842
809
|
daemonId,
|
|
843
810
|
daemonIdentity.publicKey,
|
|
@@ -846,27 +813,21 @@ describe("SBRP E2EE integration", () => {
|
|
|
846
813
|
|
|
847
814
|
// Session B
|
|
848
815
|
const { message: initB, ephemeralKeyPair: ephB } = createHandshakeInit();
|
|
849
|
-
const { message: acceptB,
|
|
816
|
+
const { message: acceptB, sessionKeys: dkB } = processHandshakeInit(
|
|
850
817
|
initB,
|
|
851
818
|
daemonId,
|
|
852
819
|
daemonIdentity,
|
|
853
820
|
);
|
|
854
|
-
const
|
|
821
|
+
const ckB = processHandshakeAccept(
|
|
855
822
|
acceptB,
|
|
856
823
|
daemonId,
|
|
857
824
|
daemonIdentity.publicKey,
|
|
858
825
|
ephB,
|
|
859
826
|
);
|
|
860
827
|
|
|
861
|
-
const clientSessionA = createClientSession(
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
);
|
|
865
|
-
const clientSessionB = createClientSession(
|
|
866
|
-
asClientId("B"),
|
|
867
|
-
drB.sessionKeys,
|
|
868
|
-
);
|
|
869
|
-
const daemonSessionB = createDaemonSession(crB.sessionKeys);
|
|
828
|
+
const clientSessionA = createClientSession(asClientId("A"), dkA);
|
|
829
|
+
const clientSessionB = createClientSession(asClientId("B"), dkB);
|
|
830
|
+
const daemonSessionB = createDaemonSession(ckB);
|
|
870
831
|
|
|
871
832
|
// Encrypt with session B keys
|
|
872
833
|
const message = textEncoder.encode("Wrong session test");
|
|
@@ -887,12 +848,12 @@ describe("SBRP E2EE integration", () => {
|
|
|
887
848
|
const daemonIdentity = generateIdentityKeyPair();
|
|
888
849
|
|
|
889
850
|
const { message: init, ephemeralKeyPair } = createHandshakeInit();
|
|
890
|
-
const { message: accept,
|
|
851
|
+
const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
|
|
891
852
|
init,
|
|
892
853
|
daemonId,
|
|
893
854
|
daemonIdentity,
|
|
894
855
|
);
|
|
895
|
-
const
|
|
856
|
+
const clientKeys = processHandshakeAccept(
|
|
896
857
|
accept,
|
|
897
858
|
daemonId,
|
|
898
859
|
daemonIdentity.publicKey,
|
|
@@ -901,16 +862,17 @@ describe("SBRP E2EE integration", () => {
|
|
|
901
862
|
|
|
902
863
|
const clientSession = createClientSession(
|
|
903
864
|
asClientId("tamper-test"),
|
|
904
|
-
|
|
865
|
+
daemonKeys,
|
|
905
866
|
);
|
|
906
|
-
const daemonSession = createDaemonSession(
|
|
867
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
907
868
|
|
|
908
869
|
const message = textEncoder.encode("Tamper test");
|
|
909
870
|
const encrypted = encryptClientToDaemon(daemonSession, message);
|
|
910
871
|
|
|
911
872
|
// Tamper with ciphertext (flip a bit in the middle)
|
|
912
873
|
const tamperedData = new Uint8Array(encrypted.data);
|
|
913
|
-
tamperedData
|
|
874
|
+
expect(tamperedData.length).toBeGreaterThan(20);
|
|
875
|
+
tamperedData[20] = tamperedData[20]! ^ 0x01; // Flip a bit
|
|
914
876
|
|
|
915
877
|
const tamperedMessage = {
|
|
916
878
|
...encrypted,
|
|
@@ -928,12 +890,12 @@ describe("SBRP E2EE integration", () => {
|
|
|
928
890
|
const daemonIdentity = generateIdentityKeyPair();
|
|
929
891
|
|
|
930
892
|
const { message: init, ephemeralKeyPair } = createHandshakeInit();
|
|
931
|
-
const { message: accept,
|
|
893
|
+
const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
|
|
932
894
|
init,
|
|
933
895
|
daemonId,
|
|
934
896
|
daemonIdentity,
|
|
935
897
|
);
|
|
936
|
-
const
|
|
898
|
+
const clientKeys = processHandshakeAccept(
|
|
937
899
|
accept,
|
|
938
900
|
daemonId,
|
|
939
901
|
daemonIdentity.publicKey,
|
|
@@ -942,9 +904,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
942
904
|
|
|
943
905
|
const clientSession = createClientSession(
|
|
944
906
|
asClientId("direction-test"),
|
|
945
|
-
|
|
907
|
+
daemonKeys,
|
|
946
908
|
);
|
|
947
|
-
const daemonSession = createDaemonSession(
|
|
909
|
+
const daemonSession = createDaemonSession(clientKeys);
|
|
948
910
|
|
|
949
911
|
// Client sends to daemon
|
|
950
912
|
const clientMessage = textEncoder.encode("Client to daemon");
|
|
@@ -975,12 +937,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
975
937
|
// Create many sessions
|
|
976
938
|
for (let i = 0; i < numSessions; i++) {
|
|
977
939
|
const { message: init, ephemeralKeyPair } = createHandshakeInit();
|
|
978
|
-
const { message: accept,
|
|
979
|
-
init,
|
|
980
|
-
|
|
981
|
-
daemonIdentity,
|
|
982
|
-
);
|
|
983
|
-
const clientResult = processHandshakeAccept(
|
|
940
|
+
const { message: accept, sessionKeys: daemonKeys } =
|
|
941
|
+
processHandshakeInit(init, daemonId, daemonIdentity);
|
|
942
|
+
const clientKeys = processHandshakeAccept(
|
|
984
943
|
accept,
|
|
985
944
|
daemonId,
|
|
986
945
|
daemonIdentity.publicKey,
|
|
@@ -990,9 +949,9 @@ describe("SBRP E2EE integration", () => {
|
|
|
990
949
|
sessions.push({
|
|
991
950
|
clientSession: createClientSession(
|
|
992
951
|
asClientId(`stress-${i}`),
|
|
993
|
-
|
|
952
|
+
daemonKeys,
|
|
994
953
|
),
|
|
995
|
-
daemonSession: createDaemonSession(
|
|
954
|
+
daemonSession: createDaemonSession(clientKeys),
|
|
996
955
|
id: i,
|
|
997
956
|
});
|
|
998
957
|
}
|
|
@@ -1014,11 +973,11 @@ describe("SBRP E2EE integration", () => {
|
|
|
1014
973
|
|
|
1015
974
|
// Verify session isolation - try to decrypt with wrong session
|
|
1016
975
|
const msg0 = encryptClientToDaemon(
|
|
1017
|
-
sessions[0]
|
|
976
|
+
sessions[0]!.daemonSession,
|
|
1018
977
|
textEncoder.encode("Session 0"),
|
|
1019
978
|
);
|
|
1020
979
|
expect(() =>
|
|
1021
|
-
decryptClientToDaemon(sessions[1]
|
|
980
|
+
decryptClientToDaemon(sessions[1]!.clientSession, msg0),
|
|
1022
981
|
).toThrow(SbrpError);
|
|
1023
982
|
});
|
|
1024
983
|
});
|
package/src/session.test.ts
CHANGED
|
@@ -195,15 +195,15 @@ describe("session", () => {
|
|
|
195
195
|
|
|
196
196
|
// Decrypt latest 128 (within window)
|
|
197
197
|
for (let i = 199; i >= 72; i--) {
|
|
198
|
-
decryptClientToDaemon(clientSession, messages[i]);
|
|
198
|
+
decryptClientToDaemon(clientSession, messages[i]!);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// Message with seq=0 is now outside the window (too old)
|
|
202
|
-
expect(() => decryptClientToDaemon(clientSession, messages[0])).toThrow(
|
|
202
|
+
expect(() => decryptClientToDaemon(clientSession, messages[0]!)).toThrow(
|
|
203
203
|
SbrpError,
|
|
204
204
|
);
|
|
205
205
|
try {
|
|
206
|
-
decryptClientToDaemon(clientSession, messages[0]);
|
|
206
|
+
decryptClientToDaemon(clientSession, messages[0]!);
|
|
207
207
|
} catch (e) {
|
|
208
208
|
expect(e).toBeInstanceOf(SbrpError);
|
|
209
209
|
expect((e as SbrpError).code).toBe(SbrpErrorCode.SequenceError);
|
|
@@ -227,11 +227,11 @@ describe("session", () => {
|
|
|
227
227
|
|
|
228
228
|
// Decrypt latest 128 (within window)
|
|
229
229
|
for (let i = 199; i >= 72; i--) {
|
|
230
|
-
decryptDaemonToClient(daemonSession, messages[i]);
|
|
230
|
+
decryptDaemonToClient(daemonSession, messages[i]!);
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
// Message with seq=0 is now outside the window (too old)
|
|
234
|
-
expect(() => decryptDaemonToClient(daemonSession, messages[0])).toThrow(
|
|
234
|
+
expect(() => decryptDaemonToClient(daemonSession, messages[0]!)).toThrow(
|
|
235
235
|
SbrpError,
|
|
236
236
|
);
|
|
237
237
|
});
|
|
@@ -377,7 +377,8 @@ describe("session", () => {
|
|
|
377
377
|
|
|
378
378
|
// Tamper with the ciphertext (flip a bit after nonce, in the ciphertext area)
|
|
379
379
|
const tamperedData = new Uint8Array(encrypted.data);
|
|
380
|
-
tamperedData
|
|
380
|
+
expect(tamperedData.length).toBeGreaterThan(20);
|
|
381
|
+
tamperedData[20] = tamperedData[20]! ^ 0xff;
|
|
381
382
|
const tamperedMessage: EncryptedMessage = {
|
|
382
383
|
type: "encrypted",
|
|
383
384
|
seq: encrypted.seq,
|
|
@@ -413,7 +414,8 @@ describe("session", () => {
|
|
|
413
414
|
|
|
414
415
|
// Tamper with the auth tag (last 16 bytes)
|
|
415
416
|
const tamperedData = new Uint8Array(encrypted.data);
|
|
416
|
-
tamperedData[tamperedData.length - 1]
|
|
417
|
+
tamperedData[tamperedData.length - 1] =
|
|
418
|
+
tamperedData[tamperedData.length - 1]! ^ 0x01;
|
|
417
419
|
const tamperedMessage: EncryptedMessage = {
|
|
418
420
|
type: "encrypted",
|
|
419
421
|
seq: encrypted.seq,
|
|
@@ -450,7 +452,7 @@ describe("session", () => {
|
|
|
450
452
|
// Tamper with the direction bytes in nonce (first 4 bytes)
|
|
451
453
|
// This will change the nonce used for decryption
|
|
452
454
|
const tamperedData = new Uint8Array(encrypted.data);
|
|
453
|
-
tamperedData[0]
|
|
455
|
+
tamperedData[0] = tamperedData[0]! ^ 0xff;
|
|
454
456
|
const tamperedMessage: EncryptedMessage = {
|
|
455
457
|
type: "encrypted",
|
|
456
458
|
seq: encrypted.seq,
|
|
@@ -724,14 +726,14 @@ describe("session", () => {
|
|
|
724
726
|
// Decrypt in random order
|
|
725
727
|
const c2dOrder = [5, 2, 8, 0, 3, 9, 1, 6, 4, 7];
|
|
726
728
|
for (const i of c2dOrder) {
|
|
727
|
-
expect(decryptClientToDaemon(clientSession, c2dMessages[i])).toEqual(
|
|
729
|
+
expect(decryptClientToDaemon(clientSession, c2dMessages[i]!)).toEqual(
|
|
728
730
|
new Uint8Array([i]),
|
|
729
731
|
);
|
|
730
732
|
}
|
|
731
733
|
|
|
732
734
|
const d2cOrder = [7, 4, 1, 9, 6, 3, 0, 8, 2, 5];
|
|
733
735
|
for (const i of d2cOrder) {
|
|
734
|
-
expect(decryptDaemonToClient(daemonSession, d2cMessages[i])).toEqual(
|
|
736
|
+
expect(decryptDaemonToClient(daemonSession, d2cMessages[i]!)).toEqual(
|
|
735
737
|
new Uint8Array([i + 100]),
|
|
736
738
|
);
|
|
737
739
|
}
|