@agentproto/secrets 0.2.6 → 1.0.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/dist/{chunk-Q6K3AT3V.mjs → chunk-2FHFW2AU.mjs} +3 -3
- package/dist/{chunk-Q6K3AT3V.mjs.map → chunk-2FHFW2AU.mjs.map} +1 -1
- package/dist/chunk-3YRYO6LK.mjs +222 -0
- package/dist/chunk-3YRYO6LK.mjs.map +1 -0
- package/dist/chunk-432SC3XS.mjs +100 -0
- package/dist/chunk-432SC3XS.mjs.map +1 -0
- package/dist/chunk-5HUHYBPR.mjs +69 -0
- package/dist/chunk-5HUHYBPR.mjs.map +1 -0
- package/dist/{chunk-NLZ5HXGO.mjs → chunk-7V6U6DII.mjs} +4 -4
- package/dist/{chunk-NLZ5HXGO.mjs.map → chunk-7V6U6DII.mjs.map} +1 -1
- package/dist/chunk-JNTDSOZQ.mjs +623 -0
- package/dist/chunk-JNTDSOZQ.mjs.map +1 -0
- package/dist/chunk-NQX3KBPW.mjs +50 -0
- package/dist/chunk-NQX3KBPW.mjs.map +1 -0
- package/dist/chunk-PCFPJ47T.mjs +28 -0
- package/dist/chunk-PCFPJ47T.mjs.map +1 -0
- package/dist/cli.mjs +10 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/core-CIn0-z-Y.d.ts +42 -0
- package/dist/core-D8zVNPDb.d.ts +58 -0
- package/dist/identity/index.d.ts +25 -46
- package/dist/identity/index.mjs +76 -1
- package/dist/identity/index.mjs.map +1 -1
- package/dist/pairing/browser.d.ts +18 -0
- package/dist/pairing/browser.mjs +6 -0
- package/dist/pairing/browser.mjs.map +1 -0
- package/dist/pairing/index.d.ts +45 -440
- package/dist/pairing/index.mjs +33 -465
- package/dist/pairing/index.mjs.map +1 -1
- package/dist/provision/index.mjs +5 -2
- package/dist/provision/recipe/index.mjs +6 -3
- package/dist/seal/index.d.ts +21 -31
- package/dist/seal/index.mjs +4 -1
- package/dist/types-B2ZfEhsU.d.ts +57 -0
- package/dist/webcrypto-BU57p0_g.d.ts +652 -0
- package/package.json +8 -2
- package/dist/chunk-2DL6W33G.mjs +0 -126
- package/dist/chunk-2DL6W33G.mjs.map +0 -1
- package/dist/chunk-MVHOJPML.mjs +0 -132
- package/dist/chunk-MVHOJPML.mjs.map +0 -1
package/dist/pairing/index.mjs
CHANGED
|
@@ -1,486 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
import { startClientHandshake, respondToHandshake, respondToLegacyHandshake, parseOfferUrl, derivePairRoot, deriveEpochRoutingToken, deriveEpochAuthToken, deriveEpochTokens, deriveOfferTokens, epochRoutingTokens, startTunnelHandshake, respondToTunnelHandshake } from '../chunk-JNTDSOZQ.mjs';
|
|
2
|
+
export { DEFAULT_PAIR_PAGE, HOSTED_RENDEZVOUS_URL, LEGACY_PAIR_VERSION, OFFER_URL_HOST, OFFER_URL_SCHEME, OFFER_VERSION, PAIRING_PROTOCOL_OUTDATED_MESSAGE, PAIR_PAGE_FP_PLACEHOLDER, PAIR_VERSION, PAIR_WEB_URL, PAIR_WEB_URL_TEMPLATE_CLOUD, PairingError, TUNNEL_E2E_VERSION, TunnelHandshakeError, currentEpoch, decodePairingHello, decodePairingReply, decodeTunnelAccept, decodeTunnelOffer, encodeOfferUrl, encodeOfferWebUrl, encodePairingMessage, encodeTunnelMessage, expectedPairHost, importPairRootKey, resolvePairPageUrl } from '../chunk-JNTDSOZQ.mjs';
|
|
3
|
+
import '../chunk-NQX3KBPW.mjs';
|
|
4
|
+
import { nodeCryptoProvider } from '../chunk-5HUHYBPR.mjs';
|
|
5
|
+
export { nodeCryptoProvider } from '../chunk-5HUHYBPR.mjs';
|
|
6
|
+
import '../chunk-432SC3XS.mjs';
|
|
7
|
+
export { webCryptoProvider } from '../chunk-3YRYO6LK.mjs';
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* @agentproto/secrets v0.1.0-alpha
|
|
7
11
|
* AIP-19 SECRETS.md `defineSecrets` reference implementation.
|
|
8
12
|
*/
|
|
9
|
-
var PAIR_VERSION = 1;
|
|
10
|
-
var HKDF_INFO = "agentproto/pair/v1";
|
|
11
|
-
var KEY_LEN = 32;
|
|
12
|
-
var PairingError = class extends Error {
|
|
13
|
-
code;
|
|
14
|
-
constructor(code, message) {
|
|
15
|
-
super(message);
|
|
16
|
-
this.name = "PairingError";
|
|
17
|
-
this.code = code;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
function x25519PublicKey(b64Der, what) {
|
|
21
|
-
try {
|
|
22
|
-
return createPublicKey({
|
|
23
|
-
key: Buffer.from(b64Der, "base64"),
|
|
24
|
-
format: "der",
|
|
25
|
-
type: "spki"
|
|
26
|
-
});
|
|
27
|
-
} catch {
|
|
28
|
-
throw new PairingError("invalid_key", `invalid ${what} X25519 public key`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function x25519PrivateKey(b64Der, what) {
|
|
32
|
-
try {
|
|
33
|
-
return createPrivateKey({
|
|
34
|
-
key: Buffer.from(b64Der, "base64"),
|
|
35
|
-
format: "der",
|
|
36
|
-
type: "pkcs8"
|
|
37
|
-
});
|
|
38
|
-
} catch {
|
|
39
|
-
throw new PairingError("invalid_key", `invalid ${what} X25519 private key`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function transcriptHash(ePubB64, ct0, dePubB64) {
|
|
43
|
-
return createHash("sha256").update(Buffer.from(ePubB64, "base64")).update(Buffer.from(ct0, "utf8")).update(Buffer.from(dePubB64, "base64")).digest();
|
|
44
|
-
}
|
|
45
|
-
function deriveDirectionKeys(ecdhEphemeral, ecdhStatic, transcript) {
|
|
46
|
-
const ikm = Buffer.concat([ecdhEphemeral, ecdhStatic]);
|
|
47
|
-
const okm = Buffer.from(hkdfSync("sha256", ikm, transcript, HKDF_INFO, KEY_LEN * 2));
|
|
48
|
-
return {
|
|
49
|
-
kc2d: okm.subarray(0, KEY_LEN),
|
|
50
|
-
kd2c: okm.subarray(KEY_LEN, KEY_LEN * 2)
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function startClientHandshake(params) {
|
|
54
|
-
const daemonStatic = x25519PublicKey(params.daemonX25519Pub, "daemon");
|
|
55
|
-
const ephemeral = generateKeyPairSync("x25519");
|
|
56
|
-
const ePubB64 = ephemeral.publicKey.export({ type: "spki", format: "der" }).toString("base64");
|
|
57
|
-
const payload = {
|
|
58
|
-
clientPub: ePubB64,
|
|
59
|
-
clientName: params.clientName,
|
|
60
|
-
offerToken: params.offerToken
|
|
61
|
-
};
|
|
62
|
-
const ct0 = seal(JSON.stringify(payload), params.daemonX25519Pub);
|
|
63
|
-
const hello = { v: PAIR_VERSION, ePub: ePubB64, ct0 };
|
|
64
|
-
const complete = (reply) => {
|
|
65
|
-
if (reply.v !== PAIR_VERSION) {
|
|
66
|
-
throw new PairingError("malformed_reply", `unsupported reply version ${reply.v}`);
|
|
67
|
-
}
|
|
68
|
-
const dePub = x25519PublicKey(reply.dePub, "daemon ephemeral");
|
|
69
|
-
const transcript = transcriptHash(ePubB64, ct0, reply.dePub);
|
|
70
|
-
if (!verifyTranscript(params.daemonEd25519Pub, transcript, reply.sig)) {
|
|
71
|
-
throw new PairingError(
|
|
72
|
-
"bad_signature",
|
|
73
|
-
"daemon transcript signature did not verify against the offered key"
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
const ecdhEphemeral = diffieHellman({ privateKey: ephemeral.privateKey, publicKey: dePub });
|
|
77
|
-
const ecdhStatic = diffieHellman({ privateKey: ephemeral.privateKey, publicKey: daemonStatic });
|
|
78
|
-
const { kc2d, kd2c } = deriveDirectionKeys(ecdhEphemeral, ecdhStatic, transcript);
|
|
79
|
-
return {
|
|
80
|
-
sendKey: kc2d,
|
|
81
|
-
recvKey: kd2c,
|
|
82
|
-
peerFingerprint: identityFingerprint(params.daemonX25519Pub),
|
|
83
|
-
transcriptHash: transcript,
|
|
84
|
-
clientName: params.clientName
|
|
85
|
-
};
|
|
86
|
-
};
|
|
87
|
-
return { hello, complete };
|
|
88
|
-
}
|
|
89
|
-
function respondToHandshake(hello, params) {
|
|
90
|
-
if (hello.v !== PAIR_VERSION) {
|
|
91
|
-
throw new PairingError("malformed_hello", `unsupported hello version ${hello.v}`);
|
|
92
|
-
}
|
|
93
|
-
const clientEphemeral = x25519PublicKey(hello.ePub, "client ephemeral");
|
|
94
|
-
let payloadJson;
|
|
95
|
-
try {
|
|
96
|
-
payloadJson = unseal(hello.ct0, params.identity.x25519.priv);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
if (err instanceof SealError) {
|
|
99
|
-
throw new PairingError(
|
|
100
|
-
"unseal_failed",
|
|
101
|
-
"could not open sealed hello \u2014 wrong daemon key or tampered ct\u2080"
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
throw err;
|
|
105
|
-
}
|
|
106
|
-
const payload = parseHelloPayload(payloadJson);
|
|
107
|
-
if (payload.clientPub !== hello.ePub) {
|
|
108
|
-
throw new PairingError(
|
|
109
|
-
"ephemeral_mismatch",
|
|
110
|
-
"sealed clientPub does not match the hello ephemeral key"
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
if (!params.verifyOfferToken(payload.offerToken)) {
|
|
114
|
-
throw new PairingError("offer_rejected", "offer token was rejected (unknown, expired, or spent)");
|
|
115
|
-
}
|
|
116
|
-
const daemonEphemeral = generateKeyPairSync("x25519");
|
|
117
|
-
const dePubB64 = daemonEphemeral.publicKey.export({ type: "spki", format: "der" }).toString("base64");
|
|
118
|
-
const transcript = transcriptHash(hello.ePub, hello.ct0, dePubB64);
|
|
119
|
-
const sig = signTranscript(params.identity.ed25519.priv, transcript);
|
|
120
|
-
const daemonStaticPriv = x25519PrivateKey(params.identity.x25519.priv, "daemon static");
|
|
121
|
-
const ecdhEphemeral = diffieHellman({ privateKey: daemonEphemeral.privateKey, publicKey: clientEphemeral });
|
|
122
|
-
const ecdhStatic = diffieHellman({ privateKey: daemonStaticPriv, publicKey: clientEphemeral });
|
|
123
|
-
const { kc2d, kd2c } = deriveDirectionKeys(ecdhEphemeral, ecdhStatic, transcript);
|
|
124
|
-
return {
|
|
125
|
-
reply: { v: PAIR_VERSION, dePub: dePubB64, sig },
|
|
126
|
-
session: {
|
|
127
|
-
// Daemon sends on c2d's counterpart: it RECEIVES client→daemon (kc2d)
|
|
128
|
-
// and SENDS daemon→client (kd2c).
|
|
129
|
-
sendKey: kd2c,
|
|
130
|
-
recvKey: kc2d,
|
|
131
|
-
peerFingerprint: identityFingerprint(payload.clientPub),
|
|
132
|
-
transcriptHash: transcript,
|
|
133
|
-
clientName: payload.clientName
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
function isRecord(v) {
|
|
138
|
-
return typeof v === "object" && v !== null;
|
|
139
|
-
}
|
|
140
|
-
function parseHelloPayload(json) {
|
|
141
|
-
let parsed;
|
|
142
|
-
try {
|
|
143
|
-
parsed = JSON.parse(json);
|
|
144
|
-
} catch {
|
|
145
|
-
throw new PairingError("malformed_hello", "sealed hello payload was not valid JSON");
|
|
146
|
-
}
|
|
147
|
-
if (!isRecord(parsed) || typeof parsed["clientPub"] !== "string" || typeof parsed["clientName"] !== "string" || typeof parsed["offerToken"] !== "string") {
|
|
148
|
-
throw new PairingError("malformed_hello", "sealed hello payload is missing required fields");
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
clientPub: parsed["clientPub"],
|
|
152
|
-
clientName: parsed["clientName"],
|
|
153
|
-
offerToken: parsed["offerToken"]
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function encodePairingMessage(message) {
|
|
157
|
-
return Buffer.from(JSON.stringify(message), "utf8");
|
|
158
|
-
}
|
|
159
|
-
function decodePairingHello(bytes) {
|
|
160
|
-
const parsed = parseJson(bytes, "malformed_hello");
|
|
161
|
-
if (!isRecord(parsed) || parsed["v"] !== PAIR_VERSION || typeof parsed["ePub"] !== "string" || typeof parsed["ct0"] !== "string") {
|
|
162
|
-
throw new PairingError("malformed_hello", "hello message is malformed");
|
|
163
|
-
}
|
|
164
|
-
return { v: PAIR_VERSION, ePub: parsed["ePub"], ct0: parsed["ct0"] };
|
|
165
|
-
}
|
|
166
|
-
function decodePairingReply(bytes) {
|
|
167
|
-
const parsed = parseJson(bytes, "malformed_reply");
|
|
168
|
-
if (!isRecord(parsed) || parsed["v"] !== PAIR_VERSION || typeof parsed["dePub"] !== "string" || typeof parsed["sig"] !== "string") {
|
|
169
|
-
throw new PairingError("malformed_reply", "reply message is malformed");
|
|
170
|
-
}
|
|
171
|
-
return { v: PAIR_VERSION, dePub: parsed["dePub"], sig: parsed["sig"] };
|
|
172
|
-
}
|
|
173
|
-
function parseJson(bytes, code) {
|
|
174
|
-
try {
|
|
175
|
-
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
176
|
-
} catch {
|
|
177
|
-
throw new PairingError(code, "message was not valid JSON (truncated or corrupt)");
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// src/pairing/offer-url.ts
|
|
182
|
-
var OFFER_URL_SCHEME = "agentproto:";
|
|
183
|
-
var OFFER_URL_HOST = "pair";
|
|
184
|
-
var OFFER_VERSION = 1;
|
|
185
|
-
function b64ToB64url(b64) {
|
|
186
|
-
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
187
|
-
}
|
|
188
|
-
function b64urlToB64(b64url2) {
|
|
189
|
-
const replaced = b64url2.replace(/-/g, "+").replace(/_/g, "/");
|
|
190
|
-
const pad = replaced.length % 4;
|
|
191
|
-
return pad === 0 ? replaced : replaced + "=".repeat(4 - pad);
|
|
192
|
-
}
|
|
193
|
-
function isB64url(s) {
|
|
194
|
-
return s.length > 0 && /^[A-Za-z0-9_-]+$/.test(s);
|
|
195
|
-
}
|
|
196
|
-
function encodeOfferUrl(offer) {
|
|
197
|
-
const params = new URLSearchParams();
|
|
198
|
-
params.set("v", String(OFFER_VERSION));
|
|
199
|
-
params.set("rv", offer.rendezvousUrl);
|
|
200
|
-
params.set("id", offer.fingerprint);
|
|
201
|
-
params.set("pk", b64ToB64url(offer.daemonX25519Pub));
|
|
202
|
-
params.set("sk", b64ToB64url(offer.daemonEd25519Pub));
|
|
203
|
-
params.set("t", offer.token);
|
|
204
|
-
params.set("exp", String(offer.exp));
|
|
205
|
-
return `${OFFER_URL_SCHEME}//${OFFER_URL_HOST}?${params.toString()}`;
|
|
206
|
-
}
|
|
207
|
-
function parseOfferUrl(url, opts = {}) {
|
|
208
|
-
let parsed;
|
|
209
|
-
try {
|
|
210
|
-
parsed = new URL(url);
|
|
211
|
-
} catch {
|
|
212
|
-
throw new PairingError("malformed_offer", "offer is not a valid URL");
|
|
213
|
-
}
|
|
214
|
-
if (parsed.protocol !== OFFER_URL_SCHEME) {
|
|
215
|
-
throw new PairingError(
|
|
216
|
-
"malformed_offer",
|
|
217
|
-
`offer scheme must be "${OFFER_URL_SCHEME}//" (got "${parsed.protocol}//")`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
const host = parsed.host || parsed.pathname.replace(/^\/+/, "");
|
|
221
|
-
if (host !== OFFER_URL_HOST) {
|
|
222
|
-
throw new PairingError("malformed_offer", `offer host must be "${OFFER_URL_HOST}"`);
|
|
223
|
-
}
|
|
224
|
-
const q = parsed.searchParams;
|
|
225
|
-
const v = q.get("v");
|
|
226
|
-
if (v !== String(OFFER_VERSION)) {
|
|
227
|
-
throw new PairingError("malformed_offer", `unsupported offer version "${v ?? "(absent)"}"`);
|
|
228
|
-
}
|
|
229
|
-
const rendezvousUrl = req(q, "rv");
|
|
230
|
-
let rvParsed;
|
|
231
|
-
try {
|
|
232
|
-
rvParsed = new URL(rendezvousUrl);
|
|
233
|
-
} catch {
|
|
234
|
-
throw new PairingError("malformed_offer", "offer `rv` is not a valid URL");
|
|
235
|
-
}
|
|
236
|
-
if (rvParsed.protocol !== "ws:" && rvParsed.protocol !== "wss:") {
|
|
237
|
-
throw new PairingError("malformed_offer", "offer `rv` must be a ws:// or wss:// URL");
|
|
238
|
-
}
|
|
239
|
-
const fingerprint = req(q, "id");
|
|
240
|
-
if (!/^[0-9a-f]{16}$/.test(fingerprint)) {
|
|
241
|
-
throw new PairingError("malformed_offer", "offer `id` is not a 16-hex fingerprint");
|
|
242
|
-
}
|
|
243
|
-
const pkUrl = req(q, "pk");
|
|
244
|
-
const skUrl = req(q, "sk");
|
|
245
|
-
if (!isB64url(pkUrl) || !isB64url(skUrl)) {
|
|
246
|
-
throw new PairingError("malformed_offer", "offer `pk`/`sk` must be base64url");
|
|
247
|
-
}
|
|
248
|
-
const daemonX25519Pub = b64urlToB64(pkUrl);
|
|
249
|
-
const daemonEd25519Pub = b64urlToB64(skUrl);
|
|
250
|
-
const token = req(q, "t");
|
|
251
|
-
if (!isB64url(token)) {
|
|
252
|
-
throw new PairingError("malformed_offer", "offer `t` (token) must be base64url");
|
|
253
|
-
}
|
|
254
|
-
const expRaw = req(q, "exp");
|
|
255
|
-
const exp = Number(expRaw);
|
|
256
|
-
if (!Number.isInteger(exp) || exp <= 0) {
|
|
257
|
-
throw new PairingError("malformed_offer", "offer `exp` is not a positive unix timestamp");
|
|
258
|
-
}
|
|
259
|
-
if (identityFingerprint(daemonX25519Pub) !== fingerprint) {
|
|
260
|
-
throw new PairingError(
|
|
261
|
-
"malformed_offer",
|
|
262
|
-
"offer `id` does not match fingerprint(pk) \u2014 tampered or corrupt offer"
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
if (opts.now !== void 0 && exp * 1e3 <= opts.now) {
|
|
266
|
-
throw new PairingError("offer_expired", "offer has expired");
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
v: OFFER_VERSION,
|
|
270
|
-
rendezvousUrl,
|
|
271
|
-
fingerprint,
|
|
272
|
-
daemonX25519Pub,
|
|
273
|
-
daemonEd25519Pub,
|
|
274
|
-
token,
|
|
275
|
-
exp
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
function req(q, key) {
|
|
279
|
-
const v = q.get(key);
|
|
280
|
-
if (v === null || v === "") {
|
|
281
|
-
throw new PairingError("malformed_offer", `offer is missing required param "${key}"`);
|
|
282
|
-
}
|
|
283
|
-
return v;
|
|
284
|
-
}
|
|
285
13
|
|
|
286
|
-
// src/pairing/
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
var RV_ROUTE_INFO_PREFIX = "agentproto/rv-route";
|
|
290
|
-
var RV_ROUTE_SALT = "agentproto/rv-route-salt";
|
|
291
|
-
var ROUTE_TOKEN_LEN = 16;
|
|
292
|
-
var PAIR_ROOT_LEN = 32;
|
|
293
|
-
var MS_PER_DAY = 864e5;
|
|
294
|
-
function toBuf(u8) {
|
|
295
|
-
return Buffer.isBuffer(u8) ? u8 : Buffer.from(u8);
|
|
296
|
-
}
|
|
297
|
-
function b64url(bytes) {
|
|
298
|
-
return bytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
299
|
-
}
|
|
300
|
-
function derivePairRoot(session) {
|
|
301
|
-
const k1 = toBuf(session.sendKey);
|
|
302
|
-
const k2 = toBuf(session.recvKey);
|
|
303
|
-
const [lo, hi] = Buffer.compare(k1, k2) <= 0 ? [k1, k2] : [k2, k1];
|
|
304
|
-
const ikm = Buffer.concat([lo, hi]);
|
|
305
|
-
const okm = hkdfSync("sha256", ikm, toBuf(session.transcriptHash), PAIR_ROOT_INFO, PAIR_ROOT_LEN);
|
|
306
|
-
return Buffer.from(okm).toString("base64");
|
|
307
|
-
}
|
|
308
|
-
function currentEpoch(now = Date.now()) {
|
|
309
|
-
return Math.floor(now / MS_PER_DAY);
|
|
310
|
-
}
|
|
311
|
-
function deriveEpochRoutingToken(pairRoot, epoch) {
|
|
312
|
-
const ikm = Buffer.from(pairRoot, "base64");
|
|
313
|
-
const epochBytes = Buffer.alloc(8);
|
|
314
|
-
epochBytes.writeBigUInt64BE(BigInt(epoch));
|
|
315
|
-
const info = Buffer.concat([Buffer.from(RV_ROUTE_INFO_PREFIX, "utf8"), epochBytes]);
|
|
316
|
-
const okm = hkdfSync("sha256", ikm, Buffer.from(RV_ROUTE_SALT, "utf8"), info, ROUTE_TOKEN_LEN);
|
|
317
|
-
return b64url(Buffer.from(okm));
|
|
318
|
-
}
|
|
319
|
-
function epochRoutingTokens(pairRoot, now = Date.now()) {
|
|
320
|
-
const epoch = currentEpoch(now);
|
|
321
|
-
return [
|
|
322
|
-
{ epoch, token: deriveEpochRoutingToken(pairRoot, epoch) },
|
|
323
|
-
{ epoch: epoch - 1, token: deriveEpochRoutingToken(pairRoot, epoch - 1) }
|
|
324
|
-
];
|
|
325
|
-
}
|
|
326
|
-
var TUNNEL_E2E_VERSION = 1;
|
|
327
|
-
var SESSION_INFO = "agentproto/tunnel-e2e/v1";
|
|
328
|
-
var AUTH_INFO = "agentproto/tunnel-e2e/auth/v1";
|
|
329
|
-
var OFFER_MAC_LABEL = "offer/v1";
|
|
330
|
-
var ACCEPT_MAC_LABEL = "accept/v1";
|
|
331
|
-
var KEY_LEN2 = 32;
|
|
332
|
-
var AUTH_KEY_LEN = 32;
|
|
333
|
-
var TunnelHandshakeError = class extends Error {
|
|
334
|
-
code;
|
|
335
|
-
constructor(code, message) {
|
|
336
|
-
super(message);
|
|
337
|
-
this.name = "TunnelHandshakeError";
|
|
338
|
-
this.code = code;
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
function tokenSalt(token) {
|
|
342
|
-
return createHash("sha256").update(Buffer.from(token, "utf8")).digest();
|
|
343
|
-
}
|
|
344
|
-
function deriveAuthKey(token) {
|
|
345
|
-
return Buffer.from(
|
|
346
|
-
hkdfSync("sha256", Buffer.from(token, "utf8"), tokenSalt(token), AUTH_INFO, AUTH_KEY_LEN)
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
function computeMac(authKey, label, ...parts) {
|
|
350
|
-
const h = createHmac("sha256", authKey);
|
|
351
|
-
h.update(Buffer.from(label, "utf8"));
|
|
352
|
-
for (const p of parts) h.update(p);
|
|
353
|
-
return h.digest();
|
|
354
|
-
}
|
|
355
|
-
function macMatches(expected, receivedB64) {
|
|
356
|
-
let received;
|
|
357
|
-
try {
|
|
358
|
-
received = Buffer.from(receivedB64, "base64");
|
|
359
|
-
} catch {
|
|
360
|
-
return false;
|
|
361
|
-
}
|
|
362
|
-
if (received.length !== expected.length) return false;
|
|
363
|
-
return timingSafeEqual(received, expected);
|
|
14
|
+
// src/pairing/index.ts
|
|
15
|
+
function startClientHandshake2(params, crypto = nodeCryptoProvider) {
|
|
16
|
+
return startClientHandshake(params, crypto);
|
|
364
17
|
}
|
|
365
|
-
function
|
|
366
|
-
|
|
367
|
-
return createPublicKey({ key: Buffer.from(b64Der, "base64"), format: "der", type: "spki" });
|
|
368
|
-
} catch {
|
|
369
|
-
throw new TunnelHandshakeError("invalid_key", "peer ephemeral X25519 public key is invalid");
|
|
370
|
-
}
|
|
18
|
+
function respondToHandshake2(hello, params, crypto = nodeCryptoProvider) {
|
|
19
|
+
return respondToHandshake(hello, params, crypto);
|
|
371
20
|
}
|
|
372
|
-
function
|
|
373
|
-
return
|
|
21
|
+
function respondToLegacyHandshake2(helloBytes, identity, crypto = nodeCryptoProvider) {
|
|
22
|
+
return respondToLegacyHandshake(helloBytes, identity, crypto);
|
|
374
23
|
}
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
return { kd2h: okm.subarray(0, KEY_LEN2), kh2d: okm.subarray(KEY_LEN2, KEY_LEN2 * 2) };
|
|
24
|
+
function parseOfferUrl2(url, opts = {}, crypto = nodeCryptoProvider) {
|
|
25
|
+
return parseOfferUrl(url, opts, crypto);
|
|
378
26
|
}
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
const pubRaw = kp.publicKey.export({ type: "spki", format: "der" });
|
|
382
|
-
return { priv: kp.privateKey, pubB64: pubRaw.toString("base64"), pubRaw };
|
|
27
|
+
function derivePairRoot2(session, crypto = nodeCryptoProvider) {
|
|
28
|
+
return derivePairRoot(session, crypto);
|
|
383
29
|
}
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
const salt = tokenSalt(token);
|
|
387
|
-
const eph = generateEphemeral();
|
|
388
|
-
const offer = {
|
|
389
|
-
v: TUNNEL_E2E_VERSION,
|
|
390
|
-
ePub: eph.pubB64,
|
|
391
|
-
mac: computeMac(authKey, OFFER_MAC_LABEL, eph.pubRaw).toString("base64")
|
|
392
|
-
};
|
|
393
|
-
const complete = (accept) => {
|
|
394
|
-
if (accept.v !== TUNNEL_E2E_VERSION) {
|
|
395
|
-
throw new TunnelHandshakeError(
|
|
396
|
-
"unsupported_version",
|
|
397
|
-
`unsupported accept version ${String(accept.v)}`
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
const hostPub = x25519PublicKey2(accept.ePub);
|
|
401
|
-
const hostPubRaw = Buffer.from(accept.ePub, "base64");
|
|
402
|
-
const expected = computeMac(authKey, ACCEPT_MAC_LABEL, eph.pubRaw, hostPubRaw);
|
|
403
|
-
if (!macMatches(expected, accept.mac)) {
|
|
404
|
-
throw new TunnelHandshakeError(
|
|
405
|
-
"bad_auth",
|
|
406
|
-
"host accept did not authenticate under the shared tunnel token"
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
const ecdh = diffieHellman({ privateKey: eph.priv, publicKey: hostPub });
|
|
410
|
-
const { kd2h, kh2d } = deriveDirectionKeys2(ecdh, salt);
|
|
411
|
-
return {
|
|
412
|
-
// Daemon SENDS daemon→host (kd2h) and RECEIVES host→daemon (kh2d).
|
|
413
|
-
sendKey: kd2h,
|
|
414
|
-
recvKey: kh2d,
|
|
415
|
-
transcriptHash: transcriptHash2(eph.pubRaw, hostPubRaw)
|
|
416
|
-
};
|
|
417
|
-
};
|
|
418
|
-
return { offer, complete };
|
|
30
|
+
function deriveEpochRoutingToken2(pairRoot, epoch, crypto = nodeCryptoProvider) {
|
|
31
|
+
return deriveEpochRoutingToken(pairRoot, epoch, crypto);
|
|
419
32
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
throw new TunnelHandshakeError(
|
|
423
|
-
"unsupported_version",
|
|
424
|
-
`unsupported offer version ${String(offer.v)}`
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
const authKey = deriveAuthKey(token);
|
|
428
|
-
const salt = tokenSalt(token);
|
|
429
|
-
const daemonPub = x25519PublicKey2(offer.ePub);
|
|
430
|
-
const daemonPubRaw = Buffer.from(offer.ePub, "base64");
|
|
431
|
-
const expected = computeMac(authKey, OFFER_MAC_LABEL, daemonPubRaw);
|
|
432
|
-
if (!macMatches(expected, offer.mac)) {
|
|
433
|
-
throw new TunnelHandshakeError(
|
|
434
|
-
"bad_auth",
|
|
435
|
-
"daemon offer did not authenticate under the shared tunnel token"
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
const eph = generateEphemeral();
|
|
439
|
-
const accept = {
|
|
440
|
-
v: TUNNEL_E2E_VERSION,
|
|
441
|
-
ePub: eph.pubB64,
|
|
442
|
-
mac: computeMac(authKey, ACCEPT_MAC_LABEL, daemonPubRaw, eph.pubRaw).toString("base64")
|
|
443
|
-
};
|
|
444
|
-
const ecdh = diffieHellman({ privateKey: eph.priv, publicKey: daemonPub });
|
|
445
|
-
const { kd2h, kh2d } = deriveDirectionKeys2(ecdh, salt);
|
|
446
|
-
return {
|
|
447
|
-
accept,
|
|
448
|
-
session: {
|
|
449
|
-
// Host SENDS host→daemon (kh2d) and RECEIVES daemon→host (kd2h).
|
|
450
|
-
sendKey: kh2d,
|
|
451
|
-
recvKey: kd2h,
|
|
452
|
-
transcriptHash: transcriptHash2(daemonPubRaw, eph.pubRaw)
|
|
453
|
-
}
|
|
454
|
-
};
|
|
33
|
+
function deriveEpochAuthToken2(pairRoot, epoch, crypto = nodeCryptoProvider) {
|
|
34
|
+
return deriveEpochAuthToken(pairRoot, epoch, crypto);
|
|
455
35
|
}
|
|
456
|
-
function
|
|
457
|
-
return
|
|
36
|
+
function deriveEpochTokens2(pairRoot, epoch, crypto = nodeCryptoProvider) {
|
|
37
|
+
return deriveEpochTokens(pairRoot, epoch, crypto);
|
|
458
38
|
}
|
|
459
|
-
function
|
|
460
|
-
return
|
|
39
|
+
function deriveOfferTokens2(offerSecret, crypto = nodeCryptoProvider) {
|
|
40
|
+
return deriveOfferTokens(offerSecret, crypto);
|
|
461
41
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
465
|
-
} catch {
|
|
466
|
-
throw new TunnelHandshakeError(code, "message was not valid JSON (truncated or corrupt)");
|
|
467
|
-
}
|
|
42
|
+
function epochRoutingTokens2(pairRoot, now = Date.now(), crypto = nodeCryptoProvider) {
|
|
43
|
+
return epochRoutingTokens(pairRoot, now, crypto);
|
|
468
44
|
}
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
if (!isRecord2(parsed) || parsed["v"] !== TUNNEL_E2E_VERSION || typeof parsed["ePub"] !== "string" || typeof parsed["mac"] !== "string") {
|
|
472
|
-
throw new TunnelHandshakeError("malformed_offer", "offer message is malformed");
|
|
473
|
-
}
|
|
474
|
-
return { v: TUNNEL_E2E_VERSION, ePub: parsed["ePub"], mac: parsed["mac"] };
|
|
45
|
+
function startTunnelHandshake2(token, crypto = nodeCryptoProvider) {
|
|
46
|
+
return startTunnelHandshake(token, crypto);
|
|
475
47
|
}
|
|
476
|
-
function
|
|
477
|
-
|
|
478
|
-
if (!isRecord2(parsed) || parsed["v"] !== TUNNEL_E2E_VERSION || typeof parsed["ePub"] !== "string" || typeof parsed["mac"] !== "string") {
|
|
479
|
-
throw new TunnelHandshakeError("malformed_accept", "accept message is malformed");
|
|
480
|
-
}
|
|
481
|
-
return { v: TUNNEL_E2E_VERSION, ePub: parsed["ePub"], mac: parsed["mac"] };
|
|
48
|
+
function respondToTunnelHandshake2(offer, token, crypto = nodeCryptoProvider) {
|
|
49
|
+
return respondToTunnelHandshake(offer, token, crypto);
|
|
482
50
|
}
|
|
483
51
|
|
|
484
|
-
export {
|
|
52
|
+
export { deriveEpochAuthToken2 as deriveEpochAuthToken, deriveEpochRoutingToken2 as deriveEpochRoutingToken, deriveEpochTokens2 as deriveEpochTokens, deriveOfferTokens2 as deriveOfferTokens, derivePairRoot2 as derivePairRoot, epochRoutingTokens2 as epochRoutingTokens, parseOfferUrl2 as parseOfferUrl, respondToHandshake2 as respondToHandshake, respondToLegacyHandshake2 as respondToLegacyHandshake, respondToTunnelHandshake2 as respondToTunnelHandshake, startClientHandshake2 as startClientHandshake, startTunnelHandshake2 as startTunnelHandshake };
|
|
485
53
|
//# sourceMappingURL=index.mjs.map
|
|
486
54
|
//# sourceMappingURL=index.mjs.map
|