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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ 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
+
8
25
  ## [0.2.4] - 2025-12-31
9
26
 
10
27
  ### Fixed
@@ -70,6 +87,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
87
  - File checksum verification (SHA-256/384/512)
71
88
  - Browser and Node.js support
72
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
73
92
  [0.2.4]: https://github.com/edgarsj/edockit/compare/v0.2.3...v0.2.4
74
93
  [0.2.3]: https://github.com/edgarsj/edockit/compare/v0.2.2...v0.2.3
75
94
  [0.2.2]: https://github.com/edgarsj/edockit/compare/v0.2.1...v0.2.2
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
- console.log(`Signature valid: ${result.isValid}`);
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
- console.log(`Valid: ${result.isValid}`);
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>;
@@ -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;