edockit 0.2.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ 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.2.2] - 2025-12-30
9
+
10
+ ### Fixed
11
+
12
+ - **proxyUrl now works for timestamp revocation** - TSA certificate revocation checks now correctly use the proxy
13
+ - **XPath DOM mismatch error in browser** - Fixed "Node cannot be used in a document other than the one in which it was created" error when parsing XML in browsers
14
+
15
+ ## [0.2.1] - 2025-12-30
16
+
17
+ ### Added
18
+
19
+ - **CORS Proxy Support** - New `proxyUrl` option in `revocationOptions` for browser environments
20
+ - Routes OCSP, CRL, and CA issuer certificate requests through a CORS proxy
21
+ - Enables revocation checking in browsers where direct requests are blocked by CORS
22
+
8
23
  ## [0.2.0] - 2025-12-29
9
24
 
10
25
  ### Added
@@ -39,5 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
39
54
  - File checksum verification (SHA-256/384/512)
40
55
  - Browser and Node.js support
41
56
 
57
+ [0.2.2]: https://github.com/edgarsj/edockit/compare/v0.2.1...v0.2.2
58
+ [0.2.1]: https://github.com/edgarsj/edockit/compare/v0.2.0...v0.2.1
42
59
  [0.2.0]: https://github.com/edgarsj/edockit/compare/v0.1.2...v0.2.0
43
60
  [0.1.2]: https://github.com/edgarsj/edockit/releases/tag/v0.1.2
