@sideband/secure-relay 0.2.1 → 0.2.3

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 (46) hide show
  1. package/README.md +27 -8
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/constants.d.ts +49 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +51 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/crypto.d.ts +70 -0
  8. package/dist/crypto.d.ts.map +1 -0
  9. package/dist/crypto.js +144 -0
  10. package/dist/crypto.js.map +1 -0
  11. package/dist/frame.d.ts +213 -0
  12. package/dist/frame.d.ts.map +1 -0
  13. package/dist/frame.js +547 -0
  14. package/dist/frame.js.map +1 -0
  15. package/dist/handshake.d.ts +39 -0
  16. package/dist/handshake.d.ts.map +1 -0
  17. package/dist/handshake.js +93 -0
  18. package/dist/handshake.js.map +1 -0
  19. package/dist/index.d.ts +46 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +12 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/replay.d.ts +32 -0
  24. package/dist/replay.d.ts.map +1 -0
  25. package/dist/replay.js +88 -0
  26. package/dist/replay.js.map +1 -0
  27. package/dist/session.d.ts +67 -0
  28. package/dist/session.d.ts.map +1 -0
  29. package/dist/session.js +122 -0
  30. package/dist/session.js.map +1 -0
  31. package/dist/types.d.ts +119 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +80 -0
  34. package/dist/types.js.map +1 -0
  35. package/package.json +4 -4
  36. package/src/constants.ts +3 -3
  37. package/src/crypto.test.ts +5 -5
  38. package/src/crypto.ts +9 -9
  39. package/src/frame.test.ts +59 -10
  40. package/src/frame.ts +101 -77
  41. package/src/handshake.test.ts +29 -41
  42. package/src/handshake.ts +25 -27
  43. package/src/index.ts +4 -10
  44. package/src/integration.test.ts +97 -138
  45. package/src/session.test.ts +12 -10
  46. package/src/types.ts +1 -12
@@ -33,7 +33,7 @@ import {
33
33
  encryptClientToDaemon,
34
34
  encryptDaemonToClient,
35
35
  } from "./session.js";
