edockit 0.2.3 → 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/CHANGELOG.md +30 -0
- package/README.md +33 -10
- package/dist/core/parser/types.d.ts +1 -0
- package/dist/core/rsa-digestinfo-workaround.d.ts +29 -0
- package/dist/core/timestamp/types.d.ts +2 -2
- package/dist/core/timestamp/verify.d.ts +5 -5
- package/dist/core/verification.d.ts +28 -0
- package/dist/index.cjs.js +692 -65
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +692 -65
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +13 -16
- package/dist/index.umd.js.map +1 -1
- package/dist/utils/encoding.d.ts +1 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-01-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Multi-state validation results** - `VerificationResult` now includes granular status beyond boolean `isValid`:
|
|
13
|
+
- `status`: `"VALID"` | `"INVALID"` | `"INDETERMINATE"` | `"UNSUPPORTED"`
|
|
14
|
+
- `statusMessage`: Human-readable explanation
|
|
15
|
+
- `limitations`: Array describing platform/environment constraints
|
|
16
|
+
- **Platform limitation detection** - Detect unsupported RSA key sizes (>4096 bits) in Safari/WebKit and return `UNSUPPORTED` status instead of failing as `INVALID`
|
|
17
|
+
- **Cross-browser testing** - Added Safari/WebKit and Firefox to browser test suite locally
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **C14N 1.1 canonicalization** - Fixed bug where C14N 1.1 incorrectly added newlines between XML elements when the original had none. This caused signature verification to fail for compact XML.
|
|
22
|
+
- **INDETERMINATE for expired timestamps** - Return `INDETERMINATE` status (instead of `INVALID`) when timestamp or certificate has expired but signature is otherwise valid
|
|
23
|
+
- **Legacy RSA DigestInfo verification** - Fix signature verification for old documents signed with pre-Java 8 tools that produced non-standard DigestInfo format (missing NULL in AlgorithmIdentifier)
|
|
24
|
+
|
|
25
|
+
## [0.2.4] - 2025-12-31
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **OCSP issuerKeyHash calculation** - Fixed critical bug where OCSP requests used wrong hash (full SPKI instead of public key BIT STRING), causing incorrect revocation status responses
|
|
30
|
+
- **Timestamp signature coverage verification** - Now correctly verifies that timestamps cover the canonicalized ds:SignatureValue XML element per XAdES (ETSI EN 319 132-1) specification, fixing `coversSignature: false` issue
|
|
31
|
+
- **TSA name formatting** - Fixed timestamp TSA name showing as `[object Object]` instead of readable DN string like `CN=..., O=..., C=...`
|
|
32
|
+
- **Base64 whitespace handling** - Fixed browser `atob` errors when decoding base64 strings containing whitespace from XML
|
|
33
|
+
- **ECDSA signature format normalization** - Fixed signature verification failures for ECDSA signatures with leading zero padding by normalizing to IEEE P1363 format expected by Web Crypto API
|
|
34
|
+
|
|
8
35
|
## [0.2.3] - 2025-12-30
|
|
9
36
|
|
|
10
37
|
### Fixed
|
|
@@ -60,6 +87,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
87
|
- File checksum verification (SHA-256/384/512)
|
|
61
88
|
- Browser and Node.js support
|
|
62
89
|
|
|
90
|
+
[Unreleased]: https://github.com/edgarsj/edockit/compare/v0.3.0...HEAD
|
|
91
|
+
[0.3.0]: https://github.com/edgarsj/edockit/compare/v0.2.4...v0.3.0
|
|
92
|
+
[0.2.4]: https://github.com/edgarsj/edockit/compare/v0.2.3...v0.2.4
|
|
63
93
|
[0.2.3]: https://github.com/edgarsj/edockit/compare/v0.2.2...v0.2.3
|
|
64
94
|
[0.2.2]: https://github.com/edgarsj/edockit/compare/v0.2.1...v0.2.2
|
|
65
95
|
[0.2.1]: https://github.com/edgarsj/edockit/compare/v0.2.0...v0.2.1
|
package/README.md
CHANGED
|
@@ -51,7 +51,14 @@ const result = await verifySignature(container.signatures[0], container.files, {
|
|
|
51
51
|
verifyTime: new Date() // Verify certificate at specific time (default: timestamp time if present, otherwise now)
|
|
52
52
|
});
|
|
53
53
|
// result = {
|
|
54
|
-
// isValid: boolean, // Overall validity
|
|
54
|
+
// isValid: boolean, // Overall validity (for backwards compatibility)
|
|
55
|
+
// status: 'VALID' | 'INVALID' | 'INDETERMINATE' | 'UNSUPPORTED', // Granular status
|
|
56
|
+
// statusMessage?: string, // Human-readable explanation
|
|
57
|
+
// limitations?: [{ // Platform/environment constraints (if any)
|
|
58
|
+
// code: string, // e.g., 'RSA_KEY_SIZE_UNSUPPORTED'
|
|
59
|
+
// description: string,
|
|
60
|
+
// platform?: string // e.g., 'Safari/WebKit'
|
|
61
|
+
// }],
|
|
55
62
|
// certificate: {
|
|
56
63
|
// isValid: boolean, // Certificate validity (time-based)
|
|
57
64
|
// revocation: { // Revocation check result
|
|
@@ -90,7 +97,22 @@ for (const signature of container.signatures) {
|
|
|
90
97
|
checkRevocation: true,
|
|
91
98
|
verifyTimestamps: true
|
|
92
99
|
});
|
|
93
|
-
|
|
100
|
+
|
|
101
|
+
// Use granular status for detailed handling
|
|
102
|
+
console.log(`Status: ${result.status}`); // VALID, INVALID, INDETERMINATE, or UNSUPPORTED
|
|
103
|
+
|
|
104
|
+
if (result.status === 'VALID') {
|
|
105
|
+
console.log('Signature is valid');
|
|
106
|
+
} else if (result.status === 'UNSUPPORTED') {
|
|
107
|
+
// Platform limitation (e.g., RSA >4096 bits in Safari)
|
|
108
|
+
console.log(`Cannot verify: ${result.statusMessage}`);
|
|
109
|
+
} else if (result.status === 'INDETERMINATE') {
|
|
110
|
+
// Can't conclude (e.g., revocation status unknown)
|
|
111
|
+
console.log(`Inconclusive: ${result.statusMessage}`);
|
|
112
|
+
} else {
|
|
113
|
+
// INVALID - definitely wrong
|
|
114
|
+
console.log(`Invalid: ${result.statusMessage}`);
|
|
115
|
+
}
|
|
94
116
|
|
|
95
117
|
if (result.timestamp?.info) {
|
|
96
118
|
console.log(`Signed at (TSA): ${result.timestamp.info.genTime}`);
|
|
@@ -99,10 +121,6 @@ for (const signature of container.signatures) {
|
|
|
99
121
|
if (result.certificate.revocation) {
|
|
100
122
|
console.log(`Revocation status: ${result.certificate.revocation.status}`);
|
|
101
123
|
}
|
|
102
|
-
|
|
103
|
-
if (!result.isValid && result.errors) {
|
|
104
|
-
console.log(`Errors: ${result.errors.join(', ')}`);
|
|
105
|
-
}
|
|
106
124
|
}
|
|
107
125
|
```
|
|
108
126
|
|
|
@@ -128,14 +146,19 @@ async function verifyDocument(url) {
|
|
|
128
146
|
},
|
|
129
147
|
});
|
|
130
148
|
|
|
131
|
-
|
|
149
|
+
// Handle different validation states
|
|
150
|
+
if (result.status === 'VALID') {
|
|
151
|
+
console.log('Signature verified successfully');
|
|
152
|
+
} else if (result.status === 'UNSUPPORTED') {
|
|
153
|
+
// Some signatures can't be verified in certain browsers (e.g., RSA >4096 in Safari)
|
|
154
|
+
console.log(`Browser limitation: ${result.statusMessage}`);
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`${result.status}: ${result.statusMessage}`);
|
|
157
|
+
}
|
|
132
158
|
|
|
133
159
|
if (result.timestamp?.info) {
|
|
134
160
|
console.log(`Timestamp: ${result.timestamp.info.genTime}`);
|
|
135
161
|
}
|
|
136
|
-
if (result.certificate.revocation) {
|
|
137
|
-
console.log(`Revocation: ${result.certificate.revocation.status}`);
|
|
138
|
-
}
|
|
139
162
|
}
|
|
140
163
|
}
|
|
141
164
|
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RSA DigestInfo Workaround
|
|
3
|
+
*
|
|
4
|
+
* Some older signing tools (particularly pre-Java 8) produced RSA signatures with
|
|
5
|
+
* non-standard DigestInfo format - missing the NULL parameter in AlgorithmIdentifier.
|
|
6
|
+
*
|
|
7
|
+
* Standard DigestInfo for SHA-1: 30 21 30 09 06 05 2b0e03021a 05 00 04 14 [hash]
|
|
8
|
+
* Non-standard (missing NULL): 30 1f 30 07 06 05 2b0e03021a 04 14 [hash]
|
|
9
|
+
*
|
|
10
|
+
* Web Crypto API's subtle.verify() is strict and rejects the non-standard format.
|
|
11
|
+
* This module provides a fallback that manually performs RSA verification using
|
|
12
|
+
* BigInt math, which works in both browser and Node.js environments.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Verify RSA signature with non-standard DigestInfo format.
|
|
16
|
+
*
|
|
17
|
+
* This function performs RSA signature verification that tolerates
|
|
18
|
+
* non-standard DigestInfo formats (missing NULL in AlgorithmIdentifier).
|
|
19
|
+
*
|
|
20
|
+
* - Node.js: Uses native crypto.publicDecrypt() for speed
|
|
21
|
+
* - Browser: Uses BigInt math (Web Crypto doesn't expose raw RSA)
|
|
22
|
+
*
|
|
23
|
+
* @param publicKeyData SPKI-formatted public key
|
|
24
|
+
* @param signatureBytes Raw signature bytes
|
|
25
|
+
* @param dataToVerify The data that was signed
|
|
26
|
+
* @param hashAlgorithm Hash algorithm name (e.g., "SHA-1", "SHA-256")
|
|
27
|
+
* @returns true if signature is valid, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
export declare function verifyRsaWithNonStandardDigestInfo(publicKeyData: ArrayBuffer, signatureBytes: Uint8Array, dataToVerify: Uint8Array, hashAlgorithm: string): Promise<boolean>;
|
|
@@ -39,8 +39,8 @@ export interface TimestampVerificationResult {
|
|
|
39
39
|
* Options for timestamp verification
|
|
40
40
|
*/
|
|
41
41
|
export interface TimestampVerificationOptions {
|
|
42
|
-
/** The
|
|
43
|
-
|
|
42
|
+
/** The canonicalized ds:SignatureValue XML element (per XAdES spec) */
|
|
43
|
+
canonicalSignatureValue?: string;
|
|
44
44
|
/** Verify the TSA certificate chain */
|
|
45
45
|
verifyTsaCertificate?: boolean;
|
|
46
46
|
/** Check TSA certificate revocation */
|
|
@@ -7,15 +7,15 @@ import { TimestampInfo, TimestampVerificationResult, TimestampVerificationOption
|
|
|
7
7
|
export declare function parseTimestamp(timestampBase64: string): TimestampInfo | null;
|
|
8
8
|
/**
|
|
9
9
|
* Verify that timestamp covers the signature value
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* Per XAdES (ETSI EN 319 132-1), the SignatureTimeStamp covers the canonicalized
|
|
12
|
+
* ds:SignatureValue XML element, not just its base64 content.
|
|
13
13
|
*
|
|
14
14
|
* @param timestampInfo Parsed timestamp info
|
|
15
|
-
* @param
|
|
15
|
+
* @param canonicalSignatureValue Canonicalized ds:SignatureValue XML element
|
|
16
16
|
* @returns True if the timestamp covers the signature
|
|
17
17
|
*/
|
|
18
|
-
export declare function verifyTimestampCoversSignature(timestampInfo: TimestampInfo,
|
|
18
|
+
export declare function verifyTimestampCoversSignature(timestampInfo: TimestampInfo, canonicalSignatureValue: string): Promise<boolean>;
|
|
19
19
|
/**
|
|
20
20
|
* Verify an RFC 3161 timestamp token
|
|
21
21
|
* @param timestampBase64 Base64-encoded timestamp token
|
|
@@ -35,6 +35,8 @@ export interface ChecksumVerificationResult {
|
|
|
35
35
|
export interface SignatureVerificationResult {
|
|
36
36
|
isValid: boolean;
|
|
37
37
|
reason?: string;
|
|
38
|
+
/** True if verification failed due to platform limitation (e.g., RSA >4096 in Safari) */
|
|
39
|
+
unsupportedPlatform?: boolean;
|
|
38
40
|
errorDetails?: {
|
|
39
41
|
category: string;
|
|
40
42
|
originalMessage: string;
|
|
@@ -53,11 +55,37 @@ export interface CertificateVerificationResult {
|
|
|
53
55
|
/** Revocation check result (if checkRevocation was enabled) */
|
|
54
56
|
revocation?: RevocationResult;
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Validation status for granular verification results
|
|
60
|
+
* - VALID: Signature cryptographically valid, all checks pass
|
|
61
|
+
* - INVALID: Definitely wrong (bad checksum, tampered content, crypto failure with supported key)
|
|
62
|
+
* - INDETERMINATE: Can't conclude (expired cert without POE, missing chain, revocation unknown)
|
|
63
|
+
* - UNSUPPORTED: Platform can't verify (e.g., RSA >4096 bits in Safari/WebKit)
|
|
64
|
+
*/
|
|
65
|
+
export type ValidationStatus = "VALID" | "INVALID" | "INDETERMINATE" | "UNSUPPORTED";
|
|
66
|
+
/**
|
|
67
|
+
* Describes a limitation that prevented full verification
|
|
68
|
+
*/
|
|
69
|
+
export interface ValidationLimitation {
|
|
70
|
+
/** Machine-readable code (e.g., 'RSA_KEY_SIZE_UNSUPPORTED', 'CERT_EXPIRED_NO_POE') */
|
|
71
|
+
code: string;
|
|
72
|
+
/** Human-readable description */
|
|
73
|
+
description: string;
|
|
74
|
+
/** Platform where this limitation applies (e.g., 'Safari/WebKit') */
|
|
75
|
+
platform?: string;
|
|
76
|
+
}
|
|
56
77
|
/**
|
|
57
78
|
* Complete verification result
|
|
58
79
|
*/
|
|
59
80
|
export interface VerificationResult {
|
|
81
|
+
/** Whether the signature is valid (for backwards compatibility) */
|
|
60
82
|
isValid: boolean;
|
|
83
|
+
/** Granular validation status */
|
|
84
|
+
status: ValidationStatus;
|
|
85
|
+
/** Human-readable status explanation */
|
|
86
|
+
statusMessage?: string;
|
|
87
|
+
/** Limitations that prevented full verification (for INDETERMINATE/UNSUPPORTED) */
|
|
88
|
+
limitations?: ValidationLimitation[];
|
|
61
89
|
certificate: CertificateVerificationResult;
|
|
62
90
|
checksums: ChecksumVerificationResult;
|
|
63
91
|
signature?: SignatureVerificationResult;
|