edockit 0.2.4 → 0.3.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.esm.js CHANGED
@@ -367,25 +367,11 @@ const methods = {
367
367
  isCanonicalizationMethod: "c14n",
368
368
  },
369
369
  c14n11: {
370
- beforeChildren: (hasElementChildren, hasMixedContent) => {
371
- // If it's mixed content, don't add newlines
372
- if (hasMixedContent)
373
- return "";
374
- return hasElementChildren ? "\n" : "";
375
- },
376
- afterChildren: (hasElementChildren, hasMixedContent) => {
377
- // If it's mixed content, don't add newlines
378
- if (hasMixedContent)
379
- return "";
380
- return hasElementChildren ? "\n" : "";
381
- },
382
- betweenChildren: (prevIsElement, nextIsElement, hasMixedContent) => {
383
- // If it's mixed content, don't add newlines between elements
384
- if (hasMixedContent)
385
- return "";
386
- // Only add newline between elements
387
- return prevIsElement && nextIsElement ? "\n" : "";
388
- },
370
+ // C14N 1.1 should NOT add newlines - it should preserve original whitespace
371
+ // The difference from C14N is in xml:id normalization, not formatting
372
+ beforeChildren: () => "",
373
+ afterChildren: () => "",
374
+ betweenChildren: () => "",
389
375
  afterElement: () => "",
390
376
  isCanonicalizationMethod: "c14n11",
391
377
  },
@@ -10221,6 +10207,273 @@ function getTimestampTime(timestampBase64) {
10221
10207
  return info?.genTime || null;
10222
10208
  }
10223
10209
 