package/README.md CHANGED
@@ -45,6 +45,7 @@ const result = await verifySignature(container.signatures[0], container.files, {
45
45
  revocationOptions: { // Optional: configure revocation check behavior
46
46
  ocspTimeout: 5000, // OCSP request timeout in ms (default: 5000)
47
47
  crlTimeout: 10000, // CRL fetch timeout in ms (default: 10000)
48
+ proxyUrl: 'https://cors-proxy.example.com/?url=', // CORS proxy for browser (optional)
48
49
  },
49
50
  verifyTimestamps: true, // RFC 3161 timestamp verification (default: true)
50
51
  verifyTime: new Date() // Verify certificate at specific time (default: timestamp time if present, otherwise now)
@@ -119,8 +120,13 @@ async function verifyDocument(url) {
119
120
  console.log("Documents:", container.documentFileList);
120
121
 
121
122
  for (const signature of container.signatures) {
122
- const result = await verifySignature(signature, container.files);
123
- // checkRevocation and verifyTimestamps default to true
123
+ const result = await verifySignature(signature, container.files, {
124
+ checkRevocation: true,
125
+ revocationOptions: {
126
+ // Use a CORS proxy for OCSP/CRL requests in browser environments
127
+ proxyUrl: 'https://your-cors-proxy.example.com/?url=',
128
+ },
129
+ });
124
130
 
125
131
  console.log(`Valid: ${result.isValid}`);
126
132
 
@@ -134,6 +140,8 @@ async function verifyDocument(url) {
134
140
  }
135
141
  ```
136
142
 
143
+ > **Note:** OCSP and CRL endpoints typically don't support CORS, so browser environments need a proxy to perform revocation checks. The `proxyUrl` option routes all revocation requests through the specified proxy, which should accept the original URL as a query parameter.
144
+
137
145
  ### Timestamp Utilities
138
146
 
139
147
  For advanced timestamp handling, you can use the timestamp utilities directly:
@@ -31,4 +31,5 @@ export declare function parseCRL(data: ArrayBuffer): X509Crl | null;
31
31
  */
32
32
  export declare function checkCRL(cert: X509Certificate, options?: {
33
33
  timeout?: number;
34
+ proxyUrl?: string;
34
35
  }): Promise<RevocationResult>;
@@ -13,6 +13,12 @@ export interface FetchOptions {
13
13
  contentType?: string;
14
14
  /** Accept header */
15
15
  accept?: string;
16
+ /**
17
+ * CORS proxy URL for browser environments.
18
+ * When set, the original URL will be URL-encoded and appended to this proxy URL.
19
+ * Example: "https://cors-proxy.example.com/?url="
20
+ */
21
+ proxyUrl?: string;
16
22
  }
17
23
  export interface FetchResult {
18
24
  ok: boolean;
@@ -32,20 +38,23 @@ export declare function fetchBinary(url: string, options?: FetchOptions): Promis
32
38
  * @param url OCSP responder URL
33
39
  * @param request DER-encoded OCSP request
34
40
  * @param timeout Timeout in milliseconds
41
+ * @param proxyUrl Optional CORS proxy URL
35
42
  * @returns FetchResult with OCSP response data
36
43
  */
37
- export declare function fetchOCSP(url: string, request: ArrayBuffer, timeout?: number): Promise<FetchResult>;
44
+ export declare function fetchOCSP(url: string, request: ArrayBuffer, timeout?: number, proxyUrl?: string): Promise<FetchResult>;
38
45
  /**
39
46
  * Fetch CRL from distribution point
40
47
  * @param url CRL distribution point URL
41
48
  * @param timeout Timeout in milliseconds
49
+ * @param proxyUrl Optional CORS proxy URL
42
50
  * @returns FetchResult with CRL data
43
51
  */
44
- export declare function fetchCRL(url: string, timeout?: number): Promise<FetchResult>;
52
+ export declare function fetchCRL(url: string, timeout?: number, proxyUrl?: string): Promise<FetchResult>;
45
53
  /**
46
54
  * Fetch issuer certificate from AIA extension
47
55
  * @param url CA Issuers URL
48
56
  * @param timeout Timeout in milliseconds
57
+ * @param proxyUrl Optional CORS proxy URL
49
58
  * @returns FetchResult with certificate data
50
59
  */
51
- export declare function fetchIssuerCertificate(url: string, timeout?: number): Promise<FetchResult>;
60
+ export declare function fetchIssuerCertificate(url: string, timeout?: number, proxyUrl?: string): Promise<FetchResult>;
@@ -23,9 +23,10 @@ export declare function findIssuerInChain(cert: X509Certificate, chain: string[]
23
23
  * Fetch issuer certificate from AIA extension
24
24
  * @param cert Certificate to fetch issuer for
25
25
  * @param timeout Timeout in ms
26
+ * @param proxyUrl Optional CORS proxy URL
26
27
  * @returns Issuer certificate or null
27
28
  */
28
- export declare function fetchIssuerFromAIA(cert: X509Certificate, timeout?: number): Promise<X509Certificate | null>;
29
+ export declare function fetchIssuerFromAIA(cert: X509Certificate, timeout?: number, proxyUrl?: string): Promise<X509Certificate | null>;
29
30
  /**
30
31
  * Build OCSP request for a certificate
31
32
  * @param cert Certificate to check
@@ -49,4 +50,5 @@ export declare function parseOCSPResponse(responseData: ArrayBuffer): Revocation
49
50
  export declare function checkOCSP(cert: X509Certificate, issuerCert: X509Certificate | null, options?: {
50
51
  timeout?: number;
51
52
  certificateChain?: string[];
53
+ proxyUrl?: string;
52
54
  }): Promise<RevocationResult>;
@@ -29,11 +29,18 @@ export interface RevocationCheckOptions {
29
29
  crlTimeout?: number;
30
30
  /** Certificate chain for finding issuer (PEM strings) */
31
31
  certificateChain?: string[];
32
+ /**
33
+ * CORS proxy URL for browser environments.
34
+ * When set, all OCSP/CRL fetch requests will be routed through this proxy.
35
+ * The original URL will be URL-encoded and appended as a query parameter.
36
+ * Example: "https://cors-proxy.example.com/?url="
37
+ */
38
+ proxyUrl?: string;
32
39
  }
33
40
  /**
34
41
  * Default options for revocation checking
35
42
  */
36
- export declare const DEFAULT_REVOCATION_OPTIONS: Required<Omit<RevocationCheckOptions, "certificateChain">>;
43
+ export declare const DEFAULT_REVOCATION_OPTIONS: Required<Omit<RevocationCheckOptions, "certificateChain" | "proxyUrl">>;
37
44
  /**
38
45
  * OID constants for certificate extensions
39
46
  */
@@ -1,4 +1,4 @@
1
- import { RevocationResult } from "../revocation/types";
1
+ import { RevocationResult, RevocationCheckOptions } from "../revocation/types";
2
2
  /**
3
3
  * Parsed timestamp information from RFC 3161 TimeStampToken
4
4
  */
@@ -45,4 +45,6 @@ export interface TimestampVerificationOptions {
45
45
  verifyTsaCertificate?: boolean;
46
46
  /** Check TSA certificate revocation */
47
47
  checkTsaRevocation?: boolean;
48
+ /** Options for TSA certificate revocation checking (timeouts, proxy, etc.) */
49
+ revocationOptions?: RevocationCheckOptions;
48
50
  }
package/dist/index.cjs.js CHANGED
@@ -111,9 +111,16 @@ function createXMLParser() {
111
111
  function queryByXPath(parent, xpathExpression, namespaces = NAMESPACES) {
112
112
  try {
113
113
  // Browser environment with native XPath
114
- if (typeof document !== "undefined" && document.evaluate) {
114
+ if (typeof document !== "undefined" && typeof document.evaluate === "function") {
115
+ // Use the document that owns the parent node, not the global document
116
+ const ownerDoc = "ownerDocument" in parent ? parent.ownerDocument : parent;
117
+ if (!ownerDoc || typeof ownerDoc.evaluate !== "function") {
118
+ // XMLDocuments from DOMParser don't have evaluate - silently return null
119
+ // (caller should use DOM traversal fallback)
120
+ return null;
121
+ }
115
122
  const nsResolver = createNsResolverForBrowser(namespaces);
116
- const result = document.evaluate(xpathExpression, parent, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
123
+ const result = ownerDoc.evaluate(xpathExpression, parent, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
117
124
  return result.singleNodeValue;
118
125
  }
119
126
  // Node.js environment with xpath module
@@ -161,9 +168,16 @@ function queryByXPath(parent, xpathExpression, namespaces = NAMESPACES) {
161
168
  function queryAllByXPath(parent, xpathExpression, namespaces = NAMESPACES) {
162
169
  try {
163
170
  // Browser environment with native XPath
164
- if (typeof document !== "undefined" && document.evaluate) {
171
+ if (typeof document !== "undefined" && typeof document.evaluate === "function") {
172
+ // Use the document that owns the parent node, not the global document
173
+ const ownerDoc = "ownerDocument" in parent ? parent.ownerDocument : parent;
174
+ if (!ownerDoc || typeof ownerDoc.evaluate !== "function") {
175
+ // XMLDocuments from DOMParser don't have evaluate - silently return empty
176
+ // (caller should use DOM traversal fallback)
177
+ return [];
178
+ }
165
179
  const nsResolver = createNsResolverForBrowser(namespaces);
166
- const result = document.evaluate(xpathExpression, parent, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
180
+ const result = ownerDoc.evaluate(xpathExpression, parent, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
167
181
  const elements = [];
168
182
  for (let i = 0; i < result.snapshotLength; i++) {
169
183
  elements.push(result.snapshotItem(i));
@@ -7926,7 +7940,9 @@ __decorate([
7926
7940
  * @returns FetchResult with binary data or error
7927
7941
  */
7928
7942
  async function fetchBinary(url, options = {}) {
7929
- const { timeout = 10000, method = "GET", body, contentType, accept } = options;
7943
+ const { timeout = 10000, method = "GET", body, contentType, accept, proxyUrl } = options;
7944
+ // Apply proxy URL if provided
7945
+ const fetchUrl = proxyUrl ? `${proxyUrl}${encodeURIComponent(url)}` : url;
7930
7946
  const controller = new AbortController();
7931
7947
  const timeoutId = setTimeout(() => controller.abort(), timeout);
7932
7948
  try {
@@ -7937,7 +7953,7 @@ async function fetchBinary(url, options = {}) {
7937
7953
  if (accept) {
7938
7954
  headers["Accept"] = accept;
7939
7955
  }
7940
- const response = await fetch(url, {
7956
+ const response = await fetch(fetchUrl, {
7941
7957
  method,
7942
7958
  headers,
7943
7959
  body: body ? new Uint8Array(body) : undefined,
@@ -7986,41 +8002,47 @@ async function fetchBinary(url, options = {}) {
7986
8002
  * @param url OCSP responder URL
7987
8003
  * @param request DER-encoded OCSP request
7988
8004
  * @param timeout Timeout in milliseconds
8005
+ * @param proxyUrl Optional CORS proxy URL
7989
8006
  * @returns FetchResult with OCSP response data
7990
8007
  */
7991
- async function fetchOCSP(url, request, timeout = 5000) {
8008
+ async function fetchOCSP(url, request, timeout = 5000, proxyUrl) {
7992
8009
  return fetchBinary(url, {
7993
8010
  method: "POST",
7994
8011
  body: request,
7995
8012
  contentType: "application/ocsp-request",
7996
8013
  accept: "application/ocsp-response",
7997
8014
  timeout,
8015
+ proxyUrl,
7998
8016
  });
7999
8017
  }
8000
8018
  /**
8001
8019
  * Fetch CRL from distribution point
8002
8020
  * @param url CRL distribution point URL
8003
8021
  * @param timeout Timeout in milliseconds
8022
+ * @param proxyUrl Optional CORS proxy URL
8004
8023
  * @returns FetchResult with CRL data
8005
8024
  */
8006
- async function fetchCRL(url, timeout = 10000) {
8025
+ async function fetchCRL(url, timeout = 10000, proxyUrl) {
8007
8026
  return fetchBinary(url, {
8008
8027
  method: "GET",
8009
8028
  accept: "application/pkix-crl",
8010
8029
  timeout,
8030
+ proxyUrl,
8011
8031
  });
8012
8032
  }
8013
8033
  /**
8014
8034
  * Fetch issuer certificate from AIA extension
8015
8035
  * @param url CA Issuers URL
8016
8036
  * @param timeout Timeout in milliseconds
8037
+ * @param proxyUrl Optional CORS proxy URL
8017
8038
  * @returns FetchResult with certificate data
8018
8039
  */
8019
- async function fetchIssuerCertificate(url, timeout = 5000) {
8040
+ async function fetchIssuerCertificate(url, timeout = 5000, proxyUrl) {
8020
8041
  return fetchBinary(url, {
8021
8042
  method: "GET",
8022
8043
  accept: "application/pkix-cert",
8023
8044
  timeout,
8045
+ proxyUrl,
8024
8046
  });
8025
8047
  }
8026
8048
 
@@ -8177,13 +8199,14 @@ function findIssuerInChain(cert, chain) {
8177
8199
  * Fetch issuer certificate from AIA extension
8178
8200
  * @param cert Certificate to fetch issuer for
8179
8201
  * @param timeout Timeout in ms
8202
+ * @param proxyUrl Optional CORS proxy URL
8180
8203
  * @returns Issuer certificate or null
8181
8204
  */
8182
- async function fetchIssuerFromAIA(cert, timeout = 5000) {
8205
+ async function fetchIssuerFromAIA(cert, timeout = 5000, proxyUrl) {
8183
8206
  const urls = extractCAIssuersUrls(cert);
8184
8207
  for (const url of urls) {
8185
8208
  try {
8186
- const result = await fetchIssuerCertificate(url, timeout);
8209
+ const result = await fetchIssuerCertificate(url, timeout, proxyUrl);
8187
8210
  if (result.ok && result.data) {
8188
8211
  // Try to parse as DER first, then PEM
8189
8212
  try {
@@ -8376,7 +8399,7 @@ function parseOCSPResponse(responseData) {
8376
8399
  * @returns Revocation result
8377
8400
  */
8378
8401
  async function checkOCSP(cert, issuerCert, options = {}) {
8379
- const { timeout = 5000, certificateChain = [] } = options;
8402
+ const { timeout = 5000, certificateChain = [], proxyUrl } = options;
8380
8403
  const now = new Date();
8381
8404
  // Get OCSP URLs
8382
8405
  const ocspUrls = extractOCSPUrls(cert);
@@ -8397,7 +8420,7 @@ async function checkOCSP(cert, issuerCert, options = {}) {
8397
8420
  }
8398
8421
  if (!issuer) {
8399
8422
  // Try AIA extension
8400
- issuer = await fetchIssuerFromAIA(cert, timeout);
8423
+ issuer = await fetchIssuerFromAIA(cert, timeout, proxyUrl);
8401
8424
  }
8402
8425
  if (!issuer) {
8403
8426
  return {
@@ -8425,7 +8448,7 @@ async function checkOCSP(cert, issuerCert, options = {}) {
8425
8448
  // Try each OCSP URL
8426
8449
  for (const url of ocspUrls) {
8427
8450
  try {
8428
- const result = await fetchOCSP(url, request, timeout);
8451
+ const result = await fetchOCSP(url, request, timeout, proxyUrl);
8429
8452
  if (result.ok && result.data) {
8430
8453
  return parseOCSPResponse(result.data);
8431
8454
  }
@@ -8532,7 +8555,7 @@ function parseCRL(data) {
8532
8555
  * @returns Revocation result
8533
8556
  */
8534
8557
  async function checkCRL(cert, options = {}) {
8535
- const { timeout = 10000 } = options;
8558
+ const { timeout = 10000, proxyUrl } = options;
8536
8559
  const now = new Date();
8537
8560
  // Get CRL URLs
8538
8561
  const crlUrls = extractCRLUrls(cert);
@@ -8549,7 +8572,7 @@ async function checkCRL(cert, options = {}) {
8549
8572
  const errors = [];
8550
8573
  for (const url of crlUrls) {
8551
8574
  try {
8552
- const result = await fetchCRL(url, timeout);
8575
+ const result = await fetchCRL(url, timeout, proxyUrl);
8553
8576
  if (!result.ok || !result.data) {
8554
8577
  errors.push(`${url}: ${result.error || "Failed to fetch"}`);
8555
8578
  continue;
@@ -8638,6 +8661,7 @@ async function checkCertificateRevocation(cert, options = {}) {
8638
8661
  ocspResult = await checkOCSP(x509Cert, null, {
8639
8662
  timeout: opts.ocspTimeout,
8640
8663
  certificateChain: opts.certificateChain,
8664
+ proxyUrl: options.proxyUrl,
8641
8665
  });
8642
8666
  // If OCSP gives a definitive answer (good or revoked), use it
8643
8667
  if (ocspResult.status === "good" || ocspResult.status === "revoked") {
@@ -8649,6 +8673,7 @@ async function checkCertificateRevocation(cert, options = {}) {
8649
8673
  if (opts.crlEnabled) {
8650
8674
  crlResult = await checkCRL(x509Cert, {
8651
8675
  timeout: opts.crlTimeout,
8676
+ proxyUrl: options.proxyUrl,
8652
8677
  });
8653
8678
  // If CRL gives a definitive answer, use it
8654
8679
  if (crlResult.status === "good" || crlResult.status === "revoked") {
@@ -10111,7 +10136,7 @@ async function verifyTimestamp(timestampBase64, options = {}) {
10111
10136
  // Check TSA certificate revocation if requested
10112
10137
  if (options.checkTsaRevocation !== false) {
10113
10138
  try {
10114
- tsaRevocation = await checkCertificateRevocation(tsaCert);
10139
+ tsaRevocation = await checkCertificateRevocation(tsaCert, options.revocationOptions);
10115
10140
  // If TSA certificate is revoked, the timestamp is invalid
10116
10141
  if (tsaRevocation.status === "revoked") {
10117
10142
  return {
@@ -10519,6 +10544,7 @@ async function verifySignature(signatureInfo, files, options = {}) {
10519
10544
  timestampResult = await verifyTimestamp(signatureInfo.signatureTimestamp, {
10520
10545
  signatureValue: signatureInfo.signatureValue,
10521
10546
  verifyTsaCertificate: true,
10547
+ revocationOptions: options.revocationOptions,
10522
10548
  });
10523
10549
  if (timestampResult.isValid && timestampResult.info) {
10524
10550
  // Use timestamp time as the trusted signing time