@orangecheck/me-client 0.32.1 → 0.32.3

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.mjs CHANGED
@@ -1232,33 +1232,85 @@ async function verifyEd255192(key, sig, msg) {
1232
1232
  return false;
1233
1233
  }
1234
1234
  }
1235
+ var SIGNED_FIELDS = [
1236
+ "v",
1237
+ "kind",
1238
+ "did_oc",
1239
+ "event_count",
1240
+ "human_event_count",
1241
+ "lifetime_sats",
1242
+ "distinct_integrator_count",
1243
+ "oldest_event_at",
1244
+ "active_days",
1245
+ "computed_at",
1246
+ "expires_at"
1247
+ ];
1248
+ var COUNTS = [
1249
+ "event_count",
1250
+ "human_event_count",
1251
+ "lifetime_sats",
1252
+ "distinct_integrator_count",
1253
+ "active_days"
1254
+ ];
1255
+ function readSignedAttestation(canonical, id) {
1256
+ const text = new TextDecoder().decode(canonical);
1257
+ let parsed;
1258
+ try {
1259
+ parsed = JSON.parse(text);
1260
+ } catch {
1261
+ return null;
1262
+ }
1263
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1264
+ const o = parsed;
1265
+ if (o.v !== 1 || o.kind !== "oc-me-activity" || typeof o.did_oc !== "string") return null;
1266
+ const countsOk = COUNTS.every((k) => {
1267
+ const n = o[k];
1268
+ return typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
1269
+ });
1270
+ if (!countsOk) return null;
1271
+ if (o.oldest_event_at !== null && typeof o.oldest_event_at !== "string") return null;
1272
+ if (typeof o.computed_at !== "string" || typeof o.expires_at !== "string") return null;
1273
+ const ordered = Object.fromEntries(SIGNED_FIELDS.map((k) => [k, o[k]]));
1274
+ if (JSON.stringify(ordered) !== text) return null;
1275
+ return { id, ...ordered };
1276
+ }
1277
+ function sameAttestation(copy, signed) {
1278
+ const c = copy;
1279
+ const v = signed;
1280
+ return ["id", ...SIGNED_FIELDS].every((k) => c[k] === v[k]);
1281
+ }
1235
1282
  async function verifyActivityAttestation(bundle, options = {}) {
1236
1283
  const checks = [];
1237
- const shapeOk = !!bundle && typeof bundle === "object" && typeof bundle.sig === "string" && typeof bundle.kid === "string" && typeof bundle.canonical_b64 === "string" && !!bundle.attestation && typeof bundle.attestation.id === "string" && typeof bundle.attestation.expires_at === "string";
1284
+ const shapeOk = !!bundle && typeof bundle === "object" && typeof bundle.sig === "string" && typeof bundle.kid === "string" && typeof bundle.canonical_b64 === "string" && (bundle.attestation === void 0 || !!bundle.attestation && typeof bundle.attestation === "object");
1238
1285
  checks.push({
1239
1286
  ok: shapeOk,
1240
- label: "bundle shape \xB7 sig / kid / canonical_b64 / attestation"
1287
+ label: "bundle shape \xB7 sig / kid / canonical_b64"
1241
1288
  });
1242
1289
  if (!shapeOk) {
1243
1290
  return { ok: false, reason: "bundle shape invalid", checks };
1244
1291
  }
1245
- const a = bundle.attestation;
1246
1292
  const canonical = base64ToBytes(bundle.canonical_b64);
1247
1293
  checks.push({
1248
1294
  ok: canonical !== null,
1249
1295
  label: "canonical_b64 decodes",
1250
1296
  detail: canonical ? `${canonical.length} bytes` : "invalid base64"
1251
1297
  });
1252
- if (!canonical) return { ok: false, reason: "canonical_b64 invalid base64", checks, attestation: a };
1253
- const digest = await sha256(canonical);
1254
- const computedId = bytesToHex(digest);
1255
- const idOk = computedId === a.id;
1298
+ if (!canonical) return { ok: false, reason: "canonical_b64 invalid base64", checks };
1299
+ const computedId = bytesToHex(await sha256(canonical));
1300
+ const signed = readSignedAttestation(canonical, computedId);
1301
+ checks.push({
1302
+ ok: signed !== null,
1303
+ label: "canonical_bytes are an oc-me-activity attestation in canonical form"
1304
+ });
1305
+ if (!signed) return { ok: false, reason: "canonical bytes are not an activity attestation", checks };
1306
+ const copy = bundle.attestation;
1307
+ const copyOk = copy === void 0 || sameAttestation(copy, signed);
1256
1308
  checks.push({
1257
- ok: idOk,
1258
- label: "sha256(canonical_bytes) === id",
1259
- detail: idOk ? a.id : `expected ${a.id}, got ${computedId}`
1309
+ ok: copyOk,
1310
+ label: "attestation === the signed bytes, id = sha256(canonical_bytes)",
1311
+ detail: copyOk ? computedId : "the readable copy differs from what was signed"
1260
1312
  });
1261
- if (!idOk) return { ok: false, reason: "id does not match canonical-bytes hash", checks, attestation: a };
1313
+ if (!copyOk) return { ok: false, reason: "attestation does not match the signed bytes", checks };
1262
1314
  let key = null;
1263
1315
  if (options.jwk) {
1264
1316
  if (options.jwk.kid !== bundle.kid) {
@@ -1267,7 +1319,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1267
1319
  label: "JWK with matching kid present",
1268
1320
  detail: `provided jwk.kid=${options.jwk.kid} but bundle.kid=${bundle.kid}`
1269
1321
  });
1270
- return { ok: false, reason: "kid mismatch with provided jwk", checks, attestation: a };
1322
+ return { ok: false, reason: "kid mismatch with provided jwk", checks };
1271
1323
  }
