@calimero-network/mero-js 27.2.0 → 27.4.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/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  ConfirmCancelled: () => ConfirmCancelled,
40
40
  ConfirmRequired: () => ConfirmRequired,
41
41
  ConsentStale: () => ConsentStale,
42
+ DEFAULT_COLLATERAL_MIRROR: () => DEFAULT_COLLATERAL_MIRROR,
42
43
  DEFAULT_ORG_CAPABILITIES: () => DEFAULT_ORG_CAPABILITIES,
43
44
  DEFAULT_RELEASE_MIRROR: () => DEFAULT_RELEASE_MIRROR,
44
45
  GITHUB_RELEASES: () => GITHUB_RELEASES,
@@ -70,6 +71,7 @@ __export(index_exports, {
70
71
  claimedRelease: () => claimedRelease,
71
72
  cloudNodeReleaseUrl: () => cloudNodeReleaseUrl,
72
73
  compareVersions: () => compareVersions,
74
+ confirmationCode: () => confirmationCode,
73
75
  consentReturn: () => consentReturn,
74
76
  createQuoteVerifier: () => createQuoteVerifier,
75
77
  createSealedFetch: () => createSealedFetch,
@@ -78,6 +80,7 @@ __export(index_exports, {
78
80
  fetchAttestedKey: () => fetchAttestedKey,
79
81
  fetchGithubNodeRelease: () => fetchGithubNodeRelease,
80
82
  fetchNodeRelease: () => fetchNodeRelease,
83
+ freshCollateral: () => freshCollateral,
81
84
  fromB64url: () => fromB64url,
82
85
  generateOwnerKey: () => generateOwnerKey,
83
86
  orgCapabilities: () => orgCapabilities,
@@ -1106,6 +1109,7 @@ var NODE_RELEASE_SIGNER = Object.freeze({
1106
1109
  });
1107
1110
  var MEASUREMENTS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
1108
1111
  var DEFAULT_RELEASE_MIRROR = "https://cloud.calimero.network";
1112
+ var DEFAULT_COLLATERAL_MIRROR = `${DEFAULT_RELEASE_MIRROR}/api/tee/pcs`;
1109
1113
  async function verifySignedNodeRelease(publishedMrtds, bundle, options = {}) {
1110
1114
  const body = typeof publishedMrtds === "string" ? new TextEncoder().encode(publishedMrtds) : publishedMrtds;
1111
1115
  await verifyCosignBundle(
@@ -1270,6 +1274,34 @@ async function releaseFor(version, policy, fetchImpl) {
1270
1274
  if (source === "github") return fetchGithubNodeRelease(version, { fetch: fetchImpl });
1271
1275
  return fetchNodeRelease(cloudNodeReleaseUrl(source ?? DEFAULT_RELEASE_MIRROR, version), { fetch: fetchImpl });
1272
1276
  }
1277
+ function defaultCollateral(policy) {
1278
+ const source = policy.collateralSource ?? DEFAULT_COLLATERAL_MIRROR;
1279
+ if (source === "intel") {
1280
+ return async (quote) => (await dcap("getCollateralFromPcs"))(quote);
1281
+ }
1282
+ return async (quote) => (await dcap("getCollateral"))(source, quote);
1283
+ }
1284
+ function freshCollateral(fetch2, policy) {
1285
+ const maxDays = policy.maxCollateralAgeDays;
1286
+ if (maxDays === void 0) return fetch2;
1287
+ return async (quote) => {
1288
+ const collateral = await fetch2(quote);
1289
+ let issued;
1290
+ try {
1291
+ issued = Date.parse(JSON.parse(collateral.tcb_info).issueDate ?? "");
1292
+ } catch {
1293
+ issued = Number.NaN;
1294
+ }
1295
+ if (!Number.isFinite(issued)) throw new Error("The quote's collateral names no TCB info issue date");
1296
+ const ageDays = ((policy.now ?? Date.now)() - issued) / 864e5;
1297
+ if (ageDays > maxDays) {
1298
+ throw new Error(
1299
+ `The quote's collateral was issued ${Math.floor(ageDays)} days ago, more than the ${maxDays} this client accepts`
1300
+ );
1301
+ }
1302
+ return collateral;
1303
+ };
1304
+ }
1273
1305
  async function verifyRelease(doc, args, policy, fetchImpl) {
1274
1306
  const version = claimedRelease(doc, policy.minReleaseVersion);
1275
1307
  const trusted = await trustSignedRelease({
@@ -1280,7 +1312,7 @@ async function verifyRelease(doc, args, policy, fetchImpl) {
1280
1312
  identity: policy.identity,
1281
1313
  issuer: policy.issuer
1282
1314
  });
1283
- const fetchCollateral = policy.fetchCollateral ?? (async (quote) => (await dcap("getCollateralFromPcs"))(quote));
1315
+ const fetchCollateral = freshCollateral(policy.fetchCollateral ?? defaultCollateral(policy), policy);
1284
1316
  const verifier = createQuoteVerifier({
1285
1317
  dcapVerify: policy.dcapVerify ?? await dcap("verify"),
1286
1318
  fetchCollateral,
@@ -1569,6 +1601,17 @@ ${generation}`;
1569
1601
  function publisherBindingBytes(cluster, org, signerId, generation) {
1570
1602
  return new TextEncoder().encode(publisherBindingMessage(cluster, org, signerId, generation));
1571
1603
  }
1604
+ var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1605
+ async function confirmationCode(requestId) {
1606
+ const h = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(requestId)));
1607
+ const word = (h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3]) >>> 0;
1608
+ let code = "";
1609
+ for (let i = 0; i < 6; i++) {
1610
+ if (i === 3) code += "-";
1611
+ code += CROCKFORD[word >>> 27 - 5 * i & 31];
1612
+ }
1613
+ return code;
1614
+ }
1572
1615
  function consentReturn(href) {
1573
1616
  const url2 = typeof href === "string" ? new URL(href) : href;
1574
1617
  const requestId = url2.searchParams.get("request_id");
@@ -2342,16 +2385,34 @@ var CalimeroClient = class {
2342
2385
  }
2343
2386
  /**
2344
2387
  * {@link requestConsent}, then send the browser to the consent page
2345
- * (`navigate`, by default `window.location.assign`). Answers the request
2346
- * once `navigate` returns: with a popup (`url => window.open(url)`), poll it
2347
- * with {@link waitForConsent}.
2388
+ * (`navigate`, by default `window.location.assign`). Pass `navigate` itself
2389
+ * or {@link OpenConsentOptions}, whose `confirm` runs first with the node's
2390
+ * answer so the app can show the person its `code`; when it answers `false`
2391
+ * the page is not opened. When the node sends a `code` that is not
2392
+ * {@link confirmationCode} of `requestId`, it throws `CalimeroError`
2393
+ * (`CONSENT_CODE_MISMATCH`) and does not navigate. Answers the request
2394
+ * once `navigate` returns (or `confirm` declines): with a popup
2395
+ * (`url => window.open(url)`), poll it with {@link waitForConsent}.
2348
2396
  */
2349
- async openConsentPage(request, navigate = (url2) => {
2350
- const w = globalThis.window;
2351
- if (!w) throw new Error("no window to open the consent page in; pass `navigate`");
2352
- w.location.assign(url2);
2353
- }) {
2397
+ async openConsentPage(request, navigateOrOptions = {}) {
2398
+ const options = typeof navigateOrOptions === "function" ? { navigate: navigateOrOptions } : navigateOrOptions;
2399
+ const navigate = options.navigate ?? ((url2) => {
2400
+ const w = globalThis.window;
2401
+ if (!w) throw new Error("no window to open the consent page in; pass `navigate`");
2402
+ w.location.assign(url2);
2403
+ });
2354
2404
  const started = await this.requestConsent(request);
2405
+ if (started.code !== void 0) {
2406
+ const expected = await confirmationCode(started.requestId);
2407
+ if (started.code !== expected) {
2408
+ throw new CalimeroError(200, "CONSENT_CODE_MISMATCH", "the consent page code does not match the request", {
2409
+ requestId: started.requestId,
2410
+ code: started.code,
2411
+ expected
2412
+ });
2413
+ }
2414
+ }
2415
+ if (options.confirm && !await options.confirm(started)) return started;
2355
2416
  navigate(started.url);
2356
2417
  return started;
2357
2418
  }