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 +1 -1
- package/dist/core/parser/types.d.ts +1 -0
- package/dist/core/rsa-modulus-padding-fix.d.ts +15 -0
- package/dist/core/verification.d.ts +8 -0
- package/dist/index.cjs.js +343 -12
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +343 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +12 -9
- package/dist/index.umd.js.map +1 -1
- package/package.json +11 -2
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
|
|
|
@@ -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
|
-
//
|
|
1506
|
-
let
|
|
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
|
-
|
|
1740
|
+
defaultDigestAlgorithm = "SHA-1";
|
|
1510
1741
|
}
|
|
1511
1742
|
else if (signature.algorithm.includes("sha384")) {
|
|
1512
|
-
|
|
1743
|
+
defaultDigestAlgorithm = "SHA-384";
|
|
1513
1744
|
}
|
|
1514
1745
|
else if (signature.algorithm.includes("sha512")) {
|
|
1515
|
-
|
|
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 (
|
|
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: ${
|
|
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
|
|
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
|
-
|
|
1741
|
-
|
|
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
|
-
|
|
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) {
|