edockit 0.1.1-beta.1 → 0.1.2

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 CHANGED
@@ -8,7 +8,7 @@ A JavaScript/TypeScript library for viewing and verifying EU standard ASiC-E con
8
8
 
9
9
  ## About
10
10
 
11
- This library supports standard European ASiC-E (.asice) containers as defined by the ETSI standards. Latvian eDoc (.edoc) files are effectively ASiC-E containers with a different file extension, so they are also supported. While the core functionality exists, extensive testing with real-world documents from various EU countries is still needed to ensure complete compatibility across different implementations.
11
+ This library supports standard European ASiC-E (.asice, .sce) containers as defined by the ETSI standards. Latvian eDoc (.edoc) files are effectively ASiC-E containers with a different file extension, so they are also supported. While the core functionality exists, extensive testing with real-world documents from various EU countries is still needed to ensure complete compatibility across different implementations.
12
12
 
13
13
  ## Installation
14
14
 
@@ -16,6 +16,7 @@ export interface SignatureInfo {
16
16
  rawData: ArrayBuffer;
17
17
  };
18
18
  signedChecksums: Record<string, string>;
19
+ digestAlgorithms?: Record<string, string>;
19
20
  signerInfo?: {
20
21
  commonName?: string;
21
22
  organization?: string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Fixes ASN.1 DER encoding of RSA keys to be compatible with browser Web Crypto
3
+ *
4
+ * This specifically targets the issue with modulus padding in SPKI format RSA keys.
5
+ * In DER encoding, when the high bit of an INTEGER is set, a 0x00 byte must be
6
+ * prepended to distinguish it from a negative number. Some libraries omit this padding,
7
+ * which works in Node.js but causes browsers to reject the key.
8
+ */
9
+ /**
10
+ * Fixes ASN.1 DER encoding of RSA modulus to ensure proper padding
11
+ *
12
+ * @param publicKeyData The original public key data as ArrayBuffer
13
+ * @returns Fixed key with proper modulus padding
14
+ */
15
+ export declare function fixRSAModulusPadding(publicKeyData: ArrayBuffer): ArrayBuffer;
@@ -27,6 +27,13 @@ export interface ChecksumVerificationResult {
27
27
  export interface SignatureVerificationResult {
28
28
  isValid: boolean;
29
29
  reason?: string;
30
+ errorDetails?: {
31
+ category: string;
32
+ originalMessage: string;
33
+ algorithm: any;
34
+ environment: string;
35
+ keyLength: number;
36
+ };
30
37
  }
31
38
  /**
32
39
  * Result of a certificate verification
@@ -61,6 +68,7 @@ export declare function computeDigest(fileContent: Uint8Array, algorithm: string
61
68
  */
62
69
  export declare function verifyChecksums(signature: {
63
70
  signedChecksums: Record<string, string>;
71
+ digestAlgorithms?: Record<string, string>;
64
72
  algorithm?: string;
65
73
  }, files: Map<string, Uint8Array>): Promise<ChecksumVerificationResult>;
66
74
  /**
package/dist/index.cjs.js CHANGED
@@ -1,3 +1,7 @@
1
+ /*!
2
+ * MIT License
3
+ * Copyright (c) 2025 Edgars Jēkabsons, ZenomyTech SIA
4
+ */
1
5
  'use strict';
2
6
 
3
7
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -1246,6 +1250,7 @@ function parseSignatureElement(signatureElement, xmlDoc) {
1246
1250
  // Get references and checksums
1247
1251
  const references = [];
1248
1252
  const signedChecksums = {};
1253
+ const digestAlgorithms = {};
1249
1254
  const referenceElements = querySelectorAll(signedInfo, "ds\\:Reference, Reference");
1250
1255
  for (const reference of referenceElements) {
1251
1256
  const uri = reference.getAttribute("URI") || "";
@@ -1265,6 +1270,14 @@ function parseSignatureElement(signatureElement, xmlDoc) {
1265
1270
  // Clean up URI
1266
1271
  const cleanUri = decodedUri.startsWith("./") ? decodedUri.substring(2) : decodedUri;
1267
1272
  references.push(cleanUri);
1273
+ // Find DigestMethod algorithm
1274
+ const digestMethodEl = querySelector(reference, "ds\\:DigestMethod, DigestMethod");
1275
+ if (digestMethodEl) {
1276
+ const algo = digestMethodEl.getAttribute("Algorithm");
1277
+ if (algo) {
1278
+ digestAlgorithms[cleanUri] = algo;
1279
+ }
1280
+ }
1268
1281
  // Find DigestValue
1269
1282
  const digestValueEl = querySelector(reference, "ds\\:DigestValue, DigestValue");
1270
1283
  if (digestValueEl && digestValueEl.textContent) {
@@ -1279,6 +1292,7 @@ function parseSignatureElement(signatureElement, xmlDoc) {
1279
1292
  publicKey,
1280
1293
  signerInfo,
1281
1294
  signedChecksums,
1295
+ digestAlgorithms,
1282
1296
  references,
1283
1297
  algorithm: signatureAlgorithm,
1284
1298
  signatureValue,
@@ -1407,6 +1421,206 @@ function parseEdoc(edocBuffer) {
1407
1421
  }
1408
1422
  }
1409
1423
 
1424
+ /**
1425
+ * Fixes ASN.1 DER encoding of RSA keys to be compatible with browser Web Crypto
1426
+ *
1427
+ * This specifically targets the issue with modulus padding in SPKI format RSA keys.
1428
+ * In DER encoding, when the high bit of an INTEGER is set, a 0x00 byte must be
1429
+ * prepended to distinguish it from a negative number. Some libraries omit this padding,
1430
+ * which works in Node.js but causes browsers to reject the key.
1431
+ */
1432
+ /**
1433
+ * Fixes ASN.1 DER encoding of RSA modulus to ensure proper padding
1434
+ *
1435
+ * @param publicKeyData The original public key data as ArrayBuffer
1436
+ * @returns Fixed key with proper modulus padding
1437
+ */
1438
+ function fixRSAModulusPadding(publicKeyData) {
1439
+ const log = () => { };
1440
+ const keyBytes = new Uint8Array(publicKeyData);
1441
+ // Check if we have a valid SPKI format RSA key
1442
+ // It should start with SEQUENCE tag (0x30)
1443
+ if (keyBytes[0] !== 0x30) {
1444
+ return publicKeyData;
1445
+ }
1446
+ // Look for RSA OID to confirm it's an RSA key
1447
+ const RSA_OID = [0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
1448
+ let oidPosition = -1;
1449
+ for (let i = 0; i <= keyBytes.length - RSA_OID.length; i++) {
1450
+ let match = true;
1451
+ for (let j = 0; j < RSA_OID.length; j++) {
1452
+ if (keyBytes[i + j] !== RSA_OID[j]) {
1453
+ match = false;
1454
+ break;
1455
+ }
1456
+ }
1457
+ if (match) {
1458
+ oidPosition = i;
1459
+ break;
1460
+ }
1461
+ }
1462
+ if (oidPosition === -1) {
1463
+ return publicKeyData;
1464
+ }
1465
+ // Find the BitString that contains the key
1466
+ let bitStringPosition = -1;
1467
+ for (let i = oidPosition + RSA_OID.length; i < keyBytes.length; i++) {
1468
+ if (keyBytes[i] === 0x03) {
1469
+ // BIT STRING tag
1470
+ bitStringPosition = i;
1471
+ break;
1472
+ }
1473
+ }
1474
+ if (bitStringPosition === -1) {
1475
+ return publicKeyData;
1476
+ }
1477
+ // Skip BIT STRING tag and length bytes to find unused bits byte
1478
+ let bitStringLengthBytes = 0;
1479
+ if ((keyBytes[bitStringPosition + 1] & 0x80) === 0) {
1480
+ // Short form length
1481
+ bitStringLengthBytes = 1;
1482
+ }
1483
+ else {
1484
+ // Long form length
1485
+ bitStringLengthBytes = 1 + (keyBytes[bitStringPosition + 1] & 0x7f);
1486
+ }
1487
+ // The unused bits byte follows the length bytes
1488
+ const unusedBitsPosition = bitStringPosition + 1 + bitStringLengthBytes;
1489
+ if (unusedBitsPosition >= keyBytes.length) {
1490
+ return publicKeyData;
1491
+ }
1492
+ // The inner SEQUENCE (RSA key) should follow the unused bits byte
1493
+ const innerSequencePosition = unusedBitsPosition + 1;
1494
+ if (innerSequencePosition >= keyBytes.length || keyBytes[innerSequencePosition] !== 0x30) {
1495
+ return publicKeyData;
1496
+ }
1497
+ // Skip the inner SEQUENCE tag and length bytes to find the modulus
1498
+ let innerSequenceLengthBytes = 0;
1499
+ if ((keyBytes[innerSequencePosition + 1] & 0x80) === 0) {
1500
+ // Short form length
1501
+ innerSequenceLengthBytes = 1;
1502
+ }
1503
+ else {
1504
+ // Long form length
1505
+ innerSequenceLengthBytes = 1 + (keyBytes[innerSequencePosition + 1] & 0x7f);
1506
+ }
1507
+ // The modulus should be an INTEGER (tag 0x02) after the inner SEQUENCE
1508
+ const modulusPosition = innerSequencePosition + 1 + innerSequenceLengthBytes;
1509
+ if (modulusPosition >= keyBytes.length || keyBytes[modulusPosition] !== 0x02) {
1510
+ return publicKeyData;
1511
+ }
1512
+ // Skip the INTEGER tag and parse its length to find the modulus value
1513
+ let modulusLengthBytes = 0;
1514
+ let modulusLength = 0;
1515
+ if ((keyBytes[modulusPosition + 1] & 0x80) === 0) {
1516
+ // Short form length
1517
+ modulusLength = keyBytes[modulusPosition + 1];
1518
+ modulusLengthBytes = 1;
1519
+ }
1520
+ else {
1521
+ // Long form length
1522
+ const numLengthBytes = keyBytes[modulusPosition + 1] & 0x7f;
1523
+ modulusLengthBytes = 1 + numLengthBytes;
1524
+ // Calculate multi-byte length
1525
+ modulusLength = 0;
1526
+ for (let i = 0; i < numLengthBytes; i++) {
1527
+ modulusLength = (modulusLength << 8) | keyBytes[modulusPosition + 2 + i];
1528
+ }
1529
+ }
1530
+ // The first byte of the modulus value
1531
+ const modulusValuePosition = modulusPosition + 1 + modulusLengthBytes;
1532
+ if (modulusValuePosition >= keyBytes.length) {
1533
+ return publicKeyData;
1534
+ }
1535
+ // Check if the high bit is set and padding is needed
1536
+ if ((keyBytes[modulusValuePosition] & 0x80) !== 0) {
1537
+ // Create a new key buffer with room for the padding byte
1538
+ const fixedKey = new Uint8Array(keyBytes.length + 1);
1539
+ // Copy bytes up to the modulus value
1540
+ fixedKey.set(keyBytes.slice(0, modulusValuePosition));
1541
+ // Add the padding byte
1542
+ fixedKey[modulusValuePosition] = 0x00;
1543
+ // Copy the rest of the original key after the padding
1544
+ fixedKey.set(keyBytes.slice(modulusValuePosition), modulusValuePosition + 1);
1545
+ // Now fix all the length fields that need to be incremented
1546
+ // 1. Fix modulus length field
1547
+ if ((keyBytes[modulusPosition + 1] & 0x80) === 0) {
1548
+ // Short form
1549
+ fixedKey[modulusPosition + 1] = keyBytes[modulusPosition + 1] + 1;
1550
+ }
1551
+ else {
1552
+ // Long form
1553
+ const numLengthBytes = keyBytes[modulusPosition + 1] & 0x7f;
1554
+ let lengthValue = 0;
1555
+ for (let i = 0; i < numLengthBytes; i++) {
1556
+ lengthValue = (lengthValue << 8) | keyBytes[modulusPosition + 2 + i];
1557
+ }
1558
+ lengthValue += 1;
1559
+ for (let i = numLengthBytes - 1; i >= 0; i--) {
1560
+ fixedKey[modulusPosition + 2 + i] = lengthValue & 0xff;
1561
+ lengthValue >>= 8;
1562
+ }
1563
+ }
1564
+ // 2. Fix inner SEQUENCE length field
1565
+ if ((keyBytes[innerSequencePosition + 1] & 0x80) === 0) {
1566
+ // Short form
1567
+ fixedKey[innerSequencePosition + 1] = keyBytes[innerSequencePosition + 1] + 1;
1568
+ }
1569
+ else {
1570
+ // Long form
1571
+ const numLengthBytes = keyBytes[innerSequencePosition + 1] & 0x7f;
1572
+ let lengthValue = 0;
1573
+ for (let i = 0; i < numLengthBytes; i++) {
1574
+ lengthValue = (lengthValue << 8) | keyBytes[innerSequencePosition + 2 + i];
1575
+ }
1576
+ lengthValue += 1;
1577
+ for (let i = numLengthBytes - 1; i >= 0; i--) {
1578
+ fixedKey[innerSequencePosition + 2 + i] = lengthValue & 0xff;
1579
+ lengthValue >>= 8;
1580
+ }
1581
+ }
1582
+ // 3. Fix BIT STRING length field
1583
+ if ((keyBytes[bitStringPosition + 1] & 0x80) === 0) {
1584
+ // Short form
1585
+ fixedKey[bitStringPosition + 1] = keyBytes[bitStringPosition + 1] + 1;
1586
+ }
1587
+ else {
1588
+ // Long form
1589
+ const numLengthBytes = keyBytes[bitStringPosition + 1] & 0x7f;
1590
+ let lengthValue = 0;
1591
+ for (let i = 0; i < numLengthBytes; i++) {
1592
+ lengthValue = (lengthValue << 8) | keyBytes[bitStringPosition + 2 + i];
1593
+ }
1594
+ lengthValue += 1;
1595
+ for (let i = numLengthBytes - 1; i >= 0; i--) {
1596
+ fixedKey[bitStringPosition + 2 + i] = lengthValue & 0xff;
1597
+ lengthValue >>= 8;
1598
+ }
1599
+ }
1600
+ // 4. Fix outer SEQUENCE length field
1601
+ if ((keyBytes[1] & 0x80) === 0) {
1602
+ // Short form
1603
+ fixedKey[1] = keyBytes[1] + 1;
1604
+ }
1605
+ else {
1606
+ // Long form
1607
+ const numLengthBytes = keyBytes[1] & 0x7f;
1608
+ let lengthValue = 0;
1609
+ for (let i = 0; i < numLengthBytes; i++) {
1610
+ lengthValue = (lengthValue << 8) | keyBytes[1 + 1 + i];
1611
+ }
1612
+ lengthValue += 1;
1613
+ for (let i = numLengthBytes - 1; i >= 0; i--) {
1614
+ fixedKey[1 + 1 + i] = lengthValue & 0xff;
1615
+ lengthValue >>= 8;
1616
+ }
1617
+ }
1618
+ log("Fixed key length: " + fixedKey.length);
1619
+ return fixedKey.buffer;
1620
+ }
1621
+ return publicKeyData;
1622
+ }
1623
+
1410
1624
  /**
1411
1625
  * Detects if code is running in a browser environment
1412
1626
  * @returns true if in browser, false otherwise
@@ -1493,6 +1707,23 @@ function nodeDigest(fileContent, hashAlgo) {
1493
1707
  }
1494
1708
  });
1495
1709
  }
1710
+ /**
1711
+ * Parse digest algorithm URI to normalized algorithm name
1712
+ * @param algorithmUri The algorithm URI (e.g., http://www.w3.org/2001/04/xmlenc#sha256)
1713
+ * @returns Normalized algorithm name (e.g., SHA-256)
1714
+ */
1715
+ function parseDigestAlgorithmUri(algorithmUri) {
1716
+ const uri = algorithmUri.toLowerCase();
1717
+ if (uri.includes("sha512"))
1718
+ return "SHA-512";
1719
+ if (uri.includes("sha384"))
1720
+ return "SHA-384";
1721
+ if (uri.includes("sha256"))
1722
+ return "SHA-256";
1723
+ if (uri.includes("sha1"))
1724
+ return "SHA-1";
1725
+ return "SHA-256"; // Default fallback
1726
+ }
1496
1727
  /**
1497
1728
  * Verify checksums of files against signature
1498
1729
  * @param signature The signature information
@@ -1502,20 +1733,24 @@ function nodeDigest(fileContent, hashAlgo) {
1502
1733
  async function verifyChecksums(signature, files) {
1503
1734
  const results = {};
1504
1735
  let allValid = true;
1505
- // Determine hash algorithm from signature algorithm or use default
1506
- let digestAlgorithm = "SHA-256";
1736
+ // Default digest algorithm from signature algorithm (fallback if per-file not available)
1737
+ let defaultDigestAlgorithm = "SHA-256";
1507
1738
  if (signature.algorithm) {
1508
1739
  if (signature.algorithm.includes("sha1")) {
1509
- digestAlgorithm = "SHA-1";
1740
+ defaultDigestAlgorithm = "SHA-1";
1510
1741
  }
1511
1742
  else if (signature.algorithm.includes("sha384")) {
1512
- digestAlgorithm = "SHA-384";
1743
+ defaultDigestAlgorithm = "SHA-384";
1513
1744
  }
1514
1745
  else if (signature.algorithm.includes("sha512")) {
1515
- digestAlgorithm = "SHA-512";
1746
+ defaultDigestAlgorithm = "SHA-512";
1516
1747
  }
1517
1748
  }
1518
1749
  const checksumPromises = Object.entries(signature.signedChecksums).map(async ([filename, expectedChecksum]) => {
1750
+ // Get the per-file digest algorithm, or fall back to default
1751
+ const digestAlgorithm = signature.digestAlgorithms?.[filename]
1752
+ ? parseDigestAlgorithmUri(signature.digestAlgorithms[filename])
1753
+ : defaultDigestAlgorithm;
1519
1754
  // Check if file exists in the container
1520
1755
  const fileContent = files.get(filename);
1521
1756
  if (!fileContent) {
@@ -1675,12 +1910,50 @@ async function verifySignedInfo(signatureXml, signatureValue, publicKeyData, alg
1675
1910
  let publicKey;
1676
1911
  try {
1677
1912
  const subtle = getCryptoSubtle();
1913
+ if (isBrowser() && algorithm.name === "RSASSA-PKCS1-v1_5") {
1914
+ // console.log("Browser environment detected, applying RSA key fix");
1915
+ publicKeyData = fixRSAModulusPadding(publicKeyData);
1916
+ }
1678
1917
  publicKey = await subtle.importKey("spki", publicKeyData, algorithm, false, ["verify"]);
1679
1918
  }
1680
- catch (error) {
1919
+ catch (unknownError) {
1920
+ // First cast to Error type if applicable
1921
+ const error = unknownError instanceof Error ? unknownError : new Error(String(unknownError));
1922
+ // Determine detailed error reason
1923
+ let detailedReason = "Unknown reason";
1924
+ let errorCategory = "KEY_IMPORT_ERROR";
1925
+ // Categorize the error
1926
+ if (error.name === "DataError") {
1927
+ detailedReason = "Key data format is invalid or incompatible";
1928
+ errorCategory = "INVALID_KEY_FORMAT";
1929
+ }
1930
+ else if (error.name === "NotSupportedError") {
1931
+ detailedReason = "Algorithm or parameters not supported";
1932
+ errorCategory = "UNSUPPORTED_ALGORITHM";
1933
+ }
1934
+ else if (error.message.includes("namedCurve")) {
1935
+ detailedReason = "Missing or invalid namedCurve parameter";
1936
+ errorCategory = "INVALID_CURVE";
1937
+ }
1938
+ else if (error.message.includes("hash")) {
1939
+ detailedReason = "Incompatible or unsupported hash algorithm";
1940
+ errorCategory = "INVALID_HASH";
1941
+ }
1942
+ // Add ECDSA-specific diagnostics
1943
+ if (algorithm.name === "ECDSA") {
1944
+ const keyLength = publicKeyData.byteLength;
1945
+ detailedReason += ` (Key length: ${keyLength})`;
1946
+ }
1681
1947
  return {
1682
1948
  isValid: false,
1683
- reason: `Failed to import public key: ${error instanceof Error ? error.message : String(error)}`,
1949
+ reason: `Failed to import public key: ${detailedReason}`,
1950
+ errorDetails: {
1951
+ category: errorCategory,
1952
+ originalMessage: error.message,
1953
+ algorithm: { ...algorithm },
1954
+ environment: isBrowser() ? "browser" : "node",
1955
+ keyLength: publicKeyData.byteLength,
1956
+ },
1684
1957
  };
1685
1958
  }
1686
1959
  // Verify the signature
@@ -1718,10 +1991,23 @@ async function verifySignature(signatureInfo, files, options = {}) {
1718
1991
  const errors = [];
1719
1992
  // Verify certificate
1720
1993
  const certResult = await verifyCertificate(signatureInfo.certificatePEM, options.verifyTime || signatureInfo.signingTime);
1994
+ // If certificate validation failed, add detailed error
1995
+ if (!certResult.isValid) {
1996
+ const certErrorMsg = `Certificate validation error: ${certResult.reason || "Unknown reason"}`;
1997
+ errors.push(certErrorMsg);
1998
+ }
1721
1999
  // Verify checksums
1722
2000
  const checksumResult = options.verifyChecksums !== false
1723
2001
  ? await verifyChecksums(signatureInfo, files)
1724
2002
  : { isValid: true, details: {} };
2003
+ // If checksum validation failed, add detailed error
2004
+ if (!checksumResult.isValid) {
2005
+ const failedChecksums = Object.entries(checksumResult.details)
2006
+ .filter(([_, details]) => !details.matches)
2007
+ .map(([filename]) => filename)
2008
+ .join(", ");
2009
+ errors.push(`Checksum validation failed for files: ${failedChecksums}`);
2010
+ }
1725
2011
  // Verify XML signature if we have the necessary components
1726
2012
  let signatureResult = { isValid: true };
1727
2013
  if (options.verifySignatures !== false &&
@@ -1734,19 +2020,64 @@ async function verifySignature(signatureInfo, files, options = {}) {
1734
2020
  name: "RSASSA-PKCS1-v1_5",
1735
2021
  hash: "SHA-256",
1736
2022
  };
1737
- if (algorithm.includes("ecdsa-sha256")) {
2023
+ if (algorithm.includes("ecdsa") && signatureInfo.publicKey.namedCurve) {
2024
+ keyAlgorithm.namedCurve = signatureInfo.publicKey.namedCurve;
1738
2025
  keyAlgorithm.name = "ECDSA";
2026
+ }
2027
+ if (algorithm.includes("ecdsa-sha256")) {
1739
2028
  keyAlgorithm.hash = "SHA-256";
1740
- if (signatureInfo.publicKey.namedCurve) {
1741
- keyAlgorithm.namedCurve = signatureInfo.publicKey.namedCurve;
1742
- }
2029
+ }
2030
+ else if (algorithm.includes("ecdsa-sha384")) {
2031
+ keyAlgorithm.hash = "SHA-384";
2032
+ }
2033
+ else if (algorithm.includes("ecdsa-sha512")) {
2034
+ keyAlgorithm.hash = "SHA-512";
1743
2035
  }
1744
2036
  else if (algorithm.includes("rsa-sha1")) {
1745
2037
  keyAlgorithm.hash = "SHA-1";
1746
2038
  }
2039
+ else if (algorithm.includes("rsa-pss")) {
2040
+ keyAlgorithm.name = "RSA-PSS";
2041
+ keyAlgorithm.saltLength = 32; // Default salt length (adjust based on hash size)
2042
+ if (algorithm.includes("sha384")) {
2043
+ keyAlgorithm.hash = "SHA-384";
2044
+ keyAlgorithm.saltLength = 48;
2045
+ }
2046
+ else if (algorithm.includes("sha512")) {
2047
+ keyAlgorithm.hash = "SHA-512";
2048
+ keyAlgorithm.saltLength = 64;
2049
+ }
2050
+ else {
2051
+ keyAlgorithm.hash = "SHA-256"; // Default
2052
+ }
2053
+ }
2054
+ else if (algorithm.includes("rsa-sha384")) {
2055
+ keyAlgorithm.hash = "SHA-384";
2056
+ }
2057
+ else if (algorithm.includes("rsa-sha512")) {
2058
+ keyAlgorithm.hash = "SHA-512";
2059
+ }
1747
2060
  signatureResult = await verifySignedInfo(signatureInfo.rawXml, signatureInfo.signatureValue, signatureInfo.publicKey.rawData, keyAlgorithm, signatureInfo.canonicalizationMethod);
2061
+ // If signature validation failed, add detailed error
1748
2062
  if (!signatureResult.isValid) {
1749
- errors.push(signatureResult.reason || "XML signature verification failed");
2063
+ // Format a detailed error message with the error details
2064
+ let detailedErrorMessage = signatureResult.reason || "XML signature verification failed";
2065
+ // Add error details if available
2066
+ if (signatureResult.errorDetails) {
2067
+ const details = signatureResult.errorDetails;
2068
+ detailedErrorMessage += ` [Category: ${details.category}, Environment: ${details.environment}`;
2069
+ if (details.algorithm) {
2070
+ detailedErrorMessage += `, Algorithm: ${details.algorithm.name}`;
2071
+ if (details.algorithm.namedCurve) {
2072
+ detailedErrorMessage += `, Curve: ${details.algorithm.namedCurve}`;
2073
+ }
2074
+ }
2075
+ if (details.keyLength) {
2076
+ detailedErrorMessage += `, Key length: ${details.keyLength} bytes`;
2077
+ }
2078
+ detailedErrorMessage += `]`;
2079
+ }
2080
+ errors.push(detailedErrorMessage);
1750
2081
  }
1751
2082
  }
1752
2083
  else if (options.verifySignatures !== false) {