@peac/protocol 0.15.2 → 0.16.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
@@ -2406,7 +2406,14 @@ var JOSE_CODE_MAP = {
2406
2406
  CRYPTO_JWS_CRIT_REJECTED: "E_JWS_CRIT_REJECTED",
2407
2407
  CRYPTO_JWS_MISSING_KID: "E_JWS_MISSING_KID",
2408
2408
  CRYPTO_JWS_B64_REJECTED: "E_JWS_B64_REJECTED",
2409
- CRYPTO_JWS_ZIP_REJECTED: "E_JWS_ZIP_REJECTED"
2409
+ CRYPTO_JWS_ZIP_REJECTED: "E_JWS_ZIP_REJECTED",
2410
+ // Raw-input I-JSON gate (RFC 7493): specific E_IJSON_* codes so callers can
2411
+ // distinguish duplicate member names, out-of-range numbers, and invalid
2412
+ // strings from ordinary format errors. (Generic JSON syntax errors from the
2413
+ // gate use CRYPTO_INVALID_JWS_FORMAT -> E_INVALID_FORMAT via FORMAT_ERROR_CODES.)
2414
+ CRYPTO_IJSON_DUPLICATE_MEMBER_NAME: "E_IJSON_DUPLICATE_MEMBER_NAME",
2415
+ CRYPTO_IJSON_NUMBER_OUT_OF_RANGE: "E_IJSON_NUMBER_OUT_OF_RANGE",
2416
+ CRYPTO_IJSON_INVALID_STRING: "E_IJSON_INVALID_STRING"
2410
2417
  };
2411
2418
  var MAX_PARSE_ISSUES = 25;
2412
2419
  function sanitizeParseIssues(issues) {
@@ -2430,10 +2437,12 @@ async function verifyLocal(jws, publicKey, options = {}) {
2430
2437
  try {
2431
2438
  const result = await defaultCodec.decode(jws, publicKey);
2432
2439
  if (!result.valid) {
2440
+ const signatureBytes = ed25519SignatureByteLength(jws);
2441
+ const invalidLength = signatureBytes !== null && signatureBytes !== ED25519_SIGNATURE_BYTES;
2433
2442
  return {
2434
2443
  valid: false,
2435
2444
  code: "E_INVALID_SIGNATURE",
2436
- message: "Ed25519 signature verification failed"
2445
+ message: invalidLength ? "Ed25519 signature has invalid length" : "Ed25519 signature verification failed"
2437
2446
  };
2438
2447
  }
2439
2448
  const accumulatedWarnings = [];
@@ -4072,6 +4081,80 @@ async function verifyAndFetchPointer(pointerHeader, fetchOptions) {
4072
4081
  });
4073
4082
  }
4074
4083
 
4084
+ // src/replay-guard.ts
4085
+ var DEFAULT_MAX_CLOCK_SKEW_SECONDS = 300;
4086
+ var DEFAULT_MAX_ENTRIES = 1e5;
4087
+ function safeInteger(value, name) {
4088
+ if (!Number.isSafeInteger(value)) {
4089
+ throw new Error(`createReplayGuard: ${name} must be a safe integer`);
4090
+ }
4091
+ return value;
4092
+ }
4093
+ function positiveInt(value, name) {
4094
+ safeInteger(value, name);
4095
+ if (value <= 0) {
4096
+ throw new Error(`createReplayGuard: ${name} must be a positive integer`);
4097
+ }
4098
+ return value;
4099
+ }
4100
+ function nonNegativeInt(value, name) {
4101
+ safeInteger(value, name);
4102
+ if (value < 0) {
4103
+ throw new Error(`createReplayGuard: ${name} must be a non-negative integer`);
4104
+ }
4105
+ return value;
4106
+ }
4107
+ function nonEmptyString(value, name) {
4108
+ if (typeof value !== "string" || value.length === 0) {
4109
+ throw new Error(`createReplayGuard: ${name} must be a non-empty string`);
4110
+ }
4111
+ return value;
4112
+ }
4113
+ function createReplayGuard(options) {
4114
+ const windowSeconds = positiveInt(options.windowSeconds, "windowSeconds");
4115
+ const maxClockSkewSeconds = options.maxClockSkewSeconds === void 0 ? DEFAULT_MAX_CLOCK_SKEW_SECONDS : nonNegativeInt(options.maxClockSkewSeconds, "maxClockSkewSeconds");
4116
+ const maxEntries = options.maxEntries === void 0 ? DEFAULT_MAX_ENTRIES : positiveInt(options.maxEntries, "maxEntries");
4117
+ const now = options.now ?? (() => Math.floor(Date.now() / 1e3));
4118
+ let lastNow = Number.NEGATIVE_INFINITY;
4119
+ const currentNow = () => {
4120
+ const raw = safeInteger(now(), "now()");
4121
+ if (raw < lastNow) return lastNow;
4122
+ lastNow = raw;
4123
+ return raw;
4124
+ };
4125
+ const seen = /* @__PURE__ */ new Map();
4126
+ const keyOf = (iss, jti) => `${iss.length}:${iss}${jti.length}:${jti}`;
4127
+ const purgeExpired = (nowSeconds) => {
4128
+ for (const [key, entry] of seen) {
4129
+ if (entry.expiresAt > nowSeconds) break;
4130
+ seen.delete(key);
4131
+ }
4132
+ };
4133
+ return {
4134
+ check(record) {
4135
+ const iat = safeInteger(record.iat, "record.iat");
4136
+ const iss = nonEmptyString(record.iss, "record.iss");
4137
+ const jti = nonEmptyString(record.jti, "record.jti");
4138
+ const t = currentNow();
4139
+ purgeExpired(t);
4140
+ if (iat < t - windowSeconds || iat > t + maxClockSkewSeconds) {
4141
+ return "outside-window";
4142
+ }
4143
+ const key = keyOf(iss, jti);
4144
+ if (seen.has(key)) {
4145
+ return "replayed";
4146
+ }
4147
+ const expiresAt = safeInteger(t + windowSeconds + maxClockSkewSeconds, "entry expiry");
4148
+ if (seen.size >= maxEntries) {
4149
+ const oldest = seen.keys().next().value;
4150
+ if (oldest !== void 0) seen.delete(oldest);
4151
+ }
4152
+ seen.set(key, { expiresAt });
4153
+ return "fresh";
4154
+ }
4155
+ };
4156
+ }
4157
+
4075
4158
  Object.defineProperty(exports, "base64urlDecode", {
4076
4159
  enumerable: true,
4077
4160
  get: function () { return crypto$1.base64urlDecode; }
@@ -4124,6 +4207,7 @@ exports.computeTextDocumentDigestUtf8 = computeTextDocumentDigestUtf8;
4124
4207
  exports.createDefaultPolicy = createDefaultPolicy;
4125
4208
  exports.createDigest = createDigest;
4126
4209
  exports.createEmptyReport = createEmptyReport;
4210
+ exports.createReplayGuard = createReplayGuard;
4127
4211
  exports.createReportBuilder = createReportBuilder;
4128
4212
  exports.fetchDiscovery = fetchDiscovery;
4129
4213
  exports.fetchIssuerConfig = fetchIssuerConfig;