@calimero-network/mero-js 25.0.7 → 25.1.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 +25 -0
- package/dist/agent-gate/chacha20poly1305.d.ts +20 -0
- package/dist/agent-gate/chacha20poly1305.js +148 -0
- package/dist/agent-gate/gate.d.ts +115 -0
- package/dist/agent-gate/gate.js +232 -0
- package/dist/agent-gate/hpke.d.ts +23 -0
- package/dist/agent-gate/hpke.js +84 -0
- package/dist/agent-gate/index.d.ts +4 -0
- package/dist/agent-gate/index.js +2 -0
- package/dist/agent-gate/policy.d.ts +65 -0
- package/dist/agent-gate/policy.js +103 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +550 -101
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +550 -101
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -48,14 +48,14 @@ function extractErrorMessage(bodyText) {
|
|
|
48
48
|
return void 0;
|
|
49
49
|
}
|
|
50
50
|
var HTTPError = class extends Error {
|
|
51
|
-
constructor(status, statusText,
|
|
51
|
+
constructor(status, statusText, url2, headers, bodyText) {
|
|
52
52
|
const explanation = extractErrorMessage(bodyText) ?? (status === 0 && bodyText?.trim() ? bodyText.trim() : void 0);
|
|
53
53
|
super(
|
|
54
54
|
explanation ? `HTTP ${status} ${statusText}: ${explanation}` : `HTTP ${status} ${statusText}`
|
|
55
55
|
);
|
|
56
56
|
this.status = status;
|
|
57
57
|
this.statusText = statusText;
|
|
58
|
-
this.url =
|
|
58
|
+
this.url = url2;
|
|
59
59
|
this.headers = headers;
|
|
60
60
|
this.bodyText = bodyText;
|
|
61
61
|
this.name = "HTTPError";
|
|
@@ -72,8 +72,8 @@ var HTTPError = class extends Error {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
var AuthRevokedError = class extends HTTPError {
|
|
75
|
-
constructor(reason, status, statusText,
|
|
76
|
-
super(status, statusText,
|
|
75
|
+
constructor(reason, status, statusText, url2, headers, bodyText) {
|
|
76
|
+
super(status, statusText, url2, headers, bodyText);
|
|
77
77
|
this.reason = reason;
|
|
78
78
|
this.name = "AuthRevokedError";
|
|
79
79
|
this.message = `Authentication revoked (${reason}): HTTP ${status} ${statusText}`;
|
|
@@ -86,12 +86,12 @@ function headersToRecord(headers) {
|
|
|
86
86
|
});
|
|
87
87
|
return record;
|
|
88
88
|
}
|
|
89
|
-
function pathForProof(
|
|
89
|
+
function pathForProof(url2) {
|
|
90
90
|
try {
|
|
91
|
-
return new URL(
|
|
91
|
+
return new URL(url2).pathname;
|
|
92
92
|
} catch {
|
|
93
|
-
const query =
|
|
94
|
-
return query === -1 ?
|
|
93
|
+
const query = url2.indexOf("?");
|
|
94
|
+
return query === -1 ? url2 : url2.slice(0, query);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
var WebHttpClient = class {
|
|
@@ -157,7 +157,7 @@ var WebHttpClient = class {
|
|
|
157
157
|
}
|
|
158
158
|
async makeRequest(path, init, retryCount = 0, requestStartTime) {
|
|
159
159
|
const MAX_RETRY_ATTEMPTS = 1;
|
|
160
|
-
const
|
|
160
|
+
const url2 = this.buildUrl(path);
|
|
161
161
|
const startTime = requestStartTime ?? Date.now();
|
|
162
162
|
const signal = this.createAbortSignal(init);
|
|
163
163
|
const headers = await this.buildHeaders(init?.headers);
|
|
@@ -182,7 +182,7 @@ var WebHttpClient = class {
|
|
|
182
182
|
headersObj,
|
|
183
183
|
await this.transport.authorizeRequest({
|
|
184
184
|
method,
|
|
185
|
-
path: new URL(
|
|
185
|
+
path: new URL(url2).pathname,
|
|
186
186
|
// Narrowed by the guard above; `BodyInit` is wider than what can be
|
|
187
187
|
// signed.
|
|
188
188
|
body
|
|
@@ -242,7 +242,7 @@ var WebHttpClient = class {
|
|
|
242
242
|
if (this.transport.getProof) {
|
|
243
243
|
const proof = await this.transport.getProof({
|
|
244
244
|
method: requestInit.method ?? "GET",
|
|
245
|
-
path: pathForProof(
|
|
245
|
+
path: pathForProof(url2),
|
|
246
246
|
body: requestInit.body
|
|
247
247
|
});
|
|
248
248
|
if (proof) {
|
|
@@ -250,13 +250,13 @@ var WebHttpClient = class {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
try {
|
|
253
|
-
const response = await this.transport.fetch(
|
|
253
|
+
const response = await this.transport.fetch(url2, requestInit);
|
|
254
254
|
if (!response.ok) {
|
|
255
255
|
const bodyText = await this.getBodyText(response);
|
|
256
256
|
const httpError = new HTTPError(
|
|
257
257
|
response.status,
|
|
258
258
|
response.statusText,
|
|
259
|
-
|
|
259
|
+
url2,
|
|
260
260
|
response.headers,
|
|
261
261
|
bodyText
|
|
262
262
|
);
|
|
@@ -274,7 +274,7 @@ var WebHttpClient = class {
|
|
|
274
274
|
authError,
|
|
275
275
|
response.status,
|
|
276
276
|
response.statusText,
|
|
277
|
-
|
|
277
|
+
url2,
|
|
278
278
|
response.headers,
|
|
279
279
|
bodyText
|
|
280
280
|
);
|
|
@@ -339,7 +339,7 @@ var WebHttpClient = class {
|
|
|
339
339
|
throw new HTTPError(
|
|
340
340
|
0,
|
|
341
341
|
"Network Error",
|
|
342
|
-
|
|
342
|
+
url2,
|
|
343
343
|
new Headers(),
|
|
344
344
|
error instanceof Error ? error.message : "Unknown error"
|
|
345
345
|
);
|
|
@@ -442,7 +442,7 @@ function createBrowserHttpClient(options) {
|
|
|
442
442
|
const transport2 = {
|
|
443
443
|
// globalThis.fetch must be called as a method on globalThis, not through a
|
|
444
444
|
// variable, or browsers throw "Illegal invocation".
|
|
445
|
-
fetch: (
|
|
445
|
+
fetch: (url2, init) => options.fetch ? options.fetch(url2, init) : globalThis.fetch(url2, init),
|
|
446
446
|
baseUrl: options.baseUrl,
|
|
447
447
|
getAuthToken: options.getAuthToken,
|
|
448
448
|
getProof: options.getProof,
|
|
@@ -469,7 +469,7 @@ function createNodeHttpClient(options) {
|
|
|
469
469
|
// Wrap fetch in arrow function to prevent "Illegal invocation" error
|
|
470
470
|
// For globalThis.fetch, call it directly to preserve 'this' context
|
|
471
471
|
// For custom implementations, calling through the variable is safe
|
|
472
|
-
fetch: isDefaultFetch ? (
|
|
472
|
+
fetch: isDefaultFetch ? (url2, init) => globalThis.fetch(url2, init) : (url2, init) => fetchImpl(url2, init),
|
|
473
473
|
baseUrl: options.baseUrl,
|
|
474
474
|
getAuthToken: options.getAuthToken,
|
|
475
475
|
getProof: options.getProof,
|
|
@@ -1045,10 +1045,10 @@ function compareSemverId(a, b) {
|
|
|
1045
1045
|
if (nb) return 1;
|
|
1046
1046
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
1047
1047
|
}
|
|
1048
|
-
async function fetchRegistry(
|
|
1048
|
+
async function fetchRegistry(url2, kind, context) {
|
|
1049
1049
|
return withRetry(
|
|
1050
1050
|
async () => {
|
|
1051
|
-
const resp = await fetch(
|
|
1051
|
+
const resp = await fetch(url2, { signal: AbortSignal.timeout(1e4) });
|
|
1052
1052
|
if (!resp.ok) {
|
|
1053
1053
|
const err = new Error(
|
|
1054
1054
|
`registry ${kind} fetch failed (${resp.status}) for ${context}`
|
|
@@ -1100,9 +1100,9 @@ var AdminApiClient = class {
|
|
|
1100
1100
|
* the running `Context.applicationVersion` to detect "a new version exists".
|
|
1101
1101
|
*/
|
|
1102
1102
|
async getRegistryVersions(registryUrl, packageName) {
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
const resp = await fetchRegistry(
|
|
1103
|
+
const url2 = new URL("/api/v2/bundles", new URL(registryUrl).origin);
|
|
1104
|
+
url2.searchParams.set("package", packageName);
|
|
1105
|
+
const resp = await fetchRegistry(url2.toString(), "versions", packageName);
|
|
1106
1106
|
const bundles = await resp.json();
|
|
1107
1107
|
return (Array.isArray(bundles) ? bundles : []).map((b) => b.appVersion).filter((v) => typeof v === "string").sort((a, b) => compareSemver(b, a));
|
|
1108
1108
|
}
|
|
@@ -2420,11 +2420,11 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
|
|
|
2420
2420
|
}
|
|
2421
2421
|
|
|
2422
2422
|
// src/auth/index.ts
|
|
2423
|
-
function parseAuthCallback(
|
|
2423
|
+
function parseAuthCallback(url2) {
|
|
2424
2424
|
try {
|
|
2425
|
-
const hashIndex =
|
|
2425
|
+
const hashIndex = url2.indexOf("#");
|
|
2426
2426
|
if (hashIndex === -1) return null;
|
|
2427
|
-
const hash =
|
|
2427
|
+
const hash = url2.substring(hashIndex + 1);
|
|
2428
2428
|
const params = new URLSearchParams(hash);
|
|
2429
2429
|
const accessToken = params.get("access_token");
|
|
2430
2430
|
if (!accessToken) return null;
|
|
@@ -2781,7 +2781,7 @@ var SseClient = class {
|
|
|
2781
2781
|
throw new Error("SseClient needs either `getAuthToken` or `authorize`");
|
|
2782
2782
|
}
|
|
2783
2783
|
this.reconnectDelayMs = opts.reconnectDelayMs ?? 3e3;
|
|
2784
|
-
this.fetchImpl = (
|
|
2784
|
+
this.fetchImpl = (url2, init) => opts.fetch ? opts.fetch(url2, init) : globalThis.fetch(url2, init);
|
|
2785
2785
|
}
|
|
2786
2786
|
on(event, handler) {
|
|
2787
2787
|
const key = event;
|
|
@@ -2846,10 +2846,10 @@ var SseClient = class {
|
|
|
2846
2846
|
this.closed = false;
|
|
2847
2847
|
this.abortController = new AbortController();
|
|
2848
2848
|
try {
|
|
2849
|
-
const
|
|
2850
|
-
const response = await this.fetchImpl(
|
|
2849
|
+
const url2 = `${this.baseUrl}/sse`;
|
|
2850
|
+
const response = await this.fetchImpl(url2, {
|
|
2851
2851
|
headers: {
|
|
2852
|
-
...await this.authorize({ method: "GET", path: new URL(
|
|
2852
|
+
...await this.authorize({ method: "GET", path: new URL(url2).pathname }),
|
|
2853
2853
|
"Accept": "text/event-stream"
|
|
2854
2854
|
},
|
|
2855
2855
|
signal: this.abortController.signal
|
|
@@ -2862,7 +2862,7 @@ var SseClient = class {
|
|
|
2862
2862
|
terminal,
|
|
2863
2863
|
response.status,
|
|
2864
2864
|
response.statusText,
|
|
2865
|
-
|
|
2865
|
+
url2,
|
|
2866
2866
|
response.headers,
|
|
2867
2867
|
bodyText
|
|
2868
2868
|
);
|
|
@@ -2870,7 +2870,7 @@ var SseClient = class {
|
|
|
2870
2870
|
throw new HTTPError(
|
|
2871
2871
|
response.status,
|
|
2872
2872
|
response.statusText,
|
|
2873
|
-
|
|
2873
|
+
url2,
|
|
2874
2874
|
response.headers,
|
|
2875
2875
|
bodyText
|
|
2876
2876
|
);
|
|
@@ -3049,12 +3049,12 @@ var SseClient = class {
|
|
|
3049
3049
|
const params = {};
|
|
3050
3050
|
if (ids.contextIds && ids.contextIds.length > 0) params.contextIds = ids.contextIds;
|
|
3051
3051
|
if (ids.groupIds && ids.groupIds.length > 0) params.groupIds = ids.groupIds;
|
|
3052
|
-
const
|
|
3052
|
+
const url2 = `${this.baseUrl}/sse/subscription`;
|
|
3053
3053
|
const body = JSON.stringify({ id: this.sessionId, method, params });
|
|
3054
|
-
const response = await this.fetchImpl(
|
|
3054
|
+
const response = await this.fetchImpl(url2, {
|
|
3055
3055
|
method: "POST",
|
|
3056
3056
|
headers: {
|
|
3057
|
-
...await this.authorize({ method: "POST", path: new URL(
|
|
3057
|
+
...await this.authorize({ method: "POST", path: new URL(url2).pathname, body }),
|
|
3058
3058
|
"Content-Type": "application/json"
|
|
3059
3059
|
},
|
|
3060
3060
|
body,
|
|
@@ -5965,14 +5965,14 @@ function reportDataOf(quote) {
|
|
|
5965
5965
|
}
|
|
5966
5966
|
async function attestRelayNodeKey(opts) {
|
|
5967
5967
|
const nonce2 = crypto.getRandomValues(new Uint8Array(32));
|
|
5968
|
-
const
|
|
5968
|
+
const url2 = `${opts.relayUrl.replace(/\/+$/, "")}/admin-api/tee/attest`;
|
|
5969
5969
|
const body = JSON.stringify({
|
|
5970
5970
|
nonce: hex(nonce2),
|
|
5971
5971
|
bindNodeKey: true,
|
|
5972
5972
|
...opts.verify?.includeCollateral ? { includeCollateral: true } : {}
|
|
5973
5973
|
});
|
|
5974
5974
|
const init = { method: "POST", headers: { "Content-Type": "application/json" }, body };
|
|
5975
|
-
const response = opts.fetch ? await opts.fetch(
|
|
5975
|
+
const response = opts.fetch ? await opts.fetch(url2, init) : await globalThis.fetch(url2, init);
|
|
5976
5976
|
if (!response.ok) {
|
|
5977
5977
|
const text = await response.text().catch(() => "");
|
|
5978
5978
|
throw new Error(`the relay would not attest (HTTP ${response.status})${text ? `: ${text}` : ""}`);
|
|
@@ -6294,13 +6294,13 @@ function createSealedFetch(options) {
|
|
|
6294
6294
|
};
|
|
6295
6295
|
return async (input, init) => {
|
|
6296
6296
|
const request = new Request(input, init);
|
|
6297
|
-
const
|
|
6298
|
-
if (
|
|
6299
|
-
throw new Error(`Refusing to send ${
|
|
6297
|
+
const url2 = new URL(request.url);
|
|
6298
|
+
if (url2.origin !== base.origin || !url2.pathname.startsWith(`${basePath}/`)) {
|
|
6299
|
+
throw new Error(`Refusing to send ${url2.href} outside the sealed node at ${options.baseUrl}`);
|
|
6300
6300
|
}
|
|
6301
6301
|
const headers = [];
|
|
6302
6302
|
request.headers.forEach((value, name) => headers.push([name, value]));
|
|
6303
|
-
const head = { method: request.method, path:
|
|
6303
|
+
const head = { method: request.method, path: url2.pathname.slice(basePath.length) + url2.search, headers };
|
|
6304
6304
|
const body = new Uint8Array(await request.arrayBuffer());
|
|
6305
6305
|
const send = async (open2) => {
|
|
6306
6306
|
const { requestId, header, envelope } = await sealRequest(open2, head, body);
|
|
@@ -6522,7 +6522,7 @@ function createQuoteVerifier(options) {
|
|
|
6522
6522
|
const allowedMrtd = measurements(options.allowedMrtd ?? [], "allowedMrtd");
|
|
6523
6523
|
const allowedImages = (options.allowedMeasurements ?? []).map((image, index) => {
|
|
6524
6524
|
const label2 = `allowedMeasurements[${index}]`;
|
|
6525
|
-
return REGISTERS.map((
|
|
6525
|
+
return REGISTERS.map((register2) => measurements([image?.[register2] ?? ""], `${label2}.${register2}`)[0]).join();
|
|
6526
6526
|
});
|
|
6527
6527
|
const allowedRtmr = [
|
|
6528
6528
|
measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
|
|
@@ -6571,7 +6571,7 @@ function createQuoteVerifier(options) {
|
|
|
6571
6571
|
if (td.tdAttributes?.length !== 8 || (td.tdAttributes[0] & 1) !== 0) {
|
|
6572
6572
|
throw new Error("The quote is from a debug TD, or its TD attributes cannot be read");
|
|
6573
6573
|
}
|
|
6574
|
-
const measured = [td.mrTd, td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3].map((
|
|
6574
|
+
const measured = [td.mrTd, td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3].map((register2) => hex(register2));
|
|
6575
6575
|
if (allowedImages.length > 0 && !allowedImages.includes(measured.join())) {
|
|
6576
6576
|
throw new Error(
|
|
6577
6577
|
`The measurements (MRTD ${measured[0]}, RTMR0-3 ${measured.slice(1).join(", ")}) are not an image this verifier trusts`
|
|
@@ -6581,10 +6581,10 @@ function createQuoteVerifier(options) {
|
|
|
6581
6581
|
throw new Error(`MRTD ${measured[0]} is not an image this verifier trusts`);
|
|
6582
6582
|
}
|
|
6583
6583
|
const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];
|
|
6584
|
-
registers.forEach((
|
|
6584
|
+
registers.forEach((register2, index) => {
|
|
6585
6585
|
const allowed = allowedRtmr[index];
|
|
6586
|
-
if (allowed.length > 0 && !allowed.includes(hex(
|
|
6587
|
-
throw new Error(`RTMR${index} ${hex(
|
|
6586
|
+
if (allowed.length > 0 && !allowed.includes(hex(register2))) {
|
|
6587
|
+
throw new Error(`RTMR${index} ${hex(register2)} is not one this verifier trusts`);
|
|
6588
6588
|
}
|
|
6589
6589
|
});
|
|
6590
6590
|
if (hex(td.reportData) !== `${nonce2}${reportDataSuffix}`.toLowerCase()) {
|
|
@@ -7061,7 +7061,7 @@ async function verifySignedNodeRelease(publishedMrtds, bundle, options = {}) {
|
|
|
7061
7061
|
throw new Error(`The signed release is a ${JSON.stringify(release?.role)} release, not a node one`);
|
|
7062
7062
|
}
|
|
7063
7063
|
const profiles = Object.values(release.profiles ?? {});
|
|
7064
|
-
const measured = (image) => MEASUREMENTS.every((
|
|
7064
|
+
const measured = (image) => MEASUREMENTS.every((register2) => /^[0-9a-f]{96}$/i.test(String(image?.[register2])));
|
|
7065
7065
|
if (typeof release.tag !== "string" || profiles.length === 0 || !profiles.every(measured)) {
|
|
7066
7066
|
throw new Error("The signed file is not a release's published-mrtds.json: it lists no measured profiles");
|
|
7067
7067
|
}
|
|
@@ -7070,17 +7070,17 @@ async function verifySignedNodeRelease(publishedMrtds, bundle, options = {}) {
|
|
|
7070
7070
|
}
|
|
7071
7071
|
return release;
|
|
7072
7072
|
}
|
|
7073
|
-
async function fetchNodeRelease(
|
|
7074
|
-
const response = await (options.fetch ?? fetch)(
|
|
7075
|
-
if (!response.ok) throw new Error(`Fetching the node release from ${
|
|
7073
|
+
async function fetchNodeRelease(url2, options = {}) {
|
|
7074
|
+
const response = await (options.fetch ?? fetch)(url2, { headers: { accept: "application/json" } });
|
|
7075
|
+
if (!response.ok) throw new Error(`Fetching the node release from ${url2} failed: HTTP ${response.status}`);
|
|
7076
7076
|
let release;
|
|
7077
7077
|
try {
|
|
7078
7078
|
release = (await response.json())?.data;
|
|
7079
7079
|
} catch {
|
|
7080
|
-
throw new Error(`${
|
|
7080
|
+
throw new Error(`${url2} did not answer with JSON`);
|
|
7081
7081
|
}
|
|
7082
7082
|
if (typeof release?.version !== "string" || typeof release.publishedMrtds !== "string" || typeof release.bundle !== "string") {
|
|
7083
|
-
throw new Error(`${
|
|
7083
|
+
throw new Error(`${url2} did not answer with a signed node release`);
|
|
7084
7084
|
}
|
|
7085
7085
|
return { version: release.version, publishedMrtds: release.publishedMrtds, bundle: release.bundle };
|
|
7086
7086
|
}
|
|
@@ -7102,20 +7102,20 @@ function infoUrl(baseUrl) {
|
|
|
7102
7102
|
return `${baseUrl.replace(/\/+$/, "")}/admin-api/tee/info`;
|
|
7103
7103
|
}
|
|
7104
7104
|
async function readNodeReleaseVersion(baseUrl, givenFetch) {
|
|
7105
|
-
const
|
|
7106
|
-
const response = await givenFetch(
|
|
7105
|
+
const url2 = infoUrl(baseUrl);
|
|
7106
|
+
const response = await givenFetch(url2, { headers: { accept: "application/json" } });
|
|
7107
7107
|
if (response.status === 403) {
|
|
7108
7108
|
const code = await response.json().then((body) => body?.error?.code).catch(() => void 0);
|
|
7109
7109
|
if (code === "sealed_required") return void 0;
|
|
7110
7110
|
}
|
|
7111
|
-
if (!response.ok) throw new Error(`Fetching ${
|
|
7111
|
+
if (!response.ok) throw new Error(`Fetching ${url2} failed: HTTP ${response.status}`);
|
|
7112
7112
|
let osImage;
|
|
7113
7113
|
try {
|
|
7114
7114
|
osImage = (await response.json())?.data?.osImage;
|
|
7115
7115
|
} catch {
|
|
7116
|
-
throw new Error(`${
|
|
7116
|
+
throw new Error(`${url2} did not answer with JSON`);
|
|
7117
7117
|
}
|
|
7118
|
-
if (typeof osImage !== "string") throw new Error(`${
|
|
7118
|
+
if (typeof osImage !== "string") throw new Error(`${url2} did not name the node's image`);
|
|
7119
7119
|
return nodeReleaseVersionFromImage(osImage);
|
|
7120
7120
|
}
|
|
7121
7121
|
function cloudNodeReleaseUrl(cloudUrl, version) {
|
|
@@ -8725,8 +8725,8 @@ function createProofAuthorizer(credential) {
|
|
|
8725
8725
|
return { [REQUEST_PROOF_HEADER]: proof };
|
|
8726
8726
|
};
|
|
8727
8727
|
}
|
|
8728
|
-
function signedPath(
|
|
8729
|
-
return new URL(
|
|
8728
|
+
function signedPath(url2) {
|
|
8729
|
+
return new URL(url2).pathname;
|
|
8730
8730
|
}
|
|
8731
8731
|
|
|
8732
8732
|
// src/presence/statement.ts
|
|
@@ -8913,7 +8913,7 @@ var MeroClient = class {
|
|
|
8913
8913
|
const authorizeRequest = this.proofAuthorizer;
|
|
8914
8914
|
this.relayAdmin = createAdminApiClientFromHttpClient(
|
|
8915
8915
|
createHttpClient({
|
|
8916
|
-
fetch: (
|
|
8916
|
+
fetch: (url2, init) => fetchImpl ? fetchImpl(url2, init) : globalThis.fetch(url2, init),
|
|
8917
8917
|
baseUrl: relay.relayUrl,
|
|
8918
8918
|
authorizeRequest
|
|
8919
8919
|
}),
|
|
@@ -9556,9 +9556,9 @@ function writeRelayMap(account, map) {
|
|
|
9556
9556
|
}
|
|
9557
9557
|
function rememberRelay(account, relayUrl, at) {
|
|
9558
9558
|
const map = readRelayMap(account);
|
|
9559
|
-
const
|
|
9560
|
-
if (at.namespaceId) map.namespaces[at.namespaceId] =
|
|
9561
|
-
if (at.contextId) map.contexts[at.contextId] =
|
|
9559
|
+
const url2 = relayOrigin(relayUrl);
|
|
9560
|
+
if (at.namespaceId) map.namespaces[at.namespaceId] = url2;
|
|
9561
|
+
if (at.contextId) map.contexts[at.contextId] = url2;
|
|
9562
9562
|
writeRelayMap(account, map);
|
|
9563
9563
|
}
|
|
9564
9564
|
function relayForContext(account, contextId) {
|
|
@@ -9941,7 +9941,7 @@ function beginLinkState(account, returnTo) {
|
|
|
9941
9941
|
if (!ACCOUNT_HEX.test(account)) {
|
|
9942
9942
|
throw new Error("accountId must be 64 lowercase hex characters");
|
|
9943
9943
|
}
|
|
9944
|
-
const
|
|
9944
|
+
const url2 = new URL(returnTo);
|
|
9945
9945
|
const now = Date.now();
|
|
9946
9946
|
const pending = readPending(now);
|
|
9947
9947
|
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
@@ -9950,8 +9950,8 @@ function beginLinkState(account, returnTo) {
|
|
|
9950
9950
|
);
|
|
9951
9951
|
pending[state] = { account, expiresAt: now + LIFETIME_MS };
|
|
9952
9952
|
writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
|
|
9953
|
-
|
|
9954
|
-
return
|
|
9953
|
+
url2.searchParams.set(STATE_PARAM, state);
|
|
9954
|
+
return url2.toString();
|
|
9955
9955
|
}
|
|
9956
9956
|
function takeLinkState(search) {
|
|
9957
9957
|
const state = new URLSearchParams(search).get(STATE_PARAM);
|
|
@@ -9973,10 +9973,10 @@ function withoutLinkState(search) {
|
|
|
9973
9973
|
// src/cloud/link-redirect.ts
|
|
9974
9974
|
var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
|
|
9975
9975
|
function cloudLinkUrl(options) {
|
|
9976
|
-
const
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
return
|
|
9976
|
+
const url2 = new URL(options.cloudWebUrl ?? DEFAULT_CLOUD_WEB_URL);
|
|
9977
|
+
url2.searchParams.set("link-account", options.accountId);
|
|
9978
|
+
url2.searchParams.set("callback-url", beginLinkState(options.accountId, options.returnTo));
|
|
9979
|
+
return url2.toString();
|
|
9980
9980
|
}
|
|
9981
9981
|
function beginCloudLink(options) {
|
|
9982
9982
|
window.location.assign(cloudLinkUrl(options));
|
|
@@ -10032,12 +10032,12 @@ function deviceEnrolmentUrl(options) {
|
|
|
10032
10032
|
assertHex(options.kemPublicKey, "kemPublicKey");
|
|
10033
10033
|
assertSafeReturnTo(options.returnTo);
|
|
10034
10034
|
warnOnForeignCallback(options.returnTo);
|
|
10035
|
-
const
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
if (options.state)
|
|
10040
|
-
return
|
|
10035
|
+
const url2 = new URL(options.walletUrl);
|
|
10036
|
+
url2.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
|
|
10037
|
+
url2.searchParams.set(KEM_PARAM, options.kemPublicKey);
|
|
10038
|
+
url2.searchParams.set(CALLBACK_PARAM, options.returnTo);
|
|
10039
|
+
if (options.state) url2.searchParams.set(STATE_PARAM2, options.state);
|
|
10040
|
+
return url2.toString();
|
|
10041
10041
|
}
|
|
10042
10042
|
function beginDeviceEnrolment(options) {
|
|
10043
10043
|
window.location.assign(deviceEnrolmentUrl(options));
|
|
@@ -10122,17 +10122,17 @@ function warnOnForeignCallback(returnTo) {
|
|
|
10122
10122
|
);
|
|
10123
10123
|
}
|
|
10124
10124
|
function assertSafeReturnTo(returnTo) {
|
|
10125
|
-
let
|
|
10125
|
+
let url2;
|
|
10126
10126
|
try {
|
|
10127
|
-
|
|
10127
|
+
url2 = new URL(returnTo);
|
|
10128
10128
|
} catch {
|
|
10129
10129
|
throw new Error(`returnTo must be an absolute URL, got ${returnTo}`);
|
|
10130
10130
|
}
|
|
10131
|
-
const loopback =
|
|
10132
|
-
if (
|
|
10133
|
-
if (
|
|
10131
|
+
const loopback = url2.hostname === "localhost" || url2.hostname === "127.0.0.1" || url2.hostname === "[::1]" || url2.hostname === "::1";
|
|
10132
|
+
if (url2.protocol === "https:") return;
|
|
10133
|
+
if (url2.protocol === "http:" && loopback) return;
|
|
10134
10134
|
throw new Error(
|
|
10135
|
-
`returnTo must be https:, or http: on localhost \u2014 got ${
|
|
10135
|
+
`returnTo must be https:, or http: on localhost \u2014 got ${url2.protocol}//${url2.hostname}`
|
|
10136
10136
|
);
|
|
10137
10137
|
}
|
|
10138
10138
|
|
|
@@ -10419,8 +10419,8 @@ function joinNonce(account) {
|
|
|
10419
10419
|
const source = createLocalStorageNonceSource(`calimero.delegated.joinnonce.${account}`);
|
|
10420
10420
|
return () => source.next();
|
|
10421
10421
|
}
|
|
10422
|
-
function origin(
|
|
10423
|
-
return
|
|
10422
|
+
function origin(url2) {
|
|
10423
|
+
return url2.replace(/\/+$/, "");
|
|
10424
10424
|
}
|
|
10425
10425
|
async function joinWithNode(input) {
|
|
10426
10426
|
const { account, credential, deviceSecret } = input.credential;
|
|
@@ -10442,11 +10442,11 @@ async function joinWithNode(input) {
|
|
|
10442
10442
|
};
|
|
10443
10443
|
}
|
|
10444
10444
|
const body = { invitation: input.invitation, signedOp };
|
|
10445
|
-
const
|
|
10445
|
+
const url2 = input.admitUrl ?? `${origin(input.nodeUrl)}/admin-api/namespaces/${pathSegment(input.namespaceId)}/admit`;
|
|
10446
10446
|
const doFetch = input.deps?.fetch ?? fetch;
|
|
10447
10447
|
let response;
|
|
10448
10448
|
try {
|
|
10449
|
-
response = await doFetch(
|
|
10449
|
+
response = await doFetch(url2, {
|
|
10450
10450
|
method: "POST",
|
|
10451
10451
|
// No Authorization header and no request proof, deliberately: the route is
|
|
10452
10452
|
// unauthenticated because the joiner's signature is the authorization.
|
|
@@ -10569,8 +10569,8 @@ function selectLatestBundle(bundles) {
|
|
|
10569
10569
|
);
|
|
10570
10570
|
}
|
|
10571
10571
|
async function fetchRegistryBundles(registryUrl, pkg, fetchFn = globalThis.fetch) {
|
|
10572
|
-
const
|
|
10573
|
-
const response = await fetchFn(
|
|
10572
|
+
const url2 = `${registryUrl.replace(/\/+$/, "")}/api/v2/bundles?package=${encodeURIComponent(pkg)}`;
|
|
10573
|
+
const response = await fetchFn(url2, { headers: { Accept: "application/json" } });
|
|
10574
10574
|
if (!response.ok) throw new Error(`the registry has no ${pkg} (HTTP ${response.status})`);
|
|
10575
10575
|
const body = await response.json();
|
|
10576
10576
|
return Array.isArray(body) ? body : [];
|
|
@@ -10610,9 +10610,9 @@ function nextGovernanceNonce(relay, group) {
|
|
|
10610
10610
|
return next;
|
|
10611
10611
|
}
|
|
10612
10612
|
function relayFor(s, namespaceId) {
|
|
10613
|
-
const
|
|
10614
|
-
if (!
|
|
10615
|
-
return
|
|
10613
|
+
const url2 = readRelayMap(s.account).namespaces[namespaceId] ?? s.relayUrl;
|
|
10614
|
+
if (!url2) throw new Error("no relay is known for this namespace, so there is nowhere to send this");
|
|
10615
|
+
return url2.replace(/\/+$/, "");
|
|
10616
10616
|
}
|
|
10617
10617
|
function initArgsOf(params) {
|
|
10618
10618
|
if (!params || params.length === 0) return {};
|
|
@@ -10695,7 +10695,7 @@ async function foundingExecutor(s, relay, fetch2) {
|
|
|
10695
10695
|
const named = s.executorAccount && s.relayUrl?.replace(/\/+$/, "") === relay ? s.executorAccount : null;
|
|
10696
10696
|
if (named) return { executorAccount: named, executorKey: await attestedNodeKey(relay) };
|
|
10697
10697
|
const known = Object.entries(readRelayMap(s.account).namespaces).find(
|
|
10698
|
-
([,
|
|
10698
|
+
([, url2]) => url2.replace(/\/+$/, "") === relay
|
|
10699
10699
|
)?.[0];
|
|
10700
10700
|
if (!known) {
|
|
10701
10701
|
throw new Error(
|
|
@@ -11142,8 +11142,8 @@ function accountHexOrNull(value) {
|
|
|
11142
11142
|
return typeof value === "string" && ACCOUNT_HEX.test(value) ? value : null;
|
|
11143
11143
|
}
|
|
11144
11144
|
var AccountHaRefusedError = class extends HTTPError {
|
|
11145
|
-
constructor(code, status, statusText,
|
|
11146
|
-
super(status, statusText,
|
|
11145
|
+
constructor(code, status, statusText, url2, headers, bodyText) {
|
|
11146
|
+
super(status, statusText, url2, headers, bodyText);
|
|
11147
11147
|
this.code = code;
|
|
11148
11148
|
this.name = "AccountHaRefusedError";
|
|
11149
11149
|
}
|
|
@@ -12460,8 +12460,8 @@ var MeroJs = class {
|
|
|
12460
12460
|
/**
|
|
12461
12461
|
* Parse an auth callback URL hash fragment (static utility)
|
|
12462
12462
|
*/
|
|
12463
|
-
static parseAuthCallback(
|
|
12464
|
-
return parseAuthCallback(
|
|
12463
|
+
static parseAuthCallback(url2) {
|
|
12464
|
+
return parseAuthCallback(url2);
|
|
12465
12465
|
}
|
|
12466
12466
|
/**
|
|
12467
12467
|
* Build an auth login URL (static utility)
|
|
@@ -12726,8 +12726,8 @@ async function probeNodeHealth(baseUrl, options = {}) {
|
|
|
12726
12726
|
if (abortSignal) abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
12727
12727
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12728
12728
|
try {
|
|
12729
|
-
const
|
|
12730
|
-
const res = await fetch(
|
|
12729
|
+
const url2 = nodeEndpoint(baseUrl, "admin-api/health");
|
|
12730
|
+
const res = await fetch(url2, {
|
|
12731
12731
|
method: "GET",
|
|
12732
12732
|
signal: controller.signal
|
|
12733
12733
|
});
|
|
@@ -12751,20 +12751,457 @@ async function discoverLocalNodes(options = {}) {
|
|
|
12751
12751
|
const { ports = DEFAULT_LOCAL_NODE_PORTS, timeoutMs, signal } = options;
|
|
12752
12752
|
const results = await Promise.all(
|
|
12753
12753
|
ports.map(async (port) => {
|
|
12754
|
-
const
|
|
12755
|
-
const ok = await probeNodeHealth(
|
|
12756
|
-
return ok ?
|
|
12754
|
+
const url2 = localNodeUrl(port);
|
|
12755
|
+
const ok = await probeNodeHealth(url2, { timeoutMs, signal });
|
|
12756
|
+
return ok ? url2 : null;
|
|
12757
12757
|
})
|
|
12758
12758
|
);
|
|
12759
|
-
return results.filter((
|
|
12759
|
+
return results.filter((url2) => url2 !== null);
|
|
12760
|
+
}
|
|
12761
|
+
|
|
12762
|
+
// src/agent-gate/chacha20poly1305.ts
|
|
12763
|
+
var KEY_LEN = 32;
|
|
12764
|
+
var NONCE_LEN = 12;
|
|
12765
|
+
var TAG_LEN2 = 16;
|
|
12766
|
+
function rotl(value, shift) {
|
|
12767
|
+
return (value << shift | value >>> 32 - shift) >>> 0;
|
|
12768
|
+
}
|
|
12769
|
+
function quarterRound(s, a, b, c, d) {
|
|
12770
|
+
s[a] = s[a] + s[b] >>> 0;
|
|
12771
|
+
s[d] = rotl(s[d] ^ s[a], 16);
|
|
12772
|
+
s[c] = s[c] + s[d] >>> 0;
|
|
12773
|
+
s[b] = rotl(s[b] ^ s[c], 12);
|
|
12774
|
+
s[a] = s[a] + s[b] >>> 0;
|
|
12775
|
+
s[d] = rotl(s[d] ^ s[a], 8);
|
|
12776
|
+
s[c] = s[c] + s[d] >>> 0;
|
|
12777
|
+
s[b] = rotl(s[b] ^ s[c], 7);
|
|
12778
|
+
}
|
|
12779
|
+
function u32le2(bytes, offset) {
|
|
12780
|
+
return (bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16 | bytes[offset + 3] << 24) >>> 0;
|
|
12781
|
+
}
|
|
12782
|
+
function chacha20Block(key, counter, nonce2) {
|
|
12783
|
+
const state = new Uint32Array(16);
|
|
12784
|
+
state[0] = 1634760805;
|
|
12785
|
+
state[1] = 857760878;
|
|
12786
|
+
state[2] = 2036477234;
|
|
12787
|
+
state[3] = 1797285236;
|
|
12788
|
+
for (let i = 0; i < 8; i += 1) state[4 + i] = u32le2(key, i * 4);
|
|
12789
|
+
state[12] = counter >>> 0;
|
|
12790
|
+
for (let i = 0; i < 3; i += 1) state[13 + i] = u32le2(nonce2, i * 4);
|
|
12791
|
+
const working = state.slice();
|
|
12792
|
+
for (let round = 0; round < 10; round += 1) {
|
|
12793
|
+
quarterRound(working, 0, 4, 8, 12);
|
|
12794
|
+
quarterRound(working, 1, 5, 9, 13);
|
|
12795
|
+
quarterRound(working, 2, 6, 10, 14);
|
|
12796
|
+
quarterRound(working, 3, 7, 11, 15);
|
|
12797
|
+
quarterRound(working, 0, 5, 10, 15);
|
|
12798
|
+
quarterRound(working, 1, 6, 11, 12);
|
|
12799
|
+
quarterRound(working, 2, 7, 8, 13);
|
|
12800
|
+
quarterRound(working, 3, 4, 9, 14);
|
|
12801
|
+
}
|
|
12802
|
+
const out = new Uint8Array(64);
|
|
12803
|
+
for (let i = 0; i < 16; i += 1) {
|
|
12804
|
+
const word = working[i] + state[i] >>> 0;
|
|
12805
|
+
out[i * 4] = word & 255;
|
|
12806
|
+
out[i * 4 + 1] = word >>> 8 & 255;
|
|
12807
|
+
out[i * 4 + 2] = word >>> 16 & 255;
|
|
12808
|
+
out[i * 4 + 3] = word >>> 24 & 255;
|
|
12809
|
+
}
|
|
12810
|
+
return out;
|
|
12811
|
+
}
|
|
12812
|
+
function chacha20(key, counter, nonce2, data) {
|
|
12813
|
+
const out = new Uint8Array(data.length);
|
|
12814
|
+
for (let offset = 0, block = counter; offset < data.length; offset += 64, block += 1) {
|
|
12815
|
+
const stream = chacha20Block(key, block, nonce2);
|
|
12816
|
+
const end = Math.min(offset + 64, data.length);
|
|
12817
|
+
for (let i = offset; i < end; i += 1) out[i] = data[i] ^ stream[i - offset];
|
|
12818
|
+
}
|
|
12819
|
+
return out;
|
|
12820
|
+
}
|
|
12821
|
+
function leToBigInt(bytes) {
|
|
12822
|
+
let value = 0n;
|
|
12823
|
+
for (let i = bytes.length - 1; i >= 0; i -= 1) value = value << 8n | BigInt(bytes[i]);
|
|
12824
|
+
return value;
|
|
12825
|
+
}
|
|
12826
|
+
var P1305 = (1n << 130n) - 5n;
|
|
12827
|
+
function poly1305(key, message) {
|
|
12828
|
+
const r = leToBigInt(key.subarray(0, 16)) & 0x0ffffffc0ffffffc0ffffffc0fffffffn;
|
|
12829
|
+
const s = leToBigInt(key.subarray(16, 32));
|
|
12830
|
+
let acc = 0n;
|
|
12831
|
+
for (let offset = 0; offset < message.length; offset += 16) {
|
|
12832
|
+
const block = message.subarray(offset, Math.min(offset + 16, message.length));
|
|
12833
|
+
const n = leToBigInt(block) + (1n << BigInt(8 * block.length));
|
|
12834
|
+
acc = (acc + n) * r % P1305;
|
|
12835
|
+
}
|
|
12836
|
+
let tag3 = acc + s & (1n << 128n) - 1n;
|
|
12837
|
+
const out = new Uint8Array(TAG_LEN2);
|
|
12838
|
+
for (let i = 0; i < TAG_LEN2; i += 1) {
|
|
12839
|
+
out[i] = Number(tag3 & 0xffn);
|
|
12840
|
+
tag3 >>= 8n;
|
|
12841
|
+
}
|
|
12842
|
+
return out;
|
|
12843
|
+
}
|
|
12844
|
+
function pad16(length) {
|
|
12845
|
+
return new Uint8Array((16 - length % 16) % 16);
|
|
12846
|
+
}
|
|
12847
|
+
function u64le2(value) {
|
|
12848
|
+
const out = new Uint8Array(8);
|
|
12849
|
+
new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
|
|
12850
|
+
return out;
|
|
12851
|
+
}
|
|
12852
|
+
function macData(aad, ciphertext) {
|
|
12853
|
+
const parts = [aad, pad16(aad.length), ciphertext, pad16(ciphertext.length), u64le2(aad.length), u64le2(ciphertext.length)];
|
|
12854
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
12855
|
+
let offset = 0;
|
|
12856
|
+
for (const part of parts) {
|
|
12857
|
+
out.set(part, offset);
|
|
12858
|
+
offset += part.length;
|
|
12859
|
+
}
|
|
12860
|
+
return out;
|
|
12861
|
+
}
|
|
12862
|
+
function check(key, nonce2) {
|
|
12863
|
+
if (key.length !== KEY_LEN) throw new Error("ChaCha20-Poly1305 needs a 32-byte key");
|
|
12864
|
+
if (nonce2.length !== NONCE_LEN) throw new Error("ChaCha20-Poly1305 needs a 12-byte nonce");
|
|
12865
|
+
}
|
|
12866
|
+
function chacha20Poly1305Seal(key, nonce2, plaintext, aad = new Uint8Array(0)) {
|
|
12867
|
+
check(key, nonce2);
|
|
12868
|
+
const otk = chacha20Block(key, 0, nonce2).subarray(0, 32);
|
|
12869
|
+
const ciphertext = chacha20(key, 1, nonce2, plaintext);
|
|
12870
|
+
const tag3 = poly1305(otk, macData(aad, ciphertext));
|
|
12871
|
+
const out = new Uint8Array(ciphertext.length + TAG_LEN2);
|
|
12872
|
+
out.set(ciphertext);
|
|
12873
|
+
out.set(tag3, ciphertext.length);
|
|
12874
|
+
return out;
|
|
12875
|
+
}
|
|
12876
|
+
|
|
12877
|
+
// src/agent-gate/hpke.ts
|
|
12878
|
+
var KEM_ID = 32;
|
|
12879
|
+
var KDF_ID = 1;
|
|
12880
|
+
var AEAD_ID = 3;
|
|
12881
|
+
var MODE_AUTH = 2;
|
|
12882
|
+
var N_SECRET = 32;
|
|
12883
|
+
var N_KEY = 32;
|
|
12884
|
+
var N_NONCE = 12;
|
|
12885
|
+
var utf84 = (s) => new TextEncoder().encode(s);
|
|
12886
|
+
var i2osp2 = (n) => new Uint8Array([n >> 8 & 255, n & 255]);
|
|
12887
|
+
var KEM_SUITE = concat(utf84("KEM"), i2osp2(KEM_ID));
|
|
12888
|
+
var HPKE_SUITE = concat(utf84("HPKE"), i2osp2(KEM_ID), i2osp2(KDF_ID), i2osp2(AEAD_ID));
|
|
12889
|
+
async function hmac(key, data) {
|
|
12890
|
+
const raw = key.length === 0 ? new Uint8Array(32) : key;
|
|
12891
|
+
const k = await crypto.subtle.importKey("raw", raw, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
12892
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", k, data));
|
|
12893
|
+
}
|
|
12894
|
+
async function expand(prk, info, length) {
|
|
12895
|
+
const out = new Uint8Array(length);
|
|
12896
|
+
let previous = new Uint8Array(0);
|
|
12897
|
+
for (let i = 1, offset = 0; offset < length; i += 1) {
|
|
12898
|
+
previous = await hmac(prk, concat(previous, info, new Uint8Array([i])));
|
|
12899
|
+
out.set(previous.subarray(0, Math.min(previous.length, length - offset)), offset);
|
|
12900
|
+
offset += previous.length;
|
|
12901
|
+
}
|
|
12902
|
+
return out;
|
|
12903
|
+
}
|
|
12904
|
+
function labeledExtract(suite, salt, label2, ikm) {
|
|
12905
|
+
return hmac(salt, concat(utf84("HPKE-v1"), suite, utf84(label2), ikm));
|
|
12906
|
+
}
|
|
12907
|
+
function labeledExpand(suite, prk, label2, info, length) {
|
|
12908
|
+
return expand(prk, concat(i2osp2(length), utf84("HPKE-v1"), suite, utf84(label2), info), length);
|
|
12909
|
+
}
|
|
12910
|
+
async function extractAndExpand(dhValue, kemContext) {
|
|
12911
|
+
const prk = await labeledExtract(KEM_SUITE, new Uint8Array(0), "eae_prk", dhValue);
|
|
12912
|
+
return labeledExpand(KEM_SUITE, prk, "shared_secret", kemContext, N_SECRET);
|
|
12913
|
+
}
|
|
12914
|
+
async function keySchedule(sharedSecret, info) {
|
|
12915
|
+
const empty = new Uint8Array(0);
|
|
12916
|
+
const pskIdHash = await labeledExtract(HPKE_SUITE, empty, "psk_id_hash", empty);
|
|
12917
|
+
const infoHash = await labeledExtract(HPKE_SUITE, empty, "info_hash", info);
|
|
12918
|
+
const context = concat(new Uint8Array([MODE_AUTH]), pskIdHash, infoHash);
|
|
12919
|
+
const secret = await labeledExtract(HPKE_SUITE, sharedSecret, "secret", empty);
|
|
12920
|
+
return {
|
|
12921
|
+
key: await labeledExpand(HPKE_SUITE, secret, "key", context, N_KEY),
|
|
12922
|
+
nonce: await labeledExpand(HPKE_SUITE, secret, "base_nonce", context, N_NONCE)
|
|
12923
|
+
};
|
|
12924
|
+
}
|
|
12925
|
+
async function hpkeSealAuth(recipientPublic, senderSecret, info, plaintext, aad = new Uint8Array(0), ephemeralSecret) {
|
|
12926
|
+
if (recipientPublic.length !== 32) throw new Error("The recipient key is not 32 bytes of X25519");
|
|
12927
|
+
const ephemeral = await x25519KeyPair(ephemeralSecret);
|
|
12928
|
+
const sender = await x25519KeyPair(senderSecret);
|
|
12929
|
+
const dhValue = concat(await dh(ephemeral.privateKey, recipientPublic), await dh(sender.privateKey, recipientPublic));
|
|
12930
|
+
const shared = await extractAndExpand(dhValue, concat(ephemeral.publicKey, recipientPublic, sender.publicKey));
|
|
12931
|
+
const { key, nonce: nonce2 } = await keySchedule(shared, info);
|
|
12932
|
+
return { enc: ephemeral.publicKey, ciphertext: chacha20Poly1305Seal(key, nonce2, plaintext, aad) };
|
|
12933
|
+
}
|
|
12934
|
+
|
|
12935
|
+
// src/agent-gate/gate.ts
|
|
12936
|
+
var AGENT_ATTEST_DOMAIN = "mero-agent-gate/attest/v3";
|
|
12937
|
+
var AGENT_PROVISION_INFO_DOMAIN = "mero-agent-gate/provision/v2";
|
|
12938
|
+
var AGENT_ID = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
12939
|
+
var SECRET_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
|
|
12940
|
+
var MAX_SECRETS = 64;
|
|
12941
|
+
var MAX_SECRET_LEN = 64 * 1024;
|
|
12942
|
+
var AgentGateError = class extends Error {
|
|
12943
|
+
constructor(message, status, code) {
|
|
12944
|
+
super(message);
|
|
12945
|
+
this.status = status;
|
|
12946
|
+
this.code = code;
|
|
12947
|
+
this.name = "AgentGateError";
|
|
12948
|
+
}
|
|
12949
|
+
};
|
|
12950
|
+
function validateAgentId(agentId) {
|
|
12951
|
+
if (!AGENT_ID.test(agentId)) {
|
|
12952
|
+
throw new Error(`Agent id ${JSON.stringify(agentId)} must match [a-z0-9][a-z0-9-]{0,62}`);
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
function utf85(value) {
|
|
12956
|
+
return new TextEncoder().encode(value);
|
|
12957
|
+
}
|
|
12958
|
+
function lengthPrefixed(agentId) {
|
|
12959
|
+
const id = utf85(agentId);
|
|
12960
|
+
return concat(new Uint8Array([id.length]), id);
|
|
12961
|
+
}
|
|
12962
|
+
function toBase64(bytes) {
|
|
12963
|
+
let binary = "";
|
|
12964
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
12965
|
+
return btoa(binary);
|
|
12966
|
+
}
|
|
12967
|
+
function fromBase644(value, label2, length) {
|
|
12968
|
+
let bytes;
|
|
12969
|
+
try {
|
|
12970
|
+
bytes = Uint8Array.from(atob(value.trim()), (c) => c.charCodeAt(0));
|
|
12971
|
+
} catch {
|
|
12972
|
+
throw new Error(`${label2} is not base64`);
|
|
12973
|
+
}
|
|
12974
|
+
if (length !== void 0 && bytes.length !== length) throw new Error(`${label2} is not ${length} bytes`);
|
|
12975
|
+
return bytes;
|
|
12976
|
+
}
|
|
12977
|
+
async function agentKeyBinding(provisioningPublic, agentId, signingPublic, ownerPublic) {
|
|
12978
|
+
const zero = new Uint8Array(32);
|
|
12979
|
+
const data = concat(
|
|
12980
|
+
utf85(AGENT_ATTEST_DOMAIN),
|
|
12981
|
+
provisioningPublic,
|
|
12982
|
+
lengthPrefixed(agentId),
|
|
12983
|
+
signingPublic ?? zero,
|
|
12984
|
+
ownerPublic ?? zero
|
|
12985
|
+
);
|
|
12986
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", data));
|
|
12987
|
+
}
|
|
12988
|
+
function provisionInfo(recipientPublic, agentId) {
|
|
12989
|
+
return concat(utf85(AGENT_PROVISION_INFO_DOMAIN), recipientPublic, lengthPrefixed(agentId));
|
|
12990
|
+
}
|
|
12991
|
+
async function generateOwnerKey() {
|
|
12992
|
+
const secret = crypto.getRandomValues(new Uint8Array(32));
|
|
12993
|
+
return { secret, publicKey: await ownerPublicKey(secret) };
|
|
12994
|
+
}
|
|
12995
|
+
async function ownerPublicKey(secret) {
|
|
12996
|
+
if (secret.length !== 32) throw new Error("An owner key is 32 bytes");
|
|
12997
|
+
return toBase64((await x25519KeyPair(secret)).publicKey);
|
|
12998
|
+
}
|
|
12999
|
+
function url(gateUrl, path) {
|
|
13000
|
+
return `${gateUrl.replace(/\/+$/, "")}${path}`;
|
|
13001
|
+
}
|
|
13002
|
+
async function postJson(fetchImpl, target, body) {
|
|
13003
|
+
const response = await fetchImpl(target, {
|
|
13004
|
+
method: "POST",
|
|
13005
|
+
headers: { "content-type": "application/json" },
|
|
13006
|
+
body: JSON.stringify(body)
|
|
13007
|
+
});
|
|
13008
|
+
const text = await response.text();
|
|
13009
|
+
let parsed = void 0;
|
|
13010
|
+
try {
|
|
13011
|
+
parsed = text ? JSON.parse(text) : void 0;
|
|
13012
|
+
} catch {
|
|
13013
|
+
}
|
|
13014
|
+
if (!response.ok) {
|
|
13015
|
+
const error = parsed;
|
|
13016
|
+
throw new AgentGateError(
|
|
13017
|
+
`The gate answered ${response.status}${error?.error ? ` ${error.error}` : ""}: ${error?.details ?? text.slice(0, 200)}`,
|
|
13018
|
+
response.status,
|
|
13019
|
+
error?.error
|
|
13020
|
+
);
|
|
13021
|
+
}
|
|
13022
|
+
return parsed;
|
|
13023
|
+
}
|
|
13024
|
+
async function attestAgent(options) {
|
|
13025
|
+
const agentId = options.agentId ?? "";
|
|
13026
|
+
if (agentId) validateAgentId(agentId);
|
|
13027
|
+
const nonce2 = crypto.getRandomValues(new Uint8Array(32));
|
|
13028
|
+
const body = await postJson(options.fetch ?? fetch, url(options.gateUrl, "/attest"), {
|
|
13029
|
+
nonceB64: toBase64(nonce2),
|
|
13030
|
+
...agentId ? { agentId } : {}
|
|
13031
|
+
});
|
|
13032
|
+
if ((body?.agentId ?? "") !== agentId) {
|
|
13033
|
+
throw new Error(`Asked about agent ${JSON.stringify(agentId)}, the gate answered for ${JSON.stringify(body?.agentId)}`);
|
|
13034
|
+
}
|
|
13035
|
+
const provisioning = fromBase644(body.provisioningPublicKeyB64 ?? "", "provisioningPublicKeyB64", 32);
|
|
13036
|
+
const signing = body.signingPublicKeyB64 ? fromBase644(body.signingPublicKeyB64, "signingPublicKeyB64", 32) : void 0;
|
|
13037
|
+
const owner = body.ownerPublicKeyB64 ? fromBase644(body.ownerPublicKeyB64, "ownerPublicKeyB64", 32) : void 0;
|
|
13038
|
+
if (!signing !== !owner) {
|
|
13039
|
+
throw new Error("A claimed agent has both a signing key and an owner; the gate named one");
|
|
13040
|
+
}
|
|
13041
|
+
const binding = await agentKeyBinding(provisioning, agentId, signing, owner);
|
|
13042
|
+
await options.verifier({ quoteB64: body.quoteB64, nonce: hex(nonce2), reportDataSuffix: hex(binding) });
|
|
13043
|
+
return {
|
|
13044
|
+
agentId,
|
|
13045
|
+
provisioningPublicKey: toBase64(provisioning),
|
|
13046
|
+
...signing ? { signingPublicKey: toBase64(signing) } : {},
|
|
13047
|
+
...owner ? { ownerPublicKey: toBase64(owner) } : {}
|
|
13048
|
+
};
|
|
13049
|
+
}
|
|
13050
|
+
async function checkOwner(agent, ownerSecret) {
|
|
13051
|
+
const mine = await ownerPublicKey(ownerSecret);
|
|
13052
|
+
if (agent.ownerPublicKey && agent.ownerPublicKey !== mine) {
|
|
13053
|
+
throw new AgentGateError(
|
|
13054
|
+
`This agent is claimed by another key (${agent.ownerPublicKey}); it is not yours. Send it nothing and do not authorize its signing key`,
|
|
13055
|
+
void 0,
|
|
13056
|
+
"not_yours"
|
|
13057
|
+
);
|
|
13058
|
+
}
|
|
13059
|
+
return mine;
|
|
13060
|
+
}
|
|
13061
|
+
function validateSecrets(secrets) {
|
|
13062
|
+
const names = Object.keys(secrets);
|
|
13063
|
+
if (names.length === 0) throw new Error("There are no secrets to provision");
|
|
13064
|
+
if (names.length > MAX_SECRETS) throw new Error(`At most ${MAX_SECRETS} secrets at once`);
|
|
13065
|
+
for (const name of names) {
|
|
13066
|
+
if (!SECRET_NAME.test(name)) throw new Error(`Secret name ${JSON.stringify(name)} must match [A-Za-z0-9][A-Za-z0-9_.-]{0,63}`);
|
|
13067
|
+
if (typeof secrets[name] !== "string") throw new Error(`Secret ${name} is not a string`);
|
|
13068
|
+
if (utf85(secrets[name]).length > MAX_SECRET_LEN) throw new Error(`Secret ${name} is over ${MAX_SECRET_LEN} bytes`);
|
|
13069
|
+
}
|
|
13070
|
+
}
|
|
13071
|
+
async function sendSealed(fetchImpl, gateUrl, path, agent, ownerSecret, message) {
|
|
13072
|
+
if (!agent.agentId) throw new Error("This quote names no agent; attest with the agent id");
|
|
13073
|
+
const mine = await checkOwner(agent, ownerSecret);
|
|
13074
|
+
const recipient = fromBase644(agent.provisioningPublicKey, "provisioningPublicKey", 32);
|
|
13075
|
+
const sealed = await hpkeSealAuth(
|
|
13076
|
+
recipient,
|
|
13077
|
+
ownerSecret,
|
|
13078
|
+
provisionInfo(recipient, agent.agentId),
|
|
13079
|
+
utf85(JSON.stringify(message))
|
|
13080
|
+
);
|
|
13081
|
+
return postJson(fetchImpl, url(gateUrl, path), {
|
|
13082
|
+
agentId: agent.agentId,
|
|
13083
|
+
provisioningPublicKeyB64: agent.provisioningPublicKey,
|
|
13084
|
+
senderPublicKeyB64: mine,
|
|
13085
|
+
encappedKeyB64: toBase64(sealed.enc),
|
|
13086
|
+
ciphertextB64: toBase64(sealed.ciphertext)
|
|
13087
|
+
});
|
|
13088
|
+
}
|
|
13089
|
+
async function provisionAgent(options) {
|
|
13090
|
+
validateSecrets(options.secrets);
|
|
13091
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
13092
|
+
const answer = await sendSealed(fetchImpl, options.gateUrl, "/provision", options.agent, options.ownerSecret, {
|
|
13093
|
+
secrets: options.secrets
|
|
13094
|
+
});
|
|
13095
|
+
const after = await attestAgent({ gateUrl: options.gateUrl, agentId: options.agent.agentId, verifier: options.verifier, fetch: fetchImpl });
|
|
13096
|
+
const mine = await ownerPublicKey(options.ownerSecret);
|
|
13097
|
+
if (after.ownerPublicKey !== mine || !after.signingPublicKey) {
|
|
13098
|
+
throw new AgentGateError("The gate accepted the secrets, but its quote does not name your key as owner");
|
|
13099
|
+
}
|
|
13100
|
+
if (options.agent.signingPublicKey && options.agent.signingPublicKey !== after.signingPublicKey) {
|
|
13101
|
+
throw new AgentGateError("The agent's signing key changed while provisioning");
|
|
13102
|
+
}
|
|
13103
|
+
return {
|
|
13104
|
+
agentId: after.agentId,
|
|
13105
|
+
provisioningPublicKey: after.provisioningPublicKey,
|
|
13106
|
+
signingPublicKey: after.signingPublicKey,
|
|
13107
|
+
ownerPublicKey: mine,
|
|
13108
|
+
written: answer?.written ?? [],
|
|
13109
|
+
claimed: answer?.claimed === true
|
|
13110
|
+
};
|
|
13111
|
+
}
|
|
13112
|
+
async function releaseAgent(options) {
|
|
13113
|
+
if (!options.agent.ownerPublicKey) throw new Error("This agent is not claimed; there is nothing to release");
|
|
13114
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
13115
|
+
await sendSealed(fetchImpl, options.gateUrl, "/release", options.agent, options.ownerSecret, { release: true });
|
|
13116
|
+
const after = await attestAgent({ gateUrl: options.gateUrl, agentId: options.agent.agentId, verifier: options.verifier, fetch: fetchImpl });
|
|
13117
|
+
if (after.ownerPublicKey) {
|
|
13118
|
+
throw new AgentGateError("The gate says it released the agent, but its quote still names an owner");
|
|
13119
|
+
}
|
|
13120
|
+
}
|
|
13121
|
+
|
|
13122
|
+
// src/agent-gate/policy.ts
|
|
13123
|
+
var KMS_RELEASE_SIGNER = Object.freeze({
|
|
13124
|
+
identity: "https://github.com/calimero-network/mero-tee/.github/workflows/release-kms.yaml@refs/heads/master",
|
|
13125
|
+
issuer: "https://token.actions.githubusercontent.com"
|
|
13126
|
+
});
|
|
13127
|
+
var LISTS = [
|
|
13128
|
+
"allowed_tcb_statuses",
|
|
13129
|
+
"allowed_mrtd",
|
|
13130
|
+
"allowed_rtmr0",
|
|
13131
|
+
"allowed_rtmr1",
|
|
13132
|
+
"allowed_rtmr2",
|
|
13133
|
+
"allowed_rtmr3"
|
|
13134
|
+
];
|
|
13135
|
+
async function verifySignedAgentPolicy(policy, bundle, options = {}) {
|
|
13136
|
+
const body = typeof policy === "string" ? new TextEncoder().encode(policy) : policy;
|
|
13137
|
+
await verifyCosignBundle(body, bundle, {
|
|
13138
|
+
identity: options.identity ?? KMS_RELEASE_SIGNER.identity,
|
|
13139
|
+
issuer: options.issuer ?? KMS_RELEASE_SIGNER.issuer
|
|
13140
|
+
});
|
|
13141
|
+
let parsed;
|
|
13142
|
+
try {
|
|
13143
|
+
parsed = JSON.parse(new TextDecoder().decode(body));
|
|
13144
|
+
} catch {
|
|
13145
|
+
throw new Error("The signed agent policy is not JSON");
|
|
13146
|
+
}
|
|
13147
|
+
if (parsed?.role !== "agent") {
|
|
13148
|
+
throw new Error(`The signed file is a ${JSON.stringify(parsed?.role)} policy, not an agent one`);
|
|
13149
|
+
}
|
|
13150
|
+
const profile = options.profile ?? PRODUCTION_PROFILE;
|
|
13151
|
+
if (parsed.profile !== profile) {
|
|
13152
|
+
throw new Error(`The signed agent policy is for the ${JSON.stringify(parsed.profile)} profile, not ${profile}`);
|
|
13153
|
+
}
|
|
13154
|
+
if (options.expectedVersion !== void 0 && parsed.version !== options.expectedVersion) {
|
|
13155
|
+
throw new Error(`The signed agent policy is release ${JSON.stringify(parsed.version)}, not ${options.expectedVersion}`);
|
|
13156
|
+
}
|
|
13157
|
+
checkLists(parsed);
|
|
13158
|
+
return parsed;
|
|
13159
|
+
}
|
|
13160
|
+
function checkLists(policy) {
|
|
13161
|
+
for (const list of LISTS) {
|
|
13162
|
+
if (!Array.isArray(policy?.[list]) || policy[list].length === 0) {
|
|
13163
|
+
throw new Error(`The agent policy's ${list} is empty, so no agent would be accepted`);
|
|
13164
|
+
}
|
|
13165
|
+
}
|
|
13166
|
+
}
|
|
13167
|
+
var TCB_STATUSES2 = [
|
|
13168
|
+
"UpToDate",
|
|
13169
|
+
"SWHardeningNeeded",
|
|
13170
|
+
"ConfigurationNeeded",
|
|
13171
|
+
"ConfigurationAndSWHardeningNeeded",
|
|
13172
|
+
"OutOfDate",
|
|
13173
|
+
"OutOfDateConfigurationNeeded"
|
|
13174
|
+
];
|
|
13175
|
+
function tcbStatus2(value) {
|
|
13176
|
+
const found = TCB_STATUSES2.find((status) => status.toLowerCase() === value.trim().toLowerCase());
|
|
13177
|
+
if (!found) throw new Error(`The agent policy allows an unknown TCB status ${JSON.stringify(value)}`);
|
|
13178
|
+
return found;
|
|
13179
|
+
}
|
|
13180
|
+
var register = (value) => value.trim().replace(/^0x/i, "");
|
|
13181
|
+
function agentQuoteVerifier(policy, options) {
|
|
13182
|
+
checkLists(policy);
|
|
13183
|
+
return createQuoteVerifier({
|
|
13184
|
+
dcapVerify: options.dcapVerify,
|
|
13185
|
+
fetchCollateral: options.fetchCollateral,
|
|
13186
|
+
now: options.now,
|
|
13187
|
+
allowedTcbStatuses: policy.allowed_tcb_statuses.map(tcbStatus2),
|
|
13188
|
+
allowedMrtd: policy.allowed_mrtd.map(register),
|
|
13189
|
+
allowedRtmr0: policy.allowed_rtmr0.map(register),
|
|
13190
|
+
allowedRtmr1: policy.allowed_rtmr1.map(register),
|
|
13191
|
+
allowedRtmr2: policy.allowed_rtmr2.map(register),
|
|
13192
|
+
allowedRtmr3: policy.allowed_rtmr3.map(register)
|
|
13193
|
+
});
|
|
12760
13194
|
}
|
|
12761
13195
|
export {
|
|
12762
13196
|
ACCOUNT_HA_AUDIENCE,
|
|
12763
13197
|
ACCOUNT_OWNERSHIP_AUDIENCE,
|
|
13198
|
+
AGENT_ATTEST_DOMAIN,
|
|
13199
|
+
AGENT_PROVISION_INFO_DOMAIN,
|
|
12764
13200
|
AccountHaRefusedError,
|
|
12765
13201
|
AccountLinkedToSeveralUsersError,
|
|
12766
13202
|
AccountNotLinkedError,
|
|
12767
13203
|
AdminApiClient,
|
|
13204
|
+
AgentGateError,
|
|
12768
13205
|
AuthApiClient,
|
|
12769
13206
|
AuthRevokedError,
|
|
12770
13207
|
CAPABILITIES,
|
|
@@ -12781,6 +13218,7 @@ export {
|
|
|
12781
13218
|
INVITED_ROLE,
|
|
12782
13219
|
IntentRefusedError,
|
|
12783
13220
|
InvitationNotClaimableError,
|
|
13221
|
+
KMS_RELEASE_SIGNER,
|
|
12784
13222
|
LocalStorageTokenStore,
|
|
12785
13223
|
MAX_INVITATION_VALIDITY_SECS,
|
|
12786
13224
|
MemoryTokenStore,
|
|
@@ -12819,8 +13257,11 @@ export {
|
|
|
12819
13257
|
accountRootSignerFromPhrase,
|
|
12820
13258
|
adminChangedOp,
|
|
12821
13259
|
admitterAddrsFromNetworkStatus,
|
|
13260
|
+
agentKeyBinding,
|
|
13261
|
+
agentQuoteVerifier,
|
|
12822
13262
|
applicationIdForBundle,
|
|
12823
13263
|
attemptRelayNodeKey,
|
|
13264
|
+
attestAgent,
|
|
12824
13265
|
attestKeyBinding,
|
|
12825
13266
|
attestRelayNodeKey,
|
|
12826
13267
|
authorNonceLookup,
|
|
@@ -12834,6 +13275,7 @@ export {
|
|
|
12834
13275
|
capMask,
|
|
12835
13276
|
capNames,
|
|
12836
13277
|
carryExecutorAccount,
|
|
13278
|
+
checkOwner,
|
|
12837
13279
|
classifyError,
|
|
12838
13280
|
clearDelegatedCredential,
|
|
12839
13281
|
clearDelegatedSession,
|
|
@@ -12901,6 +13343,7 @@ export {
|
|
|
12901
13343
|
foundDelegatedNamespace,
|
|
12902
13344
|
foundedNamespaceId,
|
|
12903
13345
|
generateAccountRoot,
|
|
13346
|
+
generateOwnerKey,
|
|
12904
13347
|
generateSessionKey,
|
|
12905
13348
|
governGroup,
|
|
12906
13349
|
governRoot,
|
|
@@ -12941,6 +13384,7 @@ export {
|
|
|
12941
13384
|
normaliseAccount,
|
|
12942
13385
|
ownerOpDigest,
|
|
12943
13386
|
ownerOpSigningPayload,
|
|
13387
|
+
ownerPublicKey,
|
|
12944
13388
|
parseAuthCallback,
|
|
12945
13389
|
parseCreationWarrant,
|
|
12946
13390
|
parseDeviceCredential,
|
|
@@ -12950,6 +13394,8 @@ export {
|
|
|
12950
13394
|
pinRelayNodeKey,
|
|
12951
13395
|
presenceStatementBytes,
|
|
12952
13396
|
probeNodeHealth,
|
|
13397
|
+
provisionAgent,
|
|
13398
|
+
provisionInfo,
|
|
12953
13399
|
readCloudLinkCallback,
|
|
12954
13400
|
readDelegatedCredential,
|
|
12955
13401
|
readDelegatedSession,
|
|
@@ -12959,6 +13405,7 @@ export {
|
|
|
12959
13405
|
redeemInvitation,
|
|
12960
13406
|
relayForContext,
|
|
12961
13407
|
relayTransportFetch,
|
|
13408
|
+
releaseAgent,
|
|
12962
13409
|
rememberGroupNamespace,
|
|
12963
13410
|
rememberRelay,
|
|
12964
13411
|
reportDataOf,
|
|
@@ -13005,7 +13452,9 @@ export {
|
|
|
13005
13452
|
transportKeyBinding,
|
|
13006
13453
|
trustSignedRelease,
|
|
13007
13454
|
trustedMeasurementsFromReleases,
|
|
13455
|
+
validateAgentId,
|
|
13008
13456
|
verifyDeviceCredential,
|
|
13457
|
+
verifySignedAgentPolicy,
|
|
13009
13458
|
verifySignedNodeRelease,
|
|
13010
13459
|
withCap,
|
|
13011
13460
|
withRetry,
|