@sideband/secure-relay 0.0.1 → 0.2.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 +33 -11
- package/{LICENSE → dist/LICENSE} +3 -2
- package/package.json +1 -2
- package/src/constants.ts +27 -2
- package/src/crypto.test.ts +644 -0
- package/src/crypto.ts +1 -2
- package/src/frame.test.ts +820 -0
- package/src/frame.ts +771 -0
- package/src/handshake.test.ts +325 -0
- package/src/handshake.ts +1 -2
- package/src/index.ts +44 -2
- package/src/integration.test.ts +1025 -0
- package/src/replay.test.ts +306 -0
- package/src/replay.ts +1 -2
- package/src/session.test.ts +767 -0
- package/src/session.ts +1 -2
- package/src/types.ts +85 -18
- package/dist/.tsbuildinfo +0 -1
- package/dist/constants.d.ts +0 -33
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -35
- package/dist/constants.js.map +0 -1
- package/dist/crypto.d.ts +0 -70
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js +0 -145
- package/dist/crypto.js.map +0 -1
- package/dist/handshake.d.ts +0 -42
- package/dist/handshake.d.ts.map +0 -1
- package/dist/handshake.js +0 -83
- package/dist/handshake.js.map +0 -1
- package/dist/index.d.ts +0 -45
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -11
- package/dist/index.js.map +0 -1
- package/dist/replay.d.ts +0 -32
- package/dist/replay.d.ts.map +0 -1
- package/dist/replay.js +0 -89
- package/dist/replay.js.map +0 -1
- package/dist/session.d.ts +0 -67
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -123
- package/dist/session.js.map +0 -1
- package/dist/types.d.ts +0 -79
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -39
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @sideband/secure-relay
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Low-level E2EE primitives for the Sideband Relay Protocol (SBRP).
|
|
4
|
+
|
|
5
|
+
Implements authenticated handshake, key derivation, and message encryption for secure browser ↔ daemon communication via untrusted relay servers. Most applications should use `@sideband/peer` instead of this package directly.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
@@ -10,6 +12,15 @@ End-to-end encrypted communication between browsers and daemons via untrusted re
|
|
|
10
12
|
- **TOFU identity pinning** — Trust-on-first-use with key change detection
|
|
11
13
|
- **Replay protection** — Bitmap-based sequence window
|
|
12
14
|
|
|
15
|
+
## Non-Goals
|
|
16
|
+
|
|
17
|
+
This package intentionally does NOT:
|
|
18
|
+
|
|
19
|
+
- Handle network transport or WebSockets
|
|
20
|
+
- Manage session lifecycle or reconnection
|
|
21
|
+
- Persist identity keys or TOFU pins
|
|
22
|
+
- Implement relay authentication or tokens
|
|
23
|
+
|
|
13
24
|
## Install
|
|
14
25
|
|
|
15
26
|
```bash
|
|
@@ -18,7 +29,7 @@ bun add @sideband/secure-relay
|
|
|
18
29
|
|
|
19
30
|
## Usage
|
|
20
31
|
|
|
21
|
-
```
|
|
32
|
+
```ts
|
|
22
33
|
import {
|
|
23
34
|
generateIdentityKeyPair,
|
|
24
35
|
createHandshakeInit,
|
|
@@ -34,7 +45,8 @@ import {
|
|
|
34
45
|
asClientId,
|
|
35
46
|
} from "@sideband/secure-relay";
|
|
36
47
|
|
|
37
|
-
// Daemon: generate identity keypair
|
|
48
|
+
// Daemon: generate identity keypair ONCE and persist securely.
|
|
49
|
+
// Regenerating causes TOFU mismatch warnings for all clients.
|
|
38
50
|
const identity = generateIdentityKeyPair();
|
|
39
51
|
const daemonId = asDaemonId("my-daemon");
|
|
40
52
|
|
|
@@ -61,25 +73,35 @@ const { sessionKeys } = processHandshakeAccept(
|
|
|
61
73
|
);
|
|
62
74
|
const daemonSession = createDaemonSession(sessionKeys);
|
|
63
75
|
|
|
64
|
-
// Encrypt/decrypt messages
|
|
76
|
+
// Encrypt/decrypt messages (sessions are stateful — do not clone)
|
|
65
77
|
const encrypted = encryptClientToDaemon(daemonSession, plaintext);
|
|
66
78
|
const decrypted = decryptClientToDaemon(clientSession, encrypted);
|
|
67
79
|
```
|
|
68
80
|
|
|
81
|
+
## TOFU Security
|
|
82
|
+
|
|
83
|
+
Identity keys use trust-on-first-use (TOFU) pinning:
|
|
84
|
+
|
|
85
|
+
- Pin daemon identity keys on first successful handshake
|
|
86
|
+
- Never accept key changes silently — `identity_key_changed` indicates potential MITM
|
|
87
|
+
- On mismatch, present both fingerprints and require explicit user approval
|
|
88
|
+
|
|
69
89
|
## Error Handling
|
|
70
90
|
|
|
71
91
|
All errors throw `SbrpError` with a specific `code`:
|
|
72
92
|
|
|
73
|
-
| Code | Meaning |
|
|
74
|
-
| ---------------------- | ----------------------------------------- |
|
|
75
|
-
| `identity_key_changed` | Pinned key doesn't match (potential MITM) |
|
|
76
|
-
| `handshake_failed` | Signature verification failed |
|
|
77
|
-
| `decrypt_failed` | Message authentication failed |
|
|
78
|
-
| `sequence_error` | Replay detected or sequence out of window |
|
|
93
|
+
| Code | Meaning | Recovery |
|
|
94
|
+
| ---------------------- | ----------------------------------------- | ------------------------- |
|
|
95
|
+
| `identity_key_changed` | Pinned key doesn't match (potential MITM) | Close session, alert user |
|
|
96
|
+
| `handshake_failed` | Signature verification failed | Close session |
|
|
97
|
+
| `decrypt_failed` | Message authentication failed | Close session |
|
|
98
|
+
| `sequence_error` | Replay detected or sequence out of window | Close session |
|
|
99
|
+
|
|
100
|
+
All errors are fatal — close the session and re-handshake.
|
|
79
101
|
|
|
80
102
|
## Specification
|
|
81
103
|
|
|
82
|
-
See [
|
|
104
|
+
See the [SBRP protocol specification](https://sideband.tech/protocols/sbrp/) for implementation details.
|
|
83
105
|
|
|
84
106
|
## License
|
|
85
107
|
|
package/{LICENSE → dist/LICENSE}
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
Apache License
|
|
2
3
|
Version 2.0, January 2004
|
|
3
4
|
http://www.apache.org/licenses/
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"Contribution" shall mean any work of authorship, including
|
|
49
50
|
the original version of the Work and any modifications or additions
|
|
50
51
|
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
-
submitted to
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
53
|
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
54
|
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
55
|
means any form of electronic, verbal, or written communication sent
|
|
@@ -175,7 +176,7 @@
|
|
|
175
176
|
|
|
176
177
|
END OF TERMS AND CONDITIONS
|
|
177
178
|
|
|
178
|
-
Copyright 2025
|
|
179
|
+
Copyright 2025 Sideband Project
|
|
179
180
|
|
|
180
181
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
182
|
you may not use this file except in compliance with the License.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sideband/secure-relay",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Secure Relay Protocol (SBRP): E2EE handshake, session encryption, and TOFU identity pinning for relay-mediated communication.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"exports": {
|
|
41
41
|
".": {
|
|
42
42
|
"types": "./dist/index.d.ts",
|
|
43
|
-
"bun": "./src/index.ts",
|
|
44
43
|
"default": "./dist/index.js"
|
|
45
44
|
},
|
|
46
45
|
"./package.json": "./package.json"
|
package/src/constants.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// SPDX-
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Protocol constants for Sideband Relay Protocol (SBRP).
|
|
@@ -47,3 +46,29 @@ export const DEFAULT_REPLAY_WINDOW_SIZE = 64n;
|
|
|
47
46
|
/** Direction bytes in nonce (4 bytes, big-endian) */
|
|
48
47
|
export const DIRECTION_CLIENT_TO_DAEMON = new Uint8Array([0, 0, 0, 1]);
|
|
49
48
|
export const DIRECTION_DAEMON_TO_CLIENT = new Uint8Array([0, 0, 0, 2]);
|
|
49
|
+
|
|
50
|
+
// Wire format constants (§13)
|
|
51
|
+
|
|
52
|
+
/** Frame header size: type (1) + length (4) + sessionId (8) */
|
|
53
|
+
export const FRAME_HEADER_SIZE = 13;
|
|
54
|
+
|
|
55
|
+
/** Maximum payload size in bytes (64 KB) */
|
|
56
|
+
export const MAX_PAYLOAD_SIZE = 65536;
|
|
57
|
+
|
|
58
|
+
/** HandshakeInit payload: X25519 ephemeral public key */
|
|
59
|
+
export const HANDSHAKE_INIT_PAYLOAD_SIZE = 32;
|
|
60
|
+
|
|
61
|
+
/** HandshakeAccept payload: X25519 ephemeral (32) + Ed25519 signature (64) */
|
|
62
|
+
export const HANDSHAKE_ACCEPT_PAYLOAD_SIZE = 96;
|
|
63
|
+
|
|
64
|
+
/** Minimum encrypted payload: nonce (12) + authTag (16), no plaintext */
|
|
65
|
+
export const MIN_ENCRYPTED_PAYLOAD_SIZE = NONCE_LENGTH + AUTH_TAG_LENGTH;
|
|
66
|
+
|
|
67
|
+
/** Control payload minimum: code (2 bytes) */
|
|
68
|
+
export const MIN_CONTROL_PAYLOAD_SIZE = 2;
|
|
69
|
+
|
|
70
|
+
/** Signal payload size: signal (1) + reason (1) */
|
|
71
|
+
export const SIGNAL_PAYLOAD_SIZE = 2;
|
|
72
|
+
|
|
73
|
+
/** Maximum Ping/Pong payload size (0-8 bytes for RTT nonce) */
|
|
74
|
+
export const MAX_PING_PAYLOAD_SIZE = 8;
|