@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/handshake.ts
CHANGED
|
@@ -27,19 +27,6 @@ import type {
|
|
|
27
27
|
} from "./types.js";
|
|
28
28
|
import { SbrpError, SbrpErrorCode } from "./types.js";
|
|
29
29
|
|
|
30
|
-
/** Result of a successful daemon handshake */
|
|
31
|
-
export interface DaemonHandshakeResult {
|
|
32
|
-
sessionKeys: SessionKeys;
|
|
33
|
-
ephemeralKeyPair: EphemeralKeyPair;
|
|
34
|
-
signature: Uint8Array;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Result of a successful client handshake */
|
|
38
|
-
export interface ClientHandshakeResult {
|
|
39
|
-
sessionKeys: SessionKeys;
|
|
40
|
-
ephemeralKeyPair: EphemeralKeyPair;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
30
|
/**
|
|
44
31
|
* Create a handshake init message (client side).
|
|
45
32
|
*
|
|
@@ -73,7 +60,7 @@ export function processHandshakeInit(
|
|
|
73
60
|
init: HandshakeInit,
|
|
74
61
|
daemonId: DaemonId,
|
|
75
62
|
identityKeyPair: IdentityKeyPair,
|
|
76
|
-
): { message: HandshakeAccept;
|
|
63
|
+
): { message: HandshakeAccept; sessionKeys: SessionKeys } {
|
|
77
64
|
const ephemeralKeyPair = generateEphemeralKeyPair();
|
|
78
65
|
|
|
79
66
|
// Sign ephemeral key with context binding
|
|
@@ -97,20 +84,18 @@ export function processHandshakeInit(
|
|
|
97
84
|
);
|
|
98
85
|
const sessionKeys = deriveSessionKeys(sharedSecret, transcriptHash);
|
|
99
86
|
|
|
100
|
-
// Best-effort zeroize
|
|
87
|
+
// Best-effort zeroize secrets
|
|
101
88
|
zeroize(sharedSecret);
|
|
89
|
+
zeroize(ephemeralKeyPair.privateKey);
|
|
102
90
|
|
|
103
91
|
return {
|
|
104
92
|
message: {
|
|
105
93
|
type: "handshake.accept",
|
|
94
|
+
identityPublicKey: identityKeyPair.publicKey,
|
|
106
95
|
acceptPublicKey: ephemeralKeyPair.publicKey,
|
|
107
96
|
signature,
|
|
108
97
|
},
|
|
109
|
-
|
|
110
|
-
sessionKeys,
|
|
111
|
-
ephemeralKeyPair,
|
|
112
|
-
signature,
|
|
113
|
-
},
|
|
98
|
+
sessionKeys,
|
|
114
99
|
};
|
|
115
100
|
}
|
|
116
101
|
|
|
@@ -123,14 +108,29 @@ export function processHandshakeInit(
|
|
|
123
108
|
* NOTE: Callers MUST enforce a 30-second handshake timeout per SBRP §1.4.
|
|
124
109
|
* This function does not track time; timeout enforcement is a transport concern.
|
|
125
110
|
*
|
|
126
|
-
* @
|
|
111
|
+
* @param ephemeralKeyPair The privateKey is zeroized in-place after key derivation.
|
|
112
|
+
* @throws {SbrpError} IdentityKeyChanged if advertised key doesn't match pinned key
|
|
113
|
+
* @throws {SbrpError} HandshakeFailed if signature verification fails
|
|
127
114
|
*/
|
|
128
115
|
export function processHandshakeAccept(
|
|
129
116
|
accept: HandshakeAccept,
|
|
130
117
|
daemonId: DaemonId,
|
|
131
118
|
pinnedIdentityPublicKey: Uint8Array,
|
|
132
119
|
ephemeralKeyPair: EphemeralKeyPair,
|
|
133
|
-
):
|
|
120
|
+
): SessionKeys {
|
|
121
|
+
// Reject if advertised identity key doesn't match pinned key.
|
|
122
|
+
// Signature is verified against pinnedIdentityPublicKey, but an attacker
|
|
123
|
+
// could swap the advertised field to mislead higher layers.
|
|
124
|
+
if (
|
|
125
|
+
accept.identityPublicKey.length !== pinnedIdentityPublicKey.length ||
|
|
126
|
+
!accept.identityPublicKey.every((b, i) => b === pinnedIdentityPublicKey[i])
|
|
127
|
+
) {
|
|
128
|
+
throw new SbrpError(
|
|
129
|
+
SbrpErrorCode.IdentityKeyChanged,
|
|
130
|
+
"Advertised identity key does not match pinned key",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
134
|
// Verify daemon signature using PINNED key (not relay-provided!)
|
|
135
135
|
const signaturePayload = createSignaturePayload(
|
|
136
136
|
daemonId,
|
|
@@ -164,11 +164,9 @@ export function processHandshakeAccept(
|
|
|
164
164
|
);
|
|
165
165
|
const sessionKeys = deriveSessionKeys(sharedSecret, transcriptHash);
|
|
166
166
|
|
|
167
|
-
// Best-effort zeroize
|
|
167
|
+
// Best-effort zeroize secrets
|
|
168
168
|
zeroize(sharedSecret);
|
|
169
|
+
zeroize(ephemeralKeyPair.privateKey);
|
|
169
170
|
|
|
170
|
-
return
|
|
171
|
-
sessionKeys,
|
|
172
|
-
ephemeralKeyPair,
|
|
173
|
-
};
|
|
171
|
+
return sessionKeys;
|
|
174
172
|
}
|
package/src/index.ts
CHANGED
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
* const { message: init, ephemeralKeyPair } = createHandshakeInit();
|
|
23
23
|
*
|
|
24
24
|
* // Daemon side: process init and create accept
|
|
25
|
-
* const { message: accept,
|
|
26
|
-
* const clientSession = createClientSession(clientId,
|
|
25
|
+
* const { message: accept, sessionKeys } = processHandshakeInit(init, daemonId, identity);
|
|
26
|
+
* const clientSession = createClientSession(clientId, sessionKeys);
|
|
27
27
|
*
|
|
28
28
|
* // Client side: process accept (with TOFU-pinned identity)
|
|
29
|
-
* const
|
|
30
|
-
* const daemonSession = createDaemonSession(
|
|
29
|
+
* const clientKeys = processHandshakeAccept(accept, daemonId, pinnedKey, ephemeralKeyPair);
|
|
30
|
+
* const daemonSession = createDaemonSession(clientKeys);
|
|
31
31
|
*
|
|
32
32
|
* // Encrypt/decrypt messages
|
|
33
33
|
* const encrypted = encryptClientToDaemon(daemonSession, plaintext);
|
|
@@ -44,7 +44,6 @@ export type {
|
|
|
44
44
|
HandshakeAccept,
|
|
45
45
|
HandshakeInit,
|
|
46
46
|
IdentityKeyPair,
|
|
47
|
-
PinnedIdentity,
|
|
48
47
|
SessionId,
|
|
49
48
|
SessionKeys,
|
|
50
49
|
} from "./types.js";
|
|
@@ -106,11 +105,6 @@ export {
|
|
|
106
105
|
} from "./crypto.js";
|
|
107
106
|
|
|
108
107
|
// Handshake
|
|
109
|
-
export type {
|
|
110
|
-
ClientHandshakeResult,
|
|
111
|
-
DaemonHandshakeResult,
|
|
112
|
-
} from "./handshake.js";
|
|
113
|
-
|
|
114
108
|
export {
|
|
115
109
|
createHandshakeInit,
|
|
116
110
|
processHandshakeAccept,
|