client-certificate-auth 1.3.7 → 1.3.8

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.
Files changed (2) hide show
  1. package/lib/parsers.js +35 -25
  2. package/package.json +3 -2
package/lib/parsers.js CHANGED
@@ -103,8 +103,9 @@ function splitPemBlocks(pem) {
103
103
 
104
104
  /**
105
105
  * Parse a multi-block PEM blob into a chained PeerCertificate. Splits the
106
- * input into individual blocks, parses each, drops blocks that fail to
107
- * parse, and links the remainder via issuerCertificate.
106
+ * input into individual blocks and links them via issuerCertificate. The
107
+ * first block is the leaf and must parse; later blocks that fail to parse
108
+ * are dropped and the chain links past them.
108
109
  *
109
110
  * Used by parsers whose proxies forward the full chain in a single field
110
111
  * (parseUrlPemAws, parseXfcc Chain). Without explicit chain linking, calling
@@ -117,22 +118,33 @@ function splitPemBlocks(pem) {
117
118
  */
118
119
  function chainFromMultiBlockPem(pem) {
119
120
  const pemBlocks = splitPemBlocks(pem);
120
- // Stryker disable next-line BlockStatement,ConditionalExpression: short-circuit; falling through hits the next certs.length===0 check with the same null result
121
+ // Stryker disable next-line BlockStatement,ConditionalExpression: short-circuit; falling through hands undefined to pemToCertificate, which throws for the same null result
121
122
  if (pemBlocks.length === 0) {
122
123
  return null;
123
124
  }
124
125
 
125
- const certs = pemBlocks.map(block => {
126
+ // Junk ahead of the leaf block means the leaf was damaged past recognition;
127
+ // the next block must not slide into its place. Leading whitespace is fine.
128
+ if (pem.slice(0, pem.indexOf(pemBlocks[0])).trim() !== '') {
129
+ return null;
130
+ }
131
+
132
+ // An unparseable leaf rejects the whole blob. Promoting the next block
133
+ // would authenticate the request as whichever certificate parsed first.
134
+ let leaf;
135
+ try {
136
+ leaf = pemToCertificate(pemBlocks[0]);
137
+ } catch {
138
+ return null;
139
+ }
140
+
141
+ const certs = [leaf];
142
+ for (const block of pemBlocks.slice(1)) {
126
143
  try {
127
- return pemToCertificate(block);
144
+ certs.push(pemToCertificate(block));
128
145
  } catch {
129
- // Stryker disable next-line BlockStatement: empty catch returns undefined which .filter(Boolean) drops alongside null — same chain output
130
- return null;
146
+ // Dropped; the chain links past it.
131
147
  }
132
- }).filter(Boolean);
133
-
134
- if (certs.length === 0) {
135
- return null;
136
148
  }
137
149
 
138
150
  // Stryker disable next-line EqualityOperator: setting issuerCertificate = undefined on last cert is same as not setting it
@@ -271,27 +283,25 @@ export function parseBase64Der(headerValue) {
271
283
  }
272
284
 
273
285
  // Handle comma-separated cert chains (Traefik format)
274
- // Stryker disable next-line MethodExpression: .trim() no-op (base64 ignores whitespace); .filter(Boolean) redundant (empty strings throw in X509Certificate, caught below)
275
- const certParts = headerValue.split(',').map(s => s.trim()).filter(Boolean);
286
+ // Stryker disable next-line MethodExpression: .trim() no-op (base64 ignores whitespace)
287
+ const certParts = headerValue.split(',').map(s => s.trim());
276
288
 
277
- // Stryker disable next-line BlockStatement,ConditionalExpression: →false equivalent (empty array → zero certs → certs.length===0 catches it); →true killed by valid base64-der tests
278
- if (certParts.length === 0) {
289
+ // An empty or unparseable leaf rejects the whole header. Promoting the next
290
+ // entry would authenticate the request as whichever certificate parsed first.
291
+ let leaf;
292
+ try {
293
+ leaf = derToCertificate(Buffer.from(certParts[0], 'base64'));
294
+ } catch {
279
295
  return null;
280
296
  }
281
297
 
282
- // Parse all certs in the chain
283
- const certs = certParts.map(base64 => {
298
+ const certs = [leaf];
299
+ for (const base64 of certParts.slice(1)) {
284
300
  try {
285
- const derBuffer = Buffer.from(base64, 'base64');
286
- return derToCertificate(derBuffer);
301
+ certs.push(derToCertificate(Buffer.from(base64, 'base64')));
287
302
  } catch {
288
- // Equivalent mutant (undefined also filtered by .filter(Boolean)) — catch-body BlockStatement unsuppressible via Stryker comments
289
- return null;
303
+ // Dropped; the chain links past it.
290
304
  }
291
- }).filter(Boolean);
292
-
293
- if (certs.length === 0) {
294
- return null;
295
305
  }
296
306
 
297
307
  // Link the cert chain via issuerCertificate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "client-certificate-auth",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Express/Connect middleware for mTLS client certificate authentication with reverse proxy support (AWS ALB, Envoy, Cloudflare, Traefik)",
5
5
  "homepage": "https://github.com/tgies/client-certificate-auth",
6
6
  "bugs": {
@@ -145,6 +145,7 @@
145
145
  "lib/**/*.d.cts"
146
146
  ],
147
147
  "overrides": {
148
- "fflate": "0.8.2"
148
+ "fflate": "0.8.2",
149
+ "qs": "^6.15.2"
149
150
  }
150
151
  }