1272
1324
  try {
1273
1325
  key = await importJWK(options.jwk, "EdDSA");
@@ -1278,7 +1330,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1278
1330
  label: "JWK with matching kid present",
1279
1331
  detail: `provided jwk failed import: ${err.message}`
1280
1332
  });
1281
- return { ok: false, reason: "provided jwk failed import", checks, attestation: a };
1333
+ return { ok: false, reason: "provided jwk failed import", checks };
1282
1334
  }
1283
1335
  } else {
1284
1336
  const issuer = options.issuer ?? DEFAULT_ISSUER2;
@@ -1297,7 +1349,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1297
1349
  detail: key ? `kid ${bundle.kid}` : `no JWK with kid ${bundle.kid} at ${issuer}${ENVELOPE_JWKS_PATH}`
1298
1350
  });
1299
1351
  if (!key) {
1300
- return { ok: false, reason: `kid ${bundle.kid} not in envelope JWKS`, checks, attestation: a };
1352
+ return { ok: false, reason: `kid ${bundle.kid} not in envelope JWKS`, checks };
1301
1353
  }
1302
1354
  } catch (err) {
1303
1355
  checks.push({
@@ -1308,8 +1360,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1308
1360
  return {
1309
1361
  ok: false,
1310
1362
  reason: `envelope JWKS unavailable: ${err.message}`,
1311
- checks,
1312
- attestation: a
1363
+ checks
1313
1364
  };
1314
1365
  }
1315
1366
  }
@@ -1320,7 +1371,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1320
1371
  label: "ed25519.verify(sig, canonical_bytes, jwk.public)",
1321
1372
  detail: "malformed sig (expected hex)"
1322
1373
  });
1323
- return { ok: false, reason: "malformed sig", checks, attestation: a };
1374
+ return { ok: false, reason: "malformed sig", checks };
1324
1375
  }
1325
1376
  const sigOk = await verifyEd255192(key, sigBytes, canonical);
1326
1377
  checks.push({
@@ -1328,17 +1379,17 @@ async function verifyActivityAttestation(bundle, options = {}) {
1328
1379
  label: "ed25519.verify(sig, canonical_bytes, jwk.public)",
1329
1380
  detail: sigOk ? "signature valid" : "signature does not match"
1330
1381
  });
1331
- if (!sigOk) return { ok: false, reason: "signature does not match", checks, attestation: a };
1382
+ if (!sigOk) return { ok: false, reason: "signature does not match", checks };
1332
1383
  const now = options.now ?? /* @__PURE__ */ new Date();
1333
- const expiresMs = Date.parse(a.expires_at);
1384
+ const expiresMs = Date.parse(signed.expires_at);
1334
1385
  const freshOk = Number.isFinite(expiresMs) && expiresMs > now.getTime();
1335
1386
  checks.push({
1336
1387
  ok: freshOk,
1337
1388
  label: "now < attestation.expires_at",
1338
1389
  detail: freshOk ? `expires ${new Date(expiresMs).toISOString()}` : "attestation has expired"
1339
1390
  });
1340
- if (!freshOk) return { ok: false, reason: "attestation has expired", checks, attestation: a };
1341
- return { ok: true, attestation: a, checks };
1391
+ if (!freshOk) return { ok: false, reason: "attestation has expired", checks };
1392
+ return { ok: true, attestation: signed, checks };
1342
1393
  }
1343
1394
  async function refreshEnvelopeJwks(issuer = DEFAULT_ISSUER2) {
1344
1395
  jwksCache2.delete(issuer);