@sideband/secure-relay 0.0.1

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/dist/crypto.js ADDED
@@ -0,0 +1,145 @@
1
+ // SPDX-FileCopyrightText: 2025-present Sideband
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ /**
4
+ * Cryptographic primitives for Sideband Relay Protocol (SBRP).
5
+ *
6
+ * Uses @noble/curves for Ed25519/X25519, @noble/ciphers for ChaCha20-Poly1305,
7
+ * and @noble/hashes for SHA-256/HKDF.
8
+ */
9
+ import { chacha20poly1305 } from "@noble/ciphers/chacha";
10
+ import { ed25519 } from "@noble/curves/ed25519";
11
+ import { x25519 } from "@noble/curves/ed25519";
12
+ import { hkdf } from "@noble/hashes/hkdf";
13
+ import { sha256 } from "@noble/hashes/sha256";
14
+ import { concatBytes, randomBytes } from "@noble/hashes/utils";
15
+ import { AUTH_TAG_LENGTH, DIRECTION_CLIENT_TO_DAEMON, DIRECTION_DAEMON_TO_CLIENT, NONCE_LENGTH, SBRP_HANDSHAKE_CONTEXT, SBRP_SESSION_KEYS_INFO, SBRP_TRANSCRIPT_CONTEXT, SESSION_KEYS_LENGTH, SYMMETRIC_KEY_LENGTH, } from "./constants.js";
16
+ import { Direction } from "./types.js";
17
+ const textEncoder = new TextEncoder();
18
+ /** Generate a new Ed25519 identity keypair */
19
+ export function generateIdentityKeyPair() {
20
+ const privateKey = ed25519.utils.randomPrivateKey();
21
+ const publicKey = ed25519.getPublicKey(privateKey);
22
+ return { publicKey, privateKey };
23
+ }
24
+ /** Generate a new X25519 ephemeral keypair */
25
+ export function generateEphemeralKeyPair() {
26
+ const privateKey = x25519.utils.randomPrivateKey();
27
+ const publicKey = x25519.getPublicKey(privateKey);
28
+ return { publicKey, privateKey };
29
+ }
30
+ /** Compute SHA-256 fingerprint of an identity public key */
31
+ export function computeFingerprint(identityPublicKey) {
32
+ const hash = sha256(identityPublicKey);
33
+ const hex = Array.from(hash)
34
+ .map((b) => b.toString(16).padStart(2, "0").toUpperCase())
35
+ .join(":");
36
+ return `SHA256:${hex}`;
37
+ }
38
+ /**
39
+ * Create the signature payload for handshake authentication.
40
+ *
41
+ * payload = SHA256("sbrp-v1-handshake" || daemonId || browserPublicKey || daemonEphemeralPublicKey)
42
+ */
43
+ export function createSignaturePayload(daemonId, browserPublicKey, daemonEphemeralPublicKey) {
44
+ return sha256(concatBytes(textEncoder.encode(SBRP_HANDSHAKE_CONTEXT), textEncoder.encode(daemonId), browserPublicKey, daemonEphemeralPublicKey));
45
+ }
46
+ /** Sign a payload with an Ed25519 identity private key */
47
+ export function signPayload(payload, identityPrivateKey) {
48
+ return ed25519.sign(payload, identityPrivateKey);
49
+ }
50
+ /** Verify an Ed25519 signature */
51
+ export function verifySignature(payload, signature, identityPublicKey) {
52
+ return ed25519.verify(signature, payload, identityPublicKey);
53
+ }
54
+ /** Compute X25519 shared secret */
55
+ export function computeSharedSecret(myPrivateKey, peerPublicKey) {
56
+ return x25519.getSharedSecret(myPrivateKey, peerPublicKey);
57
+ }
58
+ /**
59
+ * Create the transcript hash for key derivation.
60
+ *
61
+ * transcript = SHA256("sbrp-v1-transcript" || daemonId || browserPublicKey || daemonPublicKey || signature)
62
+ */
63
+ export function createTranscriptHash(daemonId, browserPublicKey, daemonPublicKey, signature) {
64
+ return sha256(concatBytes(textEncoder.encode(SBRP_TRANSCRIPT_CONTEXT), textEncoder.encode(daemonId), browserPublicKey, daemonPublicKey, signature));
65
+ }
66
+ /**
67
+ * Derive traffic keys using HKDF-SHA256.
68
+ *
69
+ * Keys are derived with transcript hash as salt to bind to the authenticated session.
70
+ */
71
+ export function deriveTrafficKeys(sharedSecret, transcriptHash) {
72
+ const keys = hkdf(sha256, sharedSecret, transcriptHash, SBRP_SESSION_KEYS_INFO, SESSION_KEYS_LENGTH);
73
+ return {
74
+ clientToDaemon: keys.slice(0, SYMMETRIC_KEY_LENGTH),
75
+ daemonToClient: keys.slice(SYMMETRIC_KEY_LENGTH, SESSION_KEYS_LENGTH),
76
+ };
77
+ }
78
+ /**
79
+ * Construct a nonce for ChaCha20-Poly1305.
80
+ *
81
+ * Nonce format (12 bytes):
82
+ * - Bytes 0-3: Direction (0x00000001 = client→daemon, 0x00000002 = daemon→client)
83
+ * - Bytes 4-11: Sequence number (big-endian uint64)
84
+ */
85
+ export function constructNonce(direction, seq) {
86
+ const nonce = new Uint8Array(NONCE_LENGTH);
87
+ const directionBytes = direction === Direction.ClientToDaemon
88
+ ? DIRECTION_CLIENT_TO_DAEMON
89
+ : DIRECTION_DAEMON_TO_CLIENT;
90
+ nonce.set(directionBytes, 0);
91
+ // Write sequence number as big-endian uint64 (bytes 4-11)
92
+ const view = new DataView(nonce.buffer);
93
+ view.setBigUint64(4, seq, false); // false = big-endian
94
+ return nonce;
95
+ }
96
+ /**
97
+ * Encrypt a message using ChaCha20-Poly1305.
98
+ *
99
+ * Returns: nonce (12 bytes) || ciphertext || authTag (16 bytes)
100
+ */
101
+ export function encrypt(key, direction, seq, plaintext) {
102
+ const nonce = constructNonce(direction, seq);
103
+ const cipher = chacha20poly1305(key, nonce);
104
+ const ciphertext = cipher.encrypt(plaintext);
105
+ return concatBytes(nonce, ciphertext);
106
+ }
107
+ /**
108
+ * Decrypt a message using ChaCha20-Poly1305.
109
+ *
110
+ * Input format: nonce (12 bytes) || ciphertext || authTag (16 bytes)
111
+ * Returns the plaintext, or throws on decryption failure.
112
+ */
113
+ export function decrypt(key, data) {
114
+ if (data.length < NONCE_LENGTH + AUTH_TAG_LENGTH) {
115
+ throw new Error("Invalid encrypted message: too short");
116
+ }
117
+ const nonce = data.slice(0, NONCE_LENGTH);
118
+ const ciphertext = data.slice(NONCE_LENGTH);
119
+ const cipher = chacha20poly1305(key, nonce);
120
+ return cipher.decrypt(ciphertext);
121
+ }
122
+ /**
123
+ * Extract sequence number from encrypted message data.
124
+ *
125
+ * Reads bytes 4-11 of the nonce as big-endian uint64.
126
+ */
127
+ export function extractSequence(data) {
128
+ if (data.length < NONCE_LENGTH) {
129
+ throw new Error("Invalid encrypted message: too short");
130
+ }
131
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
132
+ return view.getBigUint64(4, false); // false = big-endian
133
+ }
134
+ /**
135
+ * Best-effort zeroization of sensitive key material.
136
+ *
137
+ * Note: JavaScript/GC limitations mean this is not guaranteed to prevent
138
+ * key material from remaining in memory.
139
+ */
140
+ export function zeroize(data) {
141
+ data.fill(0);
142
+ }
143
+ /** Generate random bytes */
144
+ export { randomBytes };
145
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { DaemonId, EphemeralKeyPair, HandshakeAccept, HandshakeInit, IdentityKeyPair, TrafficKeys } from "./types.js";
2
+ /** Result of a successful daemon handshake */
3
+ export interface DaemonHandshakeResult {
4
+ trafficKeys: TrafficKeys;
5
+ ephemeralKeyPair: EphemeralKeyPair;
6
+ signature: Uint8Array;
7
+ }
8
+ /** Result of a successful client handshake */
9
+ export interface ClientHandshakeResult {
10
+ trafficKeys: TrafficKeys;
11
+ ephemeralKeyPair: EphemeralKeyPair;
12
+ }
13
+ /**
14
+ * Create a handshake init message (browser side).
15
+ *
16
+ * Generates an ephemeral X25519 keypair for this session.
17
+ */
18
+ export declare function createHandshakeInit(): {
19
+ message: HandshakeInit;
20
+ ephemeralKeyPair: EphemeralKeyPair;
21
+ };
22
+ /**
23
+ * Process handshake init and create accept message (daemon side).
24
+ *
25
+ * 1. Generate ephemeral X25519 keypair
26
+ * 2. Sign ephemeral public key with identity key (context-bound)
27
+ * 3. Derive traffic keys
28
+ */
29
+ export declare function processHandshakeInit(init: HandshakeInit, daemonId: DaemonId, identityKeyPair: IdentityKeyPair): {
30
+ message: HandshakeAccept;
31
+ result: DaemonHandshakeResult;
32
+ };
33
+ /**
34
+ * Process handshake accept message (client side).
35
+ *
36
+ * 1. Verify signature using PINNED identity key (TOFU)
37
+ * 2. Derive traffic keys using same transcript hash as daemon
38
+ *
39
+ * @throws {SbrpError} with code HandshakeFailed if signature verification fails
40
+ */
41
+ export declare function processHandshakeAccept(accept: HandshakeAccept, daemonId: DaemonId, pinnedIdentityPublicKey: Uint8Array, ephemeralKeyPair: EphemeralKeyPair): ClientHandshakeResult;
42
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../src/handshake.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,EACf,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CASA;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,QAAQ,EAClB,eAAe,EAAE,eAAe,GAC/B;IAAE,OAAO,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,CAuC7D;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,QAAQ,EAClB,uBAAuB,EAAE,UAAU,EACnC,gBAAgB,EAAE,gBAAgB,GACjC,qBAAqB,CAyCvB"}
@@ -0,0 +1,83 @@
1
+ // SPDX-FileCopyrightText: 2025-present Sideband
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ /**
4
+ * E2EE handshake protocol for Sideband Relay Protocol (SBRP).
5
+ *
6
+ * Implements the authenticated key exchange between browser and daemon,
7
+ * with Ed25519 signatures for MITM protection.
8
+ */
9
+ import { computeSharedSecret, createSignaturePayload, createTranscriptHash, deriveTrafficKeys, generateEphemeralKeyPair, signPayload, verifySignature, zeroize, } from "./crypto.js";
10
+ import { SbrpError, SbrpErrorCode } from "./types.js";
11
+ /**
12
+ * Create a handshake init message (browser side).
13
+ *
14
+ * Generates an ephemeral X25519 keypair for this session.
15
+ */
16
+ export function createHandshakeInit() {
17
+ const ephemeralKeyPair = generateEphemeralKeyPair();
18
+ return {
19
+ message: {
20
+ type: "handshake.init",
21
+ browserPublicKey: ephemeralKeyPair.publicKey,
22
+ },
23
+ ephemeralKeyPair,
24
+ };
25
+ }
26
+ /**
27
+ * Process handshake init and create accept message (daemon side).
28
+ *
29
+ * 1. Generate ephemeral X25519 keypair
30
+ * 2. Sign ephemeral public key with identity key (context-bound)
31
+ * 3. Derive traffic keys
32
+ */
33
+ export function processHandshakeInit(init, daemonId, identityKeyPair) {
34
+ const ephemeralKeyPair = generateEphemeralKeyPair();
35
+ // Sign ephemeral key with context binding
36
+ const signaturePayload = createSignaturePayload(daemonId, init.browserPublicKey, ephemeralKeyPair.publicKey);
37
+ const signature = signPayload(signaturePayload, identityKeyPair.privateKey);
38
+ // Derive traffic keys
39
+ const sharedSecret = computeSharedSecret(ephemeralKeyPair.privateKey, init.browserPublicKey);
40
+ const transcriptHash = createTranscriptHash(daemonId, init.browserPublicKey, ephemeralKeyPair.publicKey, signature);
41
+ const trafficKeys = deriveTrafficKeys(sharedSecret, transcriptHash);
42
+ // Best-effort zeroize shared secret
43
+ zeroize(sharedSecret);
44
+ return {
45
+ message: {
46
+ type: "handshake.accept",
47
+ daemonPublicKey: ephemeralKeyPair.publicKey,
48
+ signature,
49
+ },
50
+ result: {
51
+ trafficKeys,
52
+ ephemeralKeyPair,
53
+ signature,
54
+ },
55
+ };
56
+ }
57
+ /**
58
+ * Process handshake accept message (client side).
59
+ *
60
+ * 1. Verify signature using PINNED identity key (TOFU)
61
+ * 2. Derive traffic keys using same transcript hash as daemon
62
+ *
63
+ * @throws {SbrpError} with code HandshakeFailed if signature verification fails
64
+ */
65
+ export function processHandshakeAccept(accept, daemonId, pinnedIdentityPublicKey, ephemeralKeyPair) {
66
+ // Verify daemon signature using PINNED key (not relay-provided!)
67
+ const signaturePayload = createSignaturePayload(daemonId, ephemeralKeyPair.publicKey, accept.daemonPublicKey);
68
+ const valid = verifySignature(signaturePayload, accept.signature, pinnedIdentityPublicKey);
69
+ if (!valid) {
70
+ throw new SbrpError(SbrpErrorCode.HandshakeFailed, "Signature verification failed");
71
+ }
72
+ // Derive traffic keys using same transcript hash as daemon
73
+ const sharedSecret = computeSharedSecret(ephemeralKeyPair.privateKey, accept.daemonPublicKey);
74
+ const transcriptHash = createTranscriptHash(daemonId, ephemeralKeyPair.publicKey, accept.daemonPublicKey, accept.signature);
75
+ const trafficKeys = deriveTrafficKeys(sharedSecret, transcriptHash);
76
+ // Best-effort zeroize shared secret
77
+ zeroize(sharedSecret);
78
+ return {
79
+ trafficKeys,
80
+ ephemeralKeyPair,
81
+ };
82
+ }
83
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../src/handshake.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,6CAA6C;AAE7C;;;;;GAKG;AAEH,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,wBAAwB,EACxB,WAAW,EACX,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AASrB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAetD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IAIjC,MAAM,gBAAgB,GAAG,wBAAwB,EAAE,CAAC;IACpD,OAAO;QACL,OAAO,EAAE;YACP,IAAI,EAAE,gBAAgB;YACtB,gBAAgB,EAAE,gBAAgB,CAAC,SAAS;SAC7C;QACD,gBAAgB;KACjB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAmB,EACnB,QAAkB,EAClB,eAAgC;IAEhC,MAAM,gBAAgB,GAAG,wBAAwB,EAAE,CAAC;IAEpD,0CAA0C;IAC1C,MAAM,gBAAgB,GAAG,sBAAsB,CAC7C,QAAQ,EACR,IAAI,CAAC,gBAAgB,EACrB,gBAAgB,CAAC,SAAS,CAC3B,CAAC;IACF,MAAM,SAAS,GAAG,WAAW,CAAC,gBAAgB,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;IAE5E,sBAAsB;IACtB,MAAM,YAAY,GAAG,mBAAmB,CACtC,gBAAgB,CAAC,UAAU,EAC3B,IAAI,CAAC,gBAAgB,CACtB,CAAC;IACF,MAAM,cAAc,GAAG,oBAAoB,CACzC,QAAQ,EACR,IAAI,CAAC,gBAAgB,EACrB,gBAAgB,CAAC,SAAS,EAC1B,SAAS,CACV,CAAC;IACF,MAAM,WAAW,GAAG,iBAAiB,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IAEpE,oCAAoC;IACpC,OAAO,CAAC,YAAY,CAAC,CAAC;IAEtB,OAAO;QACL,OAAO,EAAE;YACP,IAAI,EAAE,kBAAkB;YACxB,eAAe,EAAE,gBAAgB,CAAC,SAAS;YAC3C,SAAS;SACV;QACD,MAAM,EAAE;YACN,WAAW;YACX,gBAAgB;YAChB,SAAS;SACV;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAuB,EACvB,QAAkB,EAClB,uBAAmC,EACnC,gBAAkC;IAElC,iEAAiE;IACjE,MAAM,gBAAgB,GAAG,sBAAsB,CAC7C,QAAQ,EACR,gBAAgB,CAAC,SAAS,EAC1B,MAAM,CAAC,eAAe,CACvB,CAAC;IAEF,MAAM,KAAK,GAAG,eAAe,CAC3B,gBAAgB,EAChB,MAAM,CAAC,SAAS,EAChB,uBAAuB,CACxB,CAAC;IAEF,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CACjB,aAAa,CAAC,eAAe,EAC7B,+BAA+B,CAChC,CAAC;IACJ,CAAC;IAED,2DAA2D;IAC3D,MAAM,YAAY,GAAG,mBAAmB,CACtC,gBAAgB,CAAC,UAAU,EAC3B,MAAM,CAAC,eAAe,CACvB,CAAC;IACF,MAAM,cAAc,GAAG,oBAAoB,CACzC,QAAQ,EACR,gBAAgB,CAAC,SAAS,EAC1B,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,SAAS,CACjB,CAAC;IACF,MAAM,WAAW,GAAG,iBAAiB,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IAEpE,oCAAoC;IACpC,OAAO,CAAC,YAAY,CAAC,CAAC;IAEtB,OAAO;QACL,WAAW;QACX,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @sideband/secure-relay
3
+ *
4
+ * Sideband Relay Protocol (SBRP) implementation for E2EE communication
5
+ * between daemons and clients via a relay server.
6
+ *
7
+ * Features:
8
+ * - Ed25519 identity signatures for MITM protection
9
+ * - X25519 ephemeral key exchange for forward secrecy
10
+ * - ChaCha20-Poly1305 authenticated encryption
11
+ * - TOFU (Trust On First Use) identity pinning
12
+ * - Bitmap-based replay protection
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Daemon side: generate identity keypair on first run
17
+ * const identity = generateIdentityKeyPair();
18
+ *
19
+ * // Client side: create handshake init
20
+ * const { message: init, ephemeralKeyPair } = createHandshakeInit();
21
+ *
22
+ * // Daemon side: process init and create accept
23
+ * const { message: accept, result } = processHandshakeInit(init, daemonId, identity);
24
+ * const clientSession = createClientSession(clientId, result.trafficKeys);
25
+ *
26
+ * // Client side: process accept (with TOFU-pinned identity)
27
+ * const { trafficKeys } = processHandshakeAccept(accept, daemonId, pinnedKey, ephemeralKeyPair);
28
+ * const daemonConn = createDaemonConnection(trafficKeys);
29
+ *
30
+ * // Encrypt/decrypt messages
31
+ * const encrypted = encryptClientToDaemon(daemonConn, plaintext);
32
+ * const decrypted = decryptClientToDaemon(clientSession, encrypted);
33
+ * ```
34
+ */
35
+ export type { ClientId, DaemonId, EncryptedMessage, EphemeralKeyPair, HandshakeAccept, HandshakeInit, IdentityKeyPair, PinnedIdentity, TrafficKeys, } from "./types.js";
36
+ export { asClientId, asDaemonId, Direction, SbrpError, SbrpErrorCode } from "./types.js";
37
+ export { AUTH_TAG_LENGTH, DEFAULT_REPLAY_WINDOW_SIZE, DIRECTION_CLIENT_TO_DAEMON, DIRECTION_DAEMON_TO_CLIENT, ED25519_PRIVATE_KEY_LENGTH, ED25519_PUBLIC_KEY_LENGTH, ED25519_SIGNATURE_LENGTH, NONCE_LENGTH, SBRP_HANDSHAKE_CONTEXT, SBRP_SESSION_KEYS_INFO, SBRP_TRANSCRIPT_CONTEXT, SESSION_KEYS_LENGTH, SYMMETRIC_KEY_LENGTH, X25519_PRIVATE_KEY_LENGTH, X25519_PUBLIC_KEY_LENGTH, } from "./constants.js";
38
+ export { computeFingerprint, computeSharedSecret, constructNonce, createSignaturePayload, createTranscriptHash, decrypt, deriveTrafficKeys, encrypt, extractSequence, generateEphemeralKeyPair, generateIdentityKeyPair, randomBytes, signPayload, verifySignature, zeroize, } from "./crypto.js";
39
+ export type { ClientHandshakeResult, DaemonHandshakeResult, } from "./handshake.js";
40
+ export { createHandshakeInit, processHandshakeAccept, processHandshakeInit, } from "./handshake.js";
41
+ export type { ReplayWindow } from "./replay.js";
42
+ export { checkAndUpdateReplay, createReplayWindow, isValidSequence, resetReplayWindow, } from "./replay.js";
43
+ export type { ClientSession, DaemonConnection } from "./session.js";
44
+ export { clearClientSession, clearDaemonConnection, createClientSession, createDaemonConnection, decryptClientToDaemon, decryptDaemonToClient, encryptClientToDaemon, encryptDaemonToClient, } from "./session.js";
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,eAAe,EACf,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGzF,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,wBAAwB,EACxB,uBAAuB,EACvB,WAAW,EACX,WAAW,EACX,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AAGrB,YAAY,EACV,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // SPDX-FileCopyrightText: 2025-present Sideband
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ export { asClientId, asDaemonId, Direction, SbrpError, SbrpErrorCode } from "./types.js";
4
+ // Constants
5
+ export { AUTH_TAG_LENGTH, DEFAULT_REPLAY_WINDOW_SIZE, DIRECTION_CLIENT_TO_DAEMON, DIRECTION_DAEMON_TO_CLIENT, ED25519_PRIVATE_KEY_LENGTH, ED25519_PUBLIC_KEY_LENGTH, ED25519_SIGNATURE_LENGTH, NONCE_LENGTH, SBRP_HANDSHAKE_CONTEXT, SBRP_SESSION_KEYS_INFO, SBRP_TRANSCRIPT_CONTEXT, SESSION_KEYS_LENGTH, SYMMETRIC_KEY_LENGTH, X25519_PRIVATE_KEY_LENGTH, X25519_PUBLIC_KEY_LENGTH, } from "./constants.js";
6
+ // Crypto primitives
7
+ export { computeFingerprint, computeSharedSecret, constructNonce, createSignaturePayload, createTranscriptHash, decrypt, deriveTrafficKeys, encrypt, extractSequence, generateEphemeralKeyPair, generateIdentityKeyPair, randomBytes, signPayload, verifySignature, zeroize, } from "./crypto.js";
8
+ export { createHandshakeInit, processHandshakeAccept, processHandshakeInit, } from "./handshake.js";
9
+ export { checkAndUpdateReplay, createReplayWindow, isValidSequence, resetReplayWindow, } from "./replay.js";
10
+ export { clearClientSession, clearDaemonConnection, createClientSession, createDaemonConnection, decryptClientToDaemon, decryptDaemonToClient, encryptClientToDaemon, encryptDaemonToClient, } from "./session.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,6CAA6C;AAkD7C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzF,YAAY;AACZ,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,EACZ,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,oBAAoB;AACpB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,OAAO,EACP,iBAAiB,EACjB,OAAO,EACP,eAAe,EACf,wBAAwB,EACxB,uBAAuB,EACvB,WAAW,EACX,WAAW,EACX,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AAQrB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAKrB,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,32 @@
1
+ /** Replay window state */
2
+ export interface ReplayWindow {
3
+ /** Highest accepted sequence number */
4
+ maxSeen: bigint;
5
+ /** Bitmap: bit i set = sequence (maxSeen - i) was seen */
6
+ bitmap: bigint;
7
+ /** Window size in bits */
8
+ windowSize: bigint;
9
+ }
10
+ /**
11
+ * Create a new replay window.
12
+ *
13
+ * @param windowSize - Window size in bits (default: 64)
14
+ */
15
+ export declare function createReplayWindow(windowSize?: bigint): ReplayWindow;
16
+ /**
17
+ * Check if a sequence number is valid (not a replay) and update the window.
18
+ *
19
+ * @returns true if the sequence is valid and accepted, false if it's a replay
20
+ */
21
+ export declare function checkAndUpdateReplay(seq: bigint, window: ReplayWindow): boolean;
22
+ /**
23
+ * Check if a sequence number would be valid without updating the window.
24
+ *
25
+ * Useful for pre-validation before decryption.
26
+ */
27
+ export declare function isValidSequence(seq: bigint, window: ReplayWindow): boolean;
28
+ /**
29
+ * Reset the replay window to initial state.
30
+ */
31
+ export declare function resetReplayWindow(window: ReplayWindow): void;
32
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAaA,0BAA0B;AAC1B,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,GAAE,MAAmC,GAC9C,YAAY,CAMd;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,GACnB,OAAO,CAqCT;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAgB1E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAG5D"}
package/dist/replay.js ADDED
@@ -0,0 +1,89 @@
1
+ // SPDX-FileCopyrightText: 2025-present Sideband
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ /**
4
+ * Bitmap-based replay protection for Sideband Relay Protocol (SBRP).
5
+ *
6
+ * Uses a sliding window to track seen sequence numbers and prevent
7
+ * replay attacks while avoiding memory exhaustion from attacker-controlled
8
+ * sequence numbers.
9
+ */
10
+ import { DEFAULT_REPLAY_WINDOW_SIZE } from "./constants.js";
11
+ /**
12
+ * Create a new replay window.
13
+ *
14
+ * @param windowSize - Window size in bits (default: 64)
15
+ */
16
+ export function createReplayWindow(windowSize = DEFAULT_REPLAY_WINDOW_SIZE) {
17
+ return {
18
+ maxSeen: -1n, // No messages seen yet
19
+ bitmap: 0n,
20
+ windowSize,
21
+ };
22
+ }
23
+ /**
24
+ * Check if a sequence number is valid (not a replay) and update the window.
25
+ *
26
+ * @returns true if the sequence is valid and accepted, false if it's a replay
27
+ */
28
+ export function checkAndUpdateReplay(seq, window) {
29
+ // First message ever
30
+ if (window.maxSeen === -1n) {
31
+ window.maxSeen = seq;
32
+ window.bitmap = 1n;
33
+ return true;
34
+ }
35
+ if (seq > window.maxSeen) {
36
+ // New high sequence - shift window
37
+ const shift = seq - window.maxSeen;
38
+ if (shift >= window.windowSize) {
39
+ // Sequence is far ahead, reset bitmap
40
+ window.bitmap = 1n;
41
+ }
42
+ else {
43
+ window.bitmap = (window.bitmap << shift) | 1n;
44
+ }
45
+ window.maxSeen = seq;
46
+ return true;
47
+ }
48
+ // Sequence is within or before the window
49
+ const diff = window.maxSeen - seq;
50
+ if (diff >= window.windowSize) {
51
+ // Too old, outside window
52
+ return false;
53
+ }
54
+ const mask = 1n << diff;
55
+ if (window.bitmap & mask) {
56
+ // Already seen (replay)
57
+ return false;
58
+ }
59
+ // Mark as seen
60
+ window.bitmap |= mask;
61
+ return true;
62
+ }
63
+ /**
64
+ * Check if a sequence number would be valid without updating the window.
65
+ *
66
+ * Useful for pre-validation before decryption.
67
+ */
68
+ export function isValidSequence(seq, window) {
69
+ if (window.maxSeen === -1n) {
70
+ return true;
71
+ }
72
+ if (seq > window.maxSeen) {
73
+ return true;
74
+ }
75
+ const diff = window.maxSeen - seq;
76
+ if (diff >= window.windowSize) {
77
+ return false;
78
+ }
79
+ const mask = 1n << diff;
80
+ return (window.bitmap & mask) === 0n;
81
+ }
82
+ /**
83
+ * Reset the replay window to initial state.
84
+ */
85
+ export function resetReplayWindow(window) {
86
+ window.maxSeen = -1n;
87
+ window.bitmap = 0n;
88
+ }
89
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,6CAA6C;AAE7C;;;;;;GAMG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAY5D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAqB,0BAA0B;IAE/C,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,EAAE,uBAAuB;QACrC,MAAM,EAAE,EAAE;QACV,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAW,EACX,MAAoB;IAEpB,qBAAqB;IACrB,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,mCAAmC;QACnC,MAAM,KAAK,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;QACnC,IAAI,KAAK,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC/B,sCAAsC;YACtC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;QAChD,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAClC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC9B,0BAA0B;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,IAAI,IAAI,CAAC;IACxB,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACzB,wBAAwB;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC;IACtB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,MAAoB;IAC/D,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IAClC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,IAAI,IAAI,CAAC;IACxB,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;IACrB,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { type ReplayWindow } from "./replay.js";
2
+ import type { ClientId, EncryptedMessage, TrafficKeys } from "./types.js";
3
+ /** Crypto state for one direction of communication (traffic key, counters, replay) */
4
+ interface ChannelState {
5
+ trafficKey: Uint8Array;
6
+ sendSeq: bigint;
7
+ recvWindow: ReplayWindow;
8
+ }
9
+ /** Client session (daemon-side state for each connected client) */
10
+ export interface ClientSession {
11
+ clientId: ClientId;
12
+ clientToDaemon: ChannelState;
13
+ daemonToClient: ChannelState;
14
+ }
15
+ /** Daemon connection (client-side state for communicating with daemon) */
16
+ export interface DaemonConnection {
17
+ clientToDaemon: ChannelState;
18
+ daemonToClient: ChannelState;
19
+ }
20
+ /**
21
+ * Create a client session (daemon side).
22
+ *
23
+ * Used by daemon to manage state for each connected client.
24
+ */
25
+ export declare function createClientSession(clientId: ClientId, trafficKeys: TrafficKeys): ClientSession;
26
+ /**
27
+ * Create a daemon connection (client side).
28
+ *
29
+ * Used by client to communicate with daemon.
30
+ */
31
+ export declare function createDaemonConnection(trafficKeys: TrafficKeys): DaemonConnection;
32
+ /**
33
+ * Encrypt a message from client to daemon.
34
+ */
35
+ export declare function encryptClientToDaemon(conn: DaemonConnection, plaintext: Uint8Array): EncryptedMessage;
36
+ /**
37
+ * Encrypt a message from daemon to client.
38
+ */
39
+ export declare function encryptDaemonToClient(session: ClientSession, plaintext: Uint8Array): EncryptedMessage;
40
+ /**
41
+ * Decrypt a message received by daemon from client.
42
+ *
43
+ * @throws {SbrpError} with code SequenceError if replay detected
44
+ * @throws {SbrpError} with code DecryptFailed if decryption fails
45
+ */
46
+ export declare function decryptClientToDaemon(session: ClientSession, message: EncryptedMessage): Uint8Array;
47
+ /**
48
+ * Decrypt a message received by client from daemon.
49
+ *
50
+ * @throws {SbrpError} with code SequenceError if replay detected
51
+ * @throws {SbrpError} with code DecryptFailed if decryption fails
52
+ */
53
+ export declare function decryptDaemonToClient(conn: DaemonConnection, message: EncryptedMessage): Uint8Array;
54
+ /**
55
+ * Clear all key material from a client session.
56
+ *
57
+ * Best-effort zeroization (JS/GC limitations apply).
58
+ */
59
+ export declare function clearClientSession(session: ClientSession): void;
60
+ /**
61
+ * Clear all key material from a daemon connection.
62
+ *
63
+ * Best-effort zeroization (JS/GC limitations apply).
64
+ */
65
+ export declare function clearDaemonConnection(conn: DaemonConnection): void;
66
+ export {};
67
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG1E,sFAAsF;AACtF,UAAU,YAAY;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,YAAY,CAAC;CAC1B;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,YAAY,CAAC;IAC7B,cAAc,EAAE,YAAY,CAAC;CAC9B;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,YAAY,CAAC;IAC7B,cAAc,EAAE,YAAY,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,WAAW,GACvB,aAAa,CAcf;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,WAAW,GACvB,gBAAgB,CAalB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,UAAU,GACpB,gBAAgB,CAUlB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,UAAU,GACpB,gBAAgB,CAUlB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,gBAAgB,GACxB,UAAU,CAeZ;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,gBAAgB,GACxB,UAAU,CAeZ;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAG/D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAGlE"}