@sideband/secure-relay 0.2.1 → 0.2.2
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 +21 -4
- package/package.json +4 -4
- package/src/crypto.ts +9 -9
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Implements authenticated handshake, key derivation, and message encryption for s
|
|
|
12
12
|
- **TOFU identity pinning** — Trust-on-first-use with key change detection
|
|
13
13
|
- **Replay protection** — Bitmap-based sequence window
|
|
14
14
|
|
|
15
|
-
## Non-
|
|
15
|
+
## Non-goals
|
|
16
16
|
|
|
17
17
|
This package intentionally does NOT:
|
|
18
18
|
|
|
@@ -21,6 +21,23 @@ This package intentionally does NOT:
|
|
|
21
21
|
- Persist identity keys or TOFU pins
|
|
22
22
|
- Implement relay authentication or tokens
|
|
23
23
|
|
|
24
|
+
## Threat model
|
|
25
|
+
|
|
26
|
+
This package protects the **payload** of messages between a browser client and a daemon via an untrusted relay. Specifically:
|
|
27
|
+
|
|
28
|
+
- The relay cannot read or tamper with message content (authenticated encryption).
|
|
29
|
+
- A MITM cannot impersonate the daemon without its Ed25519 private key (signature verification on handshake).
|
|
30
|
+
- Replayed messages are rejected within the sequence window.
|
|
31
|
+
|
|
32
|
+
It does **not** protect against:
|
|
33
|
+
|
|
34
|
+
- Compromise of the daemon's identity key (store it securely; if lost, all clients see a TOFU mismatch).
|
|
35
|
+
- Traffic analysis (message sizes and timing are visible to the relay).
|
|
36
|
+
- Key storage security — this package has no opinion on where keys live; that's the caller's responsibility.
|
|
37
|
+
- Denial of service from a malicious relay (the relay can drop or delay messages).
|
|
38
|
+
|
|
39
|
+
This implementation has not undergone a formal third-party security audit. Use accordingly.
|
|
40
|
+
|
|
24
41
|
## Install
|
|
25
42
|
|
|
26
43
|
```bash
|
|
@@ -78,7 +95,7 @@ const encrypted = encryptClientToDaemon(daemonSession, plaintext);
|
|
|
78
95
|
const decrypted = decryptClientToDaemon(clientSession, encrypted);
|
|
79
96
|
```
|
|
80
97
|
|
|
81
|
-
## TOFU
|
|
98
|
+
## TOFU security
|
|
82
99
|
|
|
83
100
|
Identity keys use trust-on-first-use (TOFU) pinning:
|
|
84
101
|
|
|
@@ -86,7 +103,7 @@ Identity keys use trust-on-first-use (TOFU) pinning:
|
|
|
86
103
|
- Never accept key changes silently — `identity_key_changed` indicates potential MITM
|
|
87
104
|
- On mismatch, present both fingerprints and require explicit user approval
|
|
88
105
|
|
|
89
|
-
### Detecting
|
|
106
|
+
### Detecting identity key changes
|
|
90
107
|
|
|
91
108
|
Compare the daemon's current identity key against your stored pin before handshake:
|
|
92
109
|
|
|
@@ -123,7 +140,7 @@ if (!pinnedKey) {
|
|
|
123
140
|
}
|
|
124
141
|
```
|
|
125
142
|
|
|
126
|
-
## Error
|
|
143
|
+
## Error handling
|
|
127
144
|
|
|
128
145
|
All errors throw `SbrpError` with a specific `code`:
|
|
129
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sideband/secure-relay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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": {
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"./package.json": "./package.json"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@noble/ciphers": "^
|
|
49
|
-
"@noble/curves": "^
|
|
50
|
-
"@noble/hashes": "^
|
|
48
|
+
"@noble/ciphers": "^2.1.1",
|
|
49
|
+
"@noble/curves": "^2.0.1",
|
|
50
|
+
"@noble/hashes": "^2.0.1"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"dist",
|
package/src/crypto.ts
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* and @noble/hashes for SHA-256/HKDF.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { chacha20poly1305 } from "@noble/ciphers/chacha";
|
|
11
|
-
import { ed25519 } from "@noble/curves/ed25519";
|
|
12
|
-
import { x25519 } from "@noble/curves/ed25519";
|
|
13
|
-
import { hkdf } from "@noble/hashes/hkdf";
|
|
14
|
-
import { sha256 } from "@noble/hashes/
|
|
15
|
-
import { concatBytes, randomBytes } from "@noble/hashes/utils";
|
|
10
|
+
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
11
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
12
|
+
import { x25519 } from "@noble/curves/ed25519.js";
|
|
13
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
14
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
15
|
+
import { concatBytes, randomBytes } from "@noble/hashes/utils.js";
|
|
16
16
|
import {
|
|
17
17
|
AUTH_TAG_LENGTH,
|
|
18
18
|
DIRECTION_CLIENT_TO_DAEMON,
|
|
@@ -36,14 +36,14 @@ const textEncoder = new TextEncoder();
|
|
|
36
36
|
|
|
37
37
|
/** Generate a new Ed25519 identity keypair */
|
|
38
38
|
export function generateIdentityKeyPair(): IdentityKeyPair {
|
|
39
|
-
const privateKey = ed25519.utils.
|
|
39
|
+
const privateKey = ed25519.utils.randomSecretKey();
|
|
40
40
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
41
41
|
return { publicKey, privateKey };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/** Generate a new X25519 ephemeral keypair */
|
|
45
45
|
export function generateEphemeralKeyPair(): EphemeralKeyPair {
|
|
46
|
-
const privateKey = x25519.utils.
|
|
46
|
+
const privateKey = x25519.utils.randomSecretKey();
|
|
47
47
|
const publicKey = x25519.getPublicKey(privateKey);
|
|
48
48
|
return { publicKey, privateKey };
|
|
49
49
|
}
|
|
@@ -137,7 +137,7 @@ export function deriveSessionKeys(
|
|
|
137
137
|
sha256,
|
|
138
138
|
sharedSecret,
|
|
139
139
|
transcriptHash,
|
|
140
|
-
SBRP_SESSION_KEYS_INFO,
|
|
140
|
+
textEncoder.encode(SBRP_SESSION_KEYS_INFO),
|
|
141
141
|
SESSION_KEYS_LENGTH,
|
|
142
142
|
);
|
|
143
143
|
|