36
- import { asDaemonId, asClientId, SbrpError, SbrpErrorCode } from "./types.js";
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, result: daemonResult } =
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 clientResult = processHandshakeAccept(
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(clientResult.sessionKeys.clientToDaemon).toEqual(
71
- daemonResult.sessionKeys.clientToDaemon,
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
- clientId,
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, result: daemonResult } =
119
+ const { message: acceptMessage, sessionKeys: daemonKeys } =
127
120
  processHandshakeInit(initMessage, daemonId, daemonIdentity);
128
- const clientResult = processHandshakeAccept(
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
- clientId,
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, result: daemonResult } =
159
+ const { message: acceptMessage, sessionKeys: daemonKeys } =
170
160
  processHandshakeInit(initMessage, daemonId, daemonIdentity);
171
- const clientResult = processHandshakeAccept(
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
- clientId,
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, result: daemonResult } =
185
+ const { message: acceptMessage, sessionKeys: daemonKeys } =
199
186
  processHandshakeInit(initMessage, daemonId, daemonIdentity);
200
- const clientResult = processHandshakeAccept(
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
- clientId,
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, result: daemonResultA } = processHandshakeInit(
234
- initA,
235
- daemonId,
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, result: daemonResultB } = processHandshakeInit(
249
- initB,
250
- daemonId,
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(clientResultA.sessionKeys.clientToDaemon).not.toEqual(
262
- clientResultB.sessionKeys.clientToDaemon,
239
+ expect(clientKeysA.clientToDaemon).not.toEqual(
240
+ clientKeysB.clientToDaemon,
263
241
  );
264
- expect(clientResultA.sessionKeys.daemonToClient).not.toEqual(
265
- clientResultB.sessionKeys.daemonToClient,
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
- daemonResultA.sessionKeys,
249
+ daemonKeysA,
272
250
  );
273
251
  const clientSessionB = createClientSession(
274
252
  asClientId("client-B"),
275
- daemonResultB.sessionKeys,
253
+ daemonKeysB,
276
254
  );
277
- const daemonSessionA = createDaemonSession(clientResultA.sessionKeys);
278
- const daemonSessionB = createDaemonSession(clientResultB.sessionKeys);
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, result: daemonResult1 } = processHandshakeInit(
303
- init1,
304
- daemonId,
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, result: daemonResult2 } = processHandshakeInit(
316
- init2,
317
- daemonId,
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
- daemonResult1.sessionKeys,
301
+ daemonKeys1,
330
302
  );
331
303
  const clientSession2 = createClientSession(
332
304
  asClientId("session-2"),
333
- daemonResult2.sessionKeys,
305
+ daemonKeys2,
334
306
  );
335
- const daemonSession1 = createDaemonSession(clientResult1.sessionKeys);
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, result: daemonResult } =
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 clientResult = processHandshakeAccept(
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
- daemonResult.sessionKeys,
362
+ daemonKeys,
391
363
  );
392
- const daemonSession = createDaemonSession(clientResult.sessionKeys);
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, result: dr1 } = processHandshakeInit(
428
+ const { message: accept1, sessionKeys: dk1 } = processHandshakeInit(
457
429
  decodeHandshakeInit(initFrame1),
458
430
  daemonId,
459
431
  daemonIdentity,
460
432
  );
461
- const { message: accept2, result: dr2 } = processHandshakeInit(
433
+ const { message: accept2, sessionKeys: dk2 } = processHandshakeInit(
462
434
  decodeHandshakeInit(initFrame2),
463
435
  daemonId,
464
436
  daemonIdentity,
465
437
  );
466
438
 
467
- const cr1 = processHandshakeAccept(
439
+ const ck1 = processHandshakeAccept(
468
440
  accept1,
469
441
  daemonId,
470
442
  daemonIdentity.publicKey,
471
443
  eph1,
472
444
  );
473
- const cr2 = processHandshakeAccept(
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"), dr1.sessionKeys);
482
- const cs2 = createClientSession(asClientId("c2"), dr2.sessionKeys);
483
- const ds1 = createDaemonSession(cr1.sessionKeys);
484
- const ds2 = createDaemonSession(cr2.sessionKeys);
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 result1 = processHandshakeAccept(
561
- accept1,
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(/Signature verification failed/);
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, result: daemonResult } = processHandshakeInit(
591
+ const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
625
592
  init,
626
593
  daemonId,
627
594
  daemonIdentity,
628
595
  );
629
- const clientResult = processHandshakeAccept(
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
- daemonResult.sessionKeys,
605
+ daemonKeys,
639
606
  );
640
- const daemonSession = createDaemonSession(clientResult.sessionKeys);
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, result: dr1 } = processHandshakeInit(
638
+ const { message: accept1, sessionKeys: dk1 } = processHandshakeInit(
672
639
  init1,
673
640
  daemonId,
674
641
  daemonIdentity,
675
642
  );
676
- const cr1 = processHandshakeAccept(
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
- dr1.sessionKeys,
652
+ dk1,
686
653
  );
687
- const daemonSession1 = createDaemonSession(cr1.sessionKeys);
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, result: dr2 } = processHandshakeInit(
661
+ const { message: accept2, sessionKeys: dk2 } = processHandshakeInit(
695
662
  init2,
696
663
  daemonId,
697
664
  daemonIdentity,
698
665
  );
699
- const cr2 = processHandshakeAccept(
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
- dr2.sessionKeys,
675
+ dk2,
709
676
  );
710
- const daemonSession2 = createDaemonSession(cr2.sessionKeys);
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, result: daemonResult } = processHandshakeInit(
761
+ const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
795
762
  init,
796
763
  daemonId,
797
764
  daemonIdentity,
798
765
  );
799
- const clientResult = processHandshakeAccept(
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
- daemonResult.sessionKeys,
775
+ daemonKeys,
809
776
  );
810
- const daemonSession = createDaemonSession(clientResult.sessionKeys);
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, result: drA } = processHandshakeInit(
802
+ const { message: acceptA, sessionKeys: dkA } = processHandshakeInit(
836
803
  initA,
837
804
  daemonId,
838
805
  daemonIdentity,
839
806
  );
840
- const crA = processHandshakeAccept(
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, result: drB } = processHandshakeInit(
816
+ const { message: acceptB, sessionKeys: dkB } = processHandshakeInit(
850
817
  initB,
851
818
  daemonId,
852
819
  daemonIdentity,
853
820
  );
854
- const crB = processHandshakeAccept(
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
- asClientId("A"),
863
- drA.sessionKeys,
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, result: daemonResult } = processHandshakeInit(
851
+ const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
891
852
  init,
892
853
  daemonId,
893
854
  daemonIdentity,
894
855
  );
895
- const clientResult = processHandshakeAccept(
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
- daemonResult.sessionKeys,
865
+ daemonKeys,
905
866
  );
906
- const daemonSession = createDaemonSession(clientResult.sessionKeys);
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[20] ^= 0x01; // Flip a bit
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, result: daemonResult } = processHandshakeInit(
893
+ const { message: accept, sessionKeys: daemonKeys } = processHandshakeInit(
932
894
  init,
933
895
  daemonId,
934
896
  daemonIdentity,
935
897
  );
936
- const clientResult = processHandshakeAccept(
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
- daemonResult.sessionKeys,
907
+ daemonKeys,
946
908
  );
947
- const daemonSession = createDaemonSession(clientResult.sessionKeys);
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, result: daemonResult } = processHandshakeInit(
979
- init,
980
- daemonId,
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
- daemonResult.sessionKeys,
952
+ daemonKeys,
994
953
  ),
995
- daemonSession: createDaemonSession(clientResult.sessionKeys),
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].daemonSession,
976
+ sessions[0]!.daemonSession,
1018
977
  textEncoder.encode("Session 0"),
1019
978
  );
1020
979
  expect(() =>
1021
- decryptClientToDaemon(sessions[1].clientSession, msg0),
980
+ decryptClientToDaemon(sessions[1]!.clientSession, msg0),
1022
981
  ).toThrow(SbrpError);
1023
982
  });
1024
983
  });
@@ -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[20] ^= 0xff;
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] ^= 0x01;
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] ^= 0xff;
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
  }