10210
+ /**
10211
+ * RSA DigestInfo Workaround
10212
+ *
10213
+ * Some older signing tools (particularly pre-Java 8) produced RSA signatures with
10214
+ * non-standard DigestInfo format - missing the NULL parameter in AlgorithmIdentifier.
10215
+ *
10216
+ * Standard DigestInfo for SHA-1: 30 21 30 09 06 05 2b0e03021a 05 00 04 14 [hash]
10217
+ * Non-standard (missing NULL): 30 1f 30 07 06 05 2b0e03021a 04 14 [hash]
10218
+ *
10219
+ * Web Crypto API's subtle.verify() is strict and rejects the non-standard format.
10220
+ * This module provides a fallback that manually performs RSA verification using
10221
+ * BigInt math, which works in both browser and Node.js environments.
10222
+ */
10223
+ /**
10224
+ * Parse RSA public key from SPKI format to extract modulus and exponent
10225
+ */
10226
+ function parseRSAPublicKey(spkiData) {
10227
+ const bytes = new Uint8Array(spkiData);
10228
+ // SPKI structure:
10229
+ // SEQUENCE {
10230
+ // SEQUENCE { algorithm OID, parameters (NULL or absent) }
10231
+ // BIT STRING { RSAPublicKey }
10232
+ // }
10233
+ // RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
10234
+ let pos = 0;
10235
+ // Helper to read ASN.1 length
10236
+ const readLength = () => {
10237
+ const first = bytes[pos++];
10238
+ if ((first & 0x80) === 0) {
10239
+ return first;
10240
+ }
10241
+ const numBytes = first & 0x7f;
10242
+ let length = 0;
10243
+ for (let i = 0; i < numBytes; i++) {
10244
+ length = (length << 8) | bytes[pos++];
10245
+ }
10246
+ return length;
10247
+ };
10248
+ // Helper to read INTEGER as BigInt
10249
+ const readInteger = () => {
10250
+ if (bytes[pos++] !== 0x02)
10251
+ return BigInt(0); // INTEGER tag
10252
+ const len = readLength();
10253
+ let value = BigInt(0);
10254
+ for (let i = 0; i < len; i++) {
10255
+ value = (value << BigInt(8)) | BigInt(bytes[pos++]);
10256
+ }
10257
+ return value;
10258
+ };
10259
+ try {
10260
+ // Outer SEQUENCE
10261
+ if (bytes[pos++] !== 0x30)
10262
+ return null;
10263
+ readLength();
10264
+ // AlgorithmIdentifier SEQUENCE
10265
+ if (bytes[pos++] !== 0x30)
10266
+ return null;
10267
+ const algoLen = readLength();
10268
+ pos += algoLen; // Skip algorithm identifier
10269
+ // BIT STRING containing RSAPublicKey
10270
+ if (bytes[pos++] !== 0x03)
10271
+ return null;
10272
+ readLength();
10273
+ pos++; // Skip unused bits byte
10274
+ // RSAPublicKey SEQUENCE
10275
+ if (bytes[pos++] !== 0x30)
10276
+ return null;
10277
+ readLength();
10278
+ // Read modulus and exponent
10279
+ const n = readInteger();
10280
+ const e = readInteger();
10281
+ return { n, e };
10282
+ }
10283
+ catch {
10284
+ return null;
10285
+ }
10286
+ }
10287
+ /**
10288
+ * Perform modular exponentiation: base^exp mod mod
10289
+ * Uses square-and-multiply algorithm for efficiency
10290
+ */
10291
+ function modPow(base, exp, mod) {
10292
+ let result = BigInt(1);
10293
+ base = base % mod;
10294
+ while (exp > 0) {
10295
+ if (exp % BigInt(2) === BigInt(1)) {
10296
+ result = (result * base) % mod;
10297
+ }
10298
+ exp = exp >> BigInt(1);
10299
+ base = (base * base) % mod;
10300
+ }
10301
+ return result;
10302
+ }
10303
+ /**
10304
+ * Convert Uint8Array to BigInt
10305
+ */
10306
+ function bytesToBigInt(bytes) {
10307
+ let result = BigInt(0);
10308
+ for (const byte of bytes) {
10309
+ result = (result << BigInt(8)) | BigInt(byte);
10310
+ }
10311
+ return result;
10312
+ }
10313
+ /**
10314
+ * Convert BigInt to Uint8Array with specified length
10315
+ */
10316
+ function bigIntToBytes(value, length) {
10317
+ const result = new Uint8Array(length);
10318
+ for (let i = length - 1; i >= 0; i--) {
10319
+ result[i] = Number(value & BigInt(0xff));
10320
+ value = value >> BigInt(8);
10321
+ }
10322
+ return result;
10323
+ }
10324
+ /**
10325
+ * Verify PKCS#1 v1.5 signature padding and extract DigestInfo
10326
+ * @param decrypted The decrypted signature block
10327
+ * @returns The DigestInfo bytes, or null if padding is invalid
10328
+ */
10329
+ function extractDigestInfoFromPKCS1(decrypted) {
10330
+ // PKCS#1 v1.5 signature format:
10331
+ // 0x00 0x01 [0xFF padding] 0x00 [DigestInfo]
10332
+ if (decrypted[0] !== 0x00 || decrypted[1] !== 0x01) {
10333
+ return null;
10334
+ }
10335
+ // Find the 0x00 separator after padding
10336
+ let separatorIndex = -1;
10337
+ for (let i = 2; i < decrypted.length; i++) {
10338
+ if (decrypted[i] === 0x00) {
10339
+ separatorIndex = i;
10340
+ break;
10341
+ }
10342
+ if (decrypted[i] !== 0xff) {
10343
+ return null; // Invalid padding byte
10344
+ }
10345
+ }
10346
+ if (separatorIndex === -1 || separatorIndex < 10) {
10347
+ return null; // No separator found or padding too short
10348
+ }
10349
+ return decrypted.slice(separatorIndex + 1);
10350
+ }
10351
+ /**
10352
+ * Extract hash from DigestInfo structure
10353
+ * Handles both standard (with NULL) and non-standard (without NULL) formats
10354
+ */
10355
+ function extractHashFromDigestInfo(digestInfo, expectedHashLength) {
10356
+ // DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING }
10357
+ // Look for OCTET STRING tag (0x04) followed by the hash
10358
+ for (let i = 0; i < digestInfo.length - 1; i++) {
10359
+ if (digestInfo[i] === 0x04) {
10360
+ const len = digestInfo[i + 1];
10361
+ if (len === expectedHashLength && i + 2 + len <= digestInfo.length) {
10362
+ return digestInfo.slice(i + 2, i + 2 + len);
10363
+ }
10364
+ }
10365
+ }
10366
+ return null;
10367
+ }
10368
+ /**
10369
+ * Get hash length in bytes for a given algorithm
10370
+ */
10371
+ function getHashLength(hashAlgorithm) {
10372
+ const algo = hashAlgorithm.toLowerCase().replace("-", "");
10373
+ switch (algo) {
10374
+ case "sha1":
10375
+ return 20;
10376
+ case "sha256":
10377
+ return 32;
10378
+ case "sha384":
10379
+ return 48;
10380
+ case "sha512":
10381
+ return 64;
10382
+ default:
10383
+ return 32;
10384
+ }
10385
+ }
10386
+ /**
10387
+ * Detects if code is running in a browser environment
10388
+ */
10389
+ function isBrowser$1() {
10390
+ return (typeof window !== "undefined" &&
10391
+ typeof window.crypto !== "undefined" &&
10392
+ typeof window.crypto.subtle !== "undefined");
10393
+ }
10394
+ /**
10395
+ * Verify RSA signature with non-standard DigestInfo format.
10396
+ *
10397
+ * This function performs RSA signature verification that tolerates
10398
+ * non-standard DigestInfo formats (missing NULL in AlgorithmIdentifier).
10399
+ *
10400
+ * - Node.js: Uses native crypto.publicDecrypt() for speed
10401
+ * - Browser: Uses BigInt math (Web Crypto doesn't expose raw RSA)
10402
+ *
10403
+ * @param publicKeyData SPKI-formatted public key
10404
+ * @param signatureBytes Raw signature bytes
10405
+ * @param dataToVerify The data that was signed
10406
+ * @param hashAlgorithm Hash algorithm name (e.g., "SHA-1", "SHA-256")
10407
+ * @returns true if signature is valid, false otherwise
10408
+ */
10409
+ async function verifyRsaWithNonStandardDigestInfo(publicKeyData, signatureBytes, dataToVerify, hashAlgorithm) {
10410
+ try {
10411
+ let digestInfo;
10412
+ if (isBrowser$1()) {
10413
+ // Browser: Use BigInt math (Web Crypto doesn't expose raw RSA decryption)
10414
+ const keyParams = parseRSAPublicKey(publicKeyData);
10415
+ if (!keyParams) {
10416
+ return false;
10417
+ }
10418
+ const { n, e } = keyParams;
10419
+ const keyLength = Math.ceil(n.toString(16).length / 2);
10420
+ const signatureInt = bytesToBigInt(signatureBytes);
10421
+ const decryptedInt = modPow(signatureInt, e, n);
10422
+ const decrypted = bigIntToBytes(decryptedInt, keyLength);
10423
+ digestInfo = extractDigestInfoFromPKCS1(decrypted);
10424
+ }
10425
+ else {
10426
+ // Node.js: Use native crypto.publicDecrypt() for speed
10427
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10428
+ const nodeCrypto = require("crypto");
10429
+ const publicKey = nodeCrypto.createPublicKey({
10430
+ key: Buffer.from(publicKeyData),
10431
+ format: "der",
10432
+ type: "spki",
10433
+ });
10434
+ const decrypted = nodeCrypto.publicDecrypt({ key: publicKey, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }, Buffer.from(signatureBytes));
10435
+ // Node's publicDecrypt already strips PKCS#1 padding, returns DigestInfo directly
10436
+ digestInfo = new Uint8Array(decrypted);
10437
+ }
10438
+ if (!digestInfo) {
10439
+ return false;
10440
+ }
10441
+ // Extract hash from DigestInfo (tolerates missing NULL)
10442
+ const hashLength = getHashLength(hashAlgorithm);
10443
+ const extractedHash = extractHashFromDigestInfo(digestInfo, hashLength);
10444
+ if (!extractedHash) {
10445
+ return false;
10446
+ }
10447
+ // Compute expected hash
10448
+ let expectedHash;
10449
+ if (isBrowser$1()) {
10450
+ // Normalize to Web Crypto format: SHA-1, SHA-256, SHA-384, SHA-512
10451
+ let hashName = hashAlgorithm.toUpperCase().replace(/-/g, "");
10452
+ hashName = hashName.replace(/^SHA(\d)/, "SHA-$1");
10453
+ const hashBuffer = await window.crypto.subtle.digest(hashName, dataToVerify);
10454
+ expectedHash = new Uint8Array(hashBuffer);
10455
+ }
10456
+ else {
10457
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
10458
+ const nodeCrypto = require("crypto");
10459
+ const hashName = hashAlgorithm.toLowerCase().replace("-", "");
10460
+ expectedHash = nodeCrypto.createHash(hashName).update(Buffer.from(dataToVerify)).digest();
10461
+ }
10462
+ // Compare hashes (constant-time comparison)
10463
+ if (extractedHash.length !== expectedHash.length) {
10464
+ return false;
10465
+ }
10466
+ let diff = 0;
10467
+ for (let i = 0; i < extractedHash.length; i++) {
10468
+ diff |= extractedHash[i] ^ expectedHash[i];
10469
+ }
10470
+ return diff === 0;
10471
+ }
10472
+ catch {
10473
+ return false;
10474
+ }
10475
+ }
10476
+
10224
10477
  /**
10225
10478
  * Detects if code is running in a browser environment
10226
10479
  * @returns true if in browser, false otherwise
@@ -10230,6 +10483,105 @@ function isBrowser() {
10230
10483
  typeof window.crypto !== "undefined" &&
10231
10484
  typeof window.crypto.subtle !== "undefined");
10232
10485
  }
10486
+ /**
10487
+ * Detects if running in Safari/WebKit browser
10488
+ * Safari/WebKit handles RSA key DER encoding correctly and doesn't need the modulus padding fix
10489
+ * This also detects Playwright's headless WebKit
10490
+ * @returns true if Safari/WebKit, false otherwise
10491
+ */
10492
+ function isWebKit() {
10493
+ if (typeof navigator === "undefined")
10494
+ return false;
10495
+ const ua = navigator.userAgent;
10496
+ // Detect WebKit-based browsers (Safari, or Playwright WebKit which includes "AppleWebKit" but not Chrome)
10497
+ const hasWebKit = /AppleWebKit/.test(ua);
10498
+ const isChromium = /Chrome/.test(ua) || /Chromium/.test(ua) || /Edg/.test(ua);
10499
+ return hasWebKit && !isChromium;
10500
+ }
10501
+ /**
10502
+ * Get RSA modulus length in bits from SPKI public key data
10503
+ * @param publicKeyData The SPKI-formatted public key
10504
+ * @returns Modulus length in bits, or 0 if not RSA or can't determine
10505
+ */
10506
+ function getRSAModulusLength(publicKeyData) {
10507
+ const keyBytes = new Uint8Array(publicKeyData);
10508
+ // Check for RSA OID (1.2.840.113549.1.1.1)
10509
+ const RSA_OID = [0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
10510
+ let oidPosition = -1;
10511
+ for (let i = 0; i <= keyBytes.length - RSA_OID.length; i++) {
10512
+ let match = true;
10513
+ for (let j = 0; j < RSA_OID.length; j++) {
10514
+ if (keyBytes[i + j] !== RSA_OID[j]) {
10515
+ match = false;
10516
+ break;
10517
+ }
10518
+ }
10519
+ if (match) {
10520
+ oidPosition = i;
10521
+ break;
10522
+ }
10523
+ }
10524
+ if (oidPosition === -1)
10525
+ return 0; // Not RSA
10526
+ // Find BIT STRING containing the key
10527
+ let bitStringPos = -1;
10528
+ for (let i = oidPosition + RSA_OID.length; i < keyBytes.length; i++) {
10529
+ if (keyBytes[i] === 0x03) {
10530
+ bitStringPos = i;
10531
+ break;
10532
+ }
10533
+ }
10534
+ if (bitStringPos === -1)
10535
+ return 0;
10536
+ // Skip BIT STRING header to find inner SEQUENCE
10537
+ let pos = bitStringPos + 1;
10538
+ if ((keyBytes[pos] & 0x80) === 0) {
10539
+ pos += 1; // short length
10540
+ }
10541
+ else {
10542
+ pos += 1 + (keyBytes[pos] & 0x7f); // long length
10543
+ }
10544
+ pos += 1; // skip unused bits byte
10545
+ if (keyBytes[pos] !== 0x30)
10546
+ return 0; // Should be SEQUENCE
10547
+ // Skip inner SEQUENCE header to find modulus INTEGER
10548
+ pos += 1;
10549
+ if ((keyBytes[pos] & 0x80) === 0) {
10550
+ pos += 1;
10551
+ }
10552
+ else {
10553
+ pos += 1 + (keyBytes[pos] & 0x7f);
10554
+ }
10555
+ if (keyBytes[pos] !== 0x02)
10556
+ return 0; // Should be INTEGER (modulus)
10557
+ // Get modulus length
10558
+ pos += 1;
10559
+ let modulusLength = 0;
10560
+ if ((keyBytes[pos] & 0x80) === 0) {
10561
+ modulusLength = keyBytes[pos];
10562
+ }
10563
+ else {
10564
+ const numLenBytes = keyBytes[pos] & 0x7f;
10565
+ for (let i = 0; i < numLenBytes; i++) {
10566
+ modulusLength = (modulusLength << 8) | keyBytes[pos + 1 + i];
10567
+ }
10568
+ }
10569
+ // Modulus might have leading 0x00 padding byte, subtract if present
10570
+ // Return bits (bytes * 8), accounting for padding
10571
+ return modulusLength * 8;
10572
+ }
10573
+ /**
10574
+ * Check if RSA key size is supported in the current platform
10575
+ * Safari/WebKit only supports RSA keys up to 4096 bits
10576
+ */
10577
+ function isRSAKeySizeSupported(modulusLengthBits) {
10578
+ if (!isBrowser())
10579
+ return true; // Node.js supports all sizes
10580
+ if (!isWebKit())
10581
+ return true; // Chrome/Firefox support large keys
10582
+ // Safari/WebKit: max 4096 bits
10583
+ return modulusLengthBits <= 4096;
10584
+ }
10233
10585
  /**
10234
10586
  * Compute a digest (hash) of file content with browser/node compatibility
10235
10587
  * @param fileContent The file content as Uint8Array
@@ -10602,11 +10954,40 @@ async function verifySignedInfo(signatureXml, signatureValue, publicKeyData, alg
10602
10954
  let publicKey;
10603
10955
  try {
10604
10956
  const subtle = getCryptoSubtle();
10605
- if (isBrowser() && algorithm.name === "RSASSA-PKCS1-v1_5") {
10606
- // console.log("Browser environment detected, applying RSA key fix");
10607
- publicKeyData = fixRSAModulusPadding(publicKeyData);
10957
+ const isRSA = algorithm.name === "RSASSA-PKCS1-v1_5" || algorithm.name === "RSA-PSS";
10958
+ // Check RSA key size support before attempting import
10959
+ if (isRSA) {
10960
+ const modulusLengthBits = getRSAModulusLength(publicKeyData);
10961
+ if (modulusLengthBits > 0 && !isRSAKeySizeSupported(modulusLengthBits)) {
10962
+ return {
10963
+ isValid: false,
10964
+ unsupportedPlatform: true,
10965
+ reason: `RSA key size (${modulusLengthBits} bits) not supported in this browser`,
10966
+ errorDetails: {
10967
+ category: "RSA_KEY_SIZE_UNSUPPORTED",
10968
+ originalMessage: `Safari/WebKit only supports RSA keys up to 4096 bits`,
10969
+ algorithm: { ...algorithm },
10970
+ environment: "browser",
10971
+ keyLength: publicKeyData.byteLength,
10972
+ },
10973
+ };
10974
+ }
10975
+ }
10976
+ if (isBrowser() && isRSA) {
10977
+ // Try importing original key first, then try with padding fix if it fails
10978
+ // This handles browser differences (Chrome vs Safari/WebKit)
10979
+ try {
10980
+ publicKey = await subtle.importKey("spki", publicKeyData, algorithm, false, ["verify"]);
10981
+ }
10982
+ catch {
10983
+ // Original key failed, try with modulus padding fix
10984
+ const fixedKeyData = fixRSAModulusPadding(publicKeyData);
10985
+ publicKey = await subtle.importKey("spki", fixedKeyData, algorithm, false, ["verify"]);
10986
+ }
10987
+ }
10988
+ else {
10989
+ publicKey = await subtle.importKey("spki", publicKeyData, algorithm, false, ["verify"]);
10608
10990
  }
10609
- publicKey = await subtle.importKey("spki", publicKeyData, algorithm, false, ["verify"]);
10610
10991
  }
10611
10992
  catch (unknownError) {
10612
10993
  // First cast to Error type if applicable
@@ -10653,12 +11034,36 @@ async function verifySignedInfo(signatureXml, signatureValue, publicKeyData, alg
10653
11034
  try {
10654
11035
  const subtle = getCryptoSubtle();
10655
11036
  const result = await subtle.verify(algorithm, publicKey, signatureBytes, signedData);
11037
+ if (result) {
11038
+ return {
11039
+ isValid: true,
11040
+ };
11041
+ }
11042
+ // Standard verification failed - try fallback for RSA signatures
11043
+ // Some older signatures use non-standard DigestInfo format (missing NULL in AlgorithmIdentifier)
11044
+ if (algorithm.name === "RSASSA-PKCS1-v1_5") {
11045
+ const fallbackResult = await verifyRsaWithNonStandardDigestInfo(publicKeyData, signatureBytes, signedData, algorithm.hash);
11046
+ if (fallbackResult) {
11047
+ return {
11048
+ isValid: true,
11049
+ };
11050
+ }
11051
+ }
10656
11052
  return {
10657
- isValid: result,
10658
- reason: result ? undefined : "Signature verification failed",
11053
+ isValid: false,
11054
+ reason: "Signature verification failed",
10659
11055
  };
10660
11056
  }
10661
11057
  catch (error) {
11058
+ // Try fallback for RSA signatures when subtle.verify throws
11059
+ if (algorithm.name === "RSASSA-PKCS1-v1_5") {
11060
+ const fallbackResult = await verifyRsaWithNonStandardDigestInfo(publicKeyData, signatureBytes, signedData, algorithm.hash);
11061
+ if (fallbackResult) {
11062
+ return {
11063
+ isValid: true,
11064
+ };
11065
+ }
11066
+ }
10662
11067
  return {
10663
11068
  isValid: false,
10664
11069
  reason: `Signature verification error: ${error instanceof Error ? error.message : String(error)}`,
@@ -10852,9 +11257,81 @@ async function verifySignature(signatureInfo, files, options = {}) {
10852
11257
  options.verifyTimestamps === false ||
10853
11258
  (timestampResult?.isValid ?? true);
10854
11259
  const isValid = certResult.isValid && checksumResult.isValid && signatureResult.isValid && timestampValid;
11260
+ // Determine validation status and limitations
11261
+ let status = "VALID";
11262
+ let statusMessage;
11263
+ const limitations = [];
11264
+ if (!isValid) {
11265
+ // Check for platform unsupported (RSA key size)
11266
+ if (signatureResult.unsupportedPlatform) {
11267
+ status = "UNSUPPORTED";
11268
+ statusMessage = signatureResult.reason;
11269
+ limitations.push({
11270
+ code: "RSA_KEY_SIZE_UNSUPPORTED",
11271
+ description: signatureResult.reason || "RSA key size not supported",
11272
+ platform: "Safari/WebKit",
11273
+ });
11274
+ }
11275
+ // Check for checksum failure (definitely invalid)
11276
+ else if (!checksumResult.isValid) {
11277
+ status = "INVALID";
11278
+ statusMessage = "File integrity check failed";
11279
+ }
11280
+ // Check for signature crypto failure with supported key
11281
+ else if (!signatureResult.isValid && !signatureResult.unsupportedPlatform) {
11282
+ status = "INVALID";
11283
+ statusMessage = signatureResult.reason || "Signature verification failed";
11284
+ }
11285
+ // Check for certificate issues
11286
+ else if (!certResult.isValid) {
11287
+ // If cert expired and no valid timestamp, it's indeterminate
11288
+ if (certResult.reason?.includes("expired") && !timestampResult?.isValid) {
11289
+ status = "INDETERMINATE";
11290
+ statusMessage = "Certificate expired and no valid timestamp proof";
11291
+ limitations.push({
11292
+ code: "CERT_EXPIRED_NO_POE",
11293
+ description: "Certificate has expired and there is no valid timestamp to prove signature was made when certificate was valid",
11294
+ });
11295
+ }
11296
+ else if (certResult.revocation?.status === "revoked") {
11297
+ status = "INVALID";
11298
+ statusMessage = "Certificate has been revoked";
11299
+ }
11300
+ else {
11301
+ status = "INDETERMINATE";
11302
+ statusMessage = certResult.reason || "Certificate validation inconclusive";
11303
+ }
11304
+ }
11305
+ // Timestamp parsing/verification failed - can't establish POE
11306
+ else if (timestampResult && !timestampResult.isValid) {
11307
+ status = "INDETERMINATE";
11308
+ statusMessage = timestampResult.reason || "Timestamp verification failed";
11309
+ limitations.push({
11310
+ code: "TIMESTAMP_VERIFICATION_FAILED",
11311
+ description: timestampResult.reason || "Could not verify timestamp to establish proof of existence",
11312
+ });
11313
+ }
11314
+ // Revocation unknown
11315
+ else if (certResult.revocation?.status === "unknown") {
11316
+ status = "INDETERMINATE";
11317
+ statusMessage = "Certificate revocation status could not be determined";
11318
+ limitations.push({
11319
+ code: "REVOCATION_UNKNOWN",
11320
+ description: certResult.revocation.reason || "Could not check certificate revocation status",
11321
+ });
11322
+ }
11323
+ // Fallback
11324
+ else {
11325
+ status = "INVALID";
11326
+ statusMessage = errors[0] || "Verification failed";
11327
+ }
11328
+ }
10855
11329
  // Return the complete result
10856
11330
  return {
10857
11331
  isValid,
11332
+ status,
11333
+ statusMessage,
11334
+ limitations: limitations.length > 0 ? limitations : undefined,
10858
11335
  certificate: certResult,
10859
11336
  checksums: checksumResult,
10860
11337
  signature: options.verifySignatures !== false ? signatureResult : undefined,