client-certificate-auth 2.1.3 → 2.2.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/README.md +2 -0
- package/lib/clientCertificateAuth.d.cts +5 -4
- package/lib/clientCertificateAuth.d.ts +5 -4
- package/lib/clientCertificateAuth.js +4 -4
- package/lib/extractor.d.ts +2 -2
- package/lib/extractor.js +1 -1
- package/lib/parsers.d.ts +14 -6
- package/lib/parsers.js +52 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -354,6 +354,8 @@ app.use(clientCertificateAuth((cert) => {
|
|
|
354
354
|
|
|
355
355
|
When `includeChain: true`, the certificate object includes `issuerCertificate` linking to the issuer's certificate (and so on up the chain). This works consistently for both socket-based and header-based extraction.
|
|
356
356
|
|
|
357
|
+
For header-based extraction the first certificate in the header is the leaf. If it is empty or unparseable the header is rejected with `header_missing_or_malformed` rather than promoting the next certificate into the leaf position. See [Reverse Proxy Setup](https://tgies.github.io/client-certificate-auth/guide/reverse-proxy#chains-in-a-single-header) for the per-encoding details.
|
|
358
|
+
|
|
357
359
|
### User Login
|
|
358
360
|
|
|
359
361
|
Client certificates provide cryptographically-verified identity, making them ideal for user authentication. Map certificate fields to user accounts in your database:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
2
|
import type { Socket } from 'net';
|
|
3
3
|
import type { PeerCertificate, DetailedPeerCertificate } from 'tls';
|
|
4
|
+
import type { ChainedPeerCertificate } from './parsers.js';
|
|
4
5
|
import type {
|
|
5
6
|
ClientCertificateAuthOptions as EsmOptions,
|
|
6
7
|
ValidationCallback as EsmValidationCallback,
|
|
@@ -56,7 +57,7 @@ export interface ClientCertRequest extends IncomingMessage {
|
|
|
56
57
|
* Available after successful certificate extraction.
|
|
57
58
|
* Contains issuerCertificate chain if includeChain option is true.
|
|
58
59
|
*/
|
|
59
|
-
clientCertificate?:
|
|
60
|
+
clientCertificate?: ChainedPeerCertificate;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -88,7 +89,7 @@ export interface ClientCertificateAuthOptions {
|
|
|
88
89
|
* @param req - The HTTP request object
|
|
89
90
|
*/
|
|
90
91
|
onAuthenticated?: (
|
|
91
|
-
cert:
|
|
92
|
+
cert: ChainedPeerCertificate,
|
|
92
93
|
req: ClientCertRequest
|
|
93
94
|
) => void | Promise<void>;
|
|
94
95
|
|
|
@@ -100,14 +101,14 @@ export interface ClientCertificateAuthOptions {
|
|
|
100
101
|
* @param reason - Why authentication was rejected
|
|
101
102
|
*/
|
|
102
103
|
onRejected?: (
|
|
103
|
-
cert:
|
|
104
|
+
cert: ChainedPeerCertificate | null,
|
|
104
105
|
req: ClientCertRequest,
|
|
105
106
|
reason: string
|
|
106
107
|
) => void | Promise<void>;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
export type ValidationCallback = (
|
|
110
|
-
cert:
|
|
111
|
+
cert: ChainedPeerCertificate,
|
|
111
112
|
req?: ClientCertRequest
|
|
112
113
|
) => boolean | PromiseLike<boolean>;
|
|
113
114
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
2
|
import type { Socket } from 'net';
|
|
3
3
|
import type { PeerCertificate, DetailedPeerCertificate } from 'tls';
|
|
4
|
+
import type { ChainedPeerCertificate } from './parsers.js';
|
|
4
5
|
import type { CertificateSource, HeaderEncoding } from './parsers.js';
|
|
5
6
|
|
|
6
7
|
export type { CertificateSource, HeaderEncoding };
|
|
@@ -54,7 +55,7 @@ export interface ClientCertRequest extends IncomingMessage {
|
|
|
54
55
|
* Available after successful certificate extraction, before authorization callback.
|
|
55
56
|
* Contains issuerCertificate chain if includeChain option is true.
|
|
56
57
|
*/
|
|
57
|
-
clientCertificate?:
|
|
58
|
+
clientCertificate?: ChainedPeerCertificate;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -127,7 +128,7 @@ export interface ClientCertificateAuthOptions {
|
|
|
127
128
|
* @param req - The HTTP request object
|
|
128
129
|
*/
|
|
129
130
|
onAuthenticated?: (
|
|
130
|
-
cert:
|
|
131
|
+
cert: ChainedPeerCertificate,
|
|
131
132
|
req: ClientCertRequest
|
|
132
133
|
) => void | Promise<void>;
|
|
133
134
|
|
|
@@ -139,14 +140,14 @@ export interface ClientCertificateAuthOptions {
|
|
|
139
140
|
* @param reason - Why authentication was rejected
|
|
140
141
|
*/
|
|
141
142
|
onRejected?: (
|
|
142
|
-
cert:
|
|
143
|
+
cert: ChainedPeerCertificate | null,
|
|
143
144
|
req: ClientCertRequest,
|
|
144
145
|
reason: string
|
|
145
146
|
) => void | Promise<void>;
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
export type ValidationCallback = (
|
|
149
|
-
cert:
|
|
150
|
+
cert: ChainedPeerCertificate,
|
|
150
151
|
req?: ClientCertRequest
|
|
151
152
|
) => boolean | PromiseLike<boolean>;
|
|
152
153
|
|
|
@@ -34,7 +34,7 @@ function normalizeCallbackError(err) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* @typedef {import('http').IncomingMessage & { secure?: boolean; socket: import('net').Socket & { authorized?: boolean; authorizationError?: Error | string; getPeerCertificate?: (detailed?: boolean) => import('tls').PeerCertificate | import('tls').DetailedPeerCertificate }; clientCertificate?: import('
|
|
37
|
+
* @typedef {import('http').IncomingMessage & { secure?: boolean; socket: import('net').Socket & { authorized?: boolean; authorizationError?: Error | string; getPeerCertificate?: (detailed?: boolean) => import('tls').PeerCertificate | import('tls').DetailedPeerCertificate }; clientCertificate?: import('./parsers.js').ChainedPeerCertificate }} ClientCertRequest
|
|
38
38
|
* @typedef {import('http').ServerResponse & { redirect: (statusOrUrl: number | string, url?: string) => void }} ClientCertResponse
|
|
39
39
|
* @typedef {(req: ClientCertRequest, res: ClientCertResponse, next: (err?: Error) => void) => void} Middleware
|
|
40
40
|
*/
|
|
@@ -64,9 +64,9 @@ function normalizeCallbackError(err) {
|
|
|
64
64
|
* @property {string} [verifyValue] - Expected value indicating successful verification (e.g., 'SUCCESS').
|
|
65
65
|
* If verifyHeader is set, requests are rejected unless the header matches this value.
|
|
66
66
|
* Comparison is exact (case-sensitive, no whitespace trimming); set this to the exact string your proxy emits.
|
|
67
|
-
* @property {(cert: import('
|
|
67
|
+
* @property {(cert: import('./parsers.js').ChainedPeerCertificate, req: ClientCertRequest) => void | Promise<void>} [onAuthenticated] -
|
|
68
68
|
* Called when a client is successfully authenticated. Fire-and-forget.
|
|
69
|
-
* @property {(cert: import('
|
|
69
|
+
* @property {(cert: import('./parsers.js').ChainedPeerCertificate | null, req: ClientCertRequest, reason: string) => void | Promise<void>} [onRejected] -
|
|
70
70
|
* Called when authentication is rejected. Fire-and-forget.
|
|
71
71
|
*/
|
|
72
72
|
|
|
@@ -78,7 +78,7 @@ function normalizeCallbackError(err) {
|
|
|
78
78
|
* `req.socket.getPeerCertificate()` or extracted from headers) and must
|
|
79
79
|
* return `true` (or a thenable resolving to `true`) for the request to proceed.
|
|
80
80
|
*
|
|
81
|
-
* @param {(cert: import('
|
|
81
|
+
* @param {(cert: import('./parsers.js').ChainedPeerCertificate, req: ClientCertRequest) => boolean | PromiseLike<boolean>} callback
|
|
82
82
|
* Validation function that receives the client certificate and the request
|
|
83
83
|
* object. Returns true/false (sync) or a `PromiseLike<boolean>` (async,
|
|
84
84
|
* including native Promises and any thenable resolving to a boolean) to
|
package/lib/extractor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} ExtractionResult
|
|
3
3
|
* @property {boolean} success - Whether extraction succeeded
|
|
4
|
-
* @property {import('
|
|
4
|
+
* @property {import('./parsers.js').ChainedPeerCertificate | null} certificate - Extracted certificate (null on failure)
|
|
5
5
|
* @property {string | null} reason - Rejection reason code (null on success)
|
|
6
6
|
*
|
|
7
7
|
* Rejection reasons:
|
|
@@ -74,7 +74,7 @@ export type ExtractionResult = {
|
|
|
74
74
|
/**
|
|
75
75
|
* - Extracted certificate (null on failure)
|
|
76
76
|
*/
|
|
77
|
-
certificate: import("
|
|
77
|
+
certificate: import("./parsers.js").ChainedPeerCertificate | null;
|
|
78
78
|
/**
|
|
79
79
|
* - Rejection reason code (null on success)
|
|
80
80
|
*
|
package/lib/extractor.js
CHANGED
|
@@ -56,7 +56,7 @@ export function validateExtractorOptions(options = {}) {
|
|
|
56
56
|
/**
|
|
57
57
|
* @typedef {Object} ExtractionResult
|
|
58
58
|
* @property {boolean} success - Whether extraction succeeded
|
|
59
|
-
* @property {import('
|
|
59
|
+
* @property {import('./parsers.js').ChainedPeerCertificate | null} certificate - Extracted certificate (null on failure)
|
|
60
60
|
* @property {string | null} reason - Rejection reason code (null on success)
|
|
61
61
|
*
|
|
62
62
|
* Rejection reasons:
|
package/lib/parsers.d.ts
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import type { PeerCertificate } from 'tls';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* A certificate with an optional issuer chain. Header-extracted chains end at
|
|
11
|
+
* the topmost certificate, while Node's socket chains self-reference at the root.
|
|
12
|
+
*/
|
|
13
|
+
export type ChainedPeerCertificate = PeerCertificate & {
|
|
14
|
+
issuerCertificate?: ChainedPeerCertificate;
|
|
15
|
+
};
|
|
16
|
+
|
|
9
17
|
/**
|
|
10
18
|
* Supported header encoding formats.
|
|
11
19
|
*/
|
|
@@ -60,26 +68,26 @@ export interface CertificateHeaderConfig {
|
|
|
60
68
|
* Parse URL-encoded PEM certificate (nginx, HAProxy format).
|
|
61
69
|
* @see https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_escaped_cert
|
|
62
70
|
*/
|
|
63
|
-
export declare function parseUrlPem(headerValue: string):
|
|
71
|
+
export declare function parseUrlPem(headerValue: string): ChainedPeerCertificate | null;
|
|
64
72
|
|
|
65
73
|
/**
|
|
66
74
|
* Parse URL-encoded PEM certificate with AWS ALB safe character handling.
|
|
67
75
|
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html
|
|
68
76
|
*/
|
|
69
|
-
export declare function parseUrlPemAws(headerValue: string):
|
|
77
|
+
export declare function parseUrlPemAws(headerValue: string): ChainedPeerCertificate | null;
|
|
70
78
|
|
|
71
79
|
/**
|
|
72
80
|
* Parse Envoy XFCC (X-Forwarded-Client-Cert) structured header format.
|
|
73
81
|
* @see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
|
|
74
82
|
*/
|
|
75
|
-
export declare function parseXfcc(headerValue: string):
|
|
83
|
+
export declare function parseXfcc(headerValue: string): ChainedPeerCertificate | null;
|
|
76
84
|
|
|
77
85
|
/**
|
|
78
86
|
* Parse base64-encoded DER certificate (Cloudflare, Traefik format).
|
|
79
87
|
* Also handles Traefik's comma-separated cert chains.
|
|
80
88
|
* @see https://developers.cloudflare.com/api-shield/security/mtls/configure/
|
|
81
89
|
*/
|
|
82
|
-
export declare function parseBase64Der(headerValue: string):
|
|
90
|
+
export declare function parseBase64Der(headerValue: string): ChainedPeerCertificate | null;
|
|
83
91
|
|
|
84
92
|
/**
|
|
85
93
|
* Parse RFC 9440 format certificate (used by Google Cloud Load Balancer).
|
|
@@ -103,7 +111,7 @@ export declare function derToCertificate(der: Buffer): PeerCertificate;
|
|
|
103
111
|
export declare function parseHeaderValue(
|
|
104
112
|
headerValue: string,
|
|
105
113
|
encoding: HeaderEncoding
|
|
106
|
-
):
|
|
114
|
+
): ChainedPeerCertificate | null;
|
|
107
115
|
|
|
108
116
|
/**
|
|
109
117
|
* Get certificate from request headers using configuration.
|
|
@@ -111,4 +119,4 @@ export declare function parseHeaderValue(
|
|
|
111
119
|
export declare function getCertificateFromHeaders(
|
|
112
120
|
headers: Record<string, string | string[] | undefined>,
|
|
113
121
|
config: CertificateHeaderConfig
|
|
114
|
-
):
|
|
122
|
+
): ChainedPeerCertificate | null;
|
package/lib/parsers.js
CHANGED
|
@@ -11,6 +11,12 @@ import { X509Certificate } from 'node:crypto';
|
|
|
11
11
|
* @typedef {import('tls').PeerCertificate} PeerCertificate
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* A certificate with an optional issuer chain. Header-extracted chains end at
|
|
16
|
+
* the topmost certificate, while Node's socket chains self-reference at the root.
|
|
17
|
+
* @typedef {PeerCertificate & { issuerCertificate?: ChainedPeerCertificate }} ChainedPeerCertificate
|
|
18
|
+
*/
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Preset configurations for common reverse proxies.
|
|
16
22
|
* Maps preset name to { header, encoding } configuration, with optional
|
|
@@ -89,10 +95,11 @@ export const PRESETS = {
|
|
|
89
95
|
/**
|
|
90
96
|
* Parse URL-encoded PEM certificate (nginx, HAProxy format).
|
|
91
97
|
* Uses standard URL encoding via $ssl_client_escaped_cert or similar.
|
|
98
|
+
* Concatenated PEM blocks are split and linked via issuerCertificate.
|
|
92
99
|
*
|
|
93
100
|
* @see https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_escaped_cert
|
|
94
101
|
* @param {string} headerValue - URL-encoded PEM certificate
|
|
95
|
-
* @returns {
|
|
102
|
+
* @returns {ChainedPeerCertificate | null} Parsed certificate or null on failure
|
|
96
103
|
*/
|
|
97
104
|
export function parseUrlPem(headerValue) {
|
|
98
105
|
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
@@ -102,7 +109,7 @@ export function parseUrlPem(headerValue) {
|
|
|
102
109
|
|
|
103
110
|
try {
|
|
104
111
|
const pem = decodeURIComponent(headerValue);
|
|
105
|
-
return
|
|
112
|
+
return chainFromMultiBlockPem(pem);
|
|
106
113
|
} catch {
|
|
107
114
|
return null;
|
|
108
115
|
}
|
|
@@ -138,36 +145,48 @@ function splitPemBlocks(pem) {
|
|
|
138
145
|
|
|
139
146
|
/**
|
|
140
147
|
* Parse a multi-block PEM blob into a chained PeerCertificate. Splits the
|
|
141
|
-
* input into individual blocks
|
|
142
|
-
*
|
|
148
|
+
* input into individual blocks and links them via issuerCertificate. The
|
|
149
|
+
* first block is the leaf and must parse; later blocks that fail to parse
|
|
150
|
+
* are dropped and the chain links past them.
|
|
143
151
|
*
|
|
144
152
|
* Used by parsers whose proxies forward the full chain in a single field
|
|
145
|
-
* (parseUrlPemAws, parseXfcc Chain). Without explicit chain
|
|
146
|
-
* X509Certificate() on a multi-block PEM returns just the leaf with no
|
|
153
|
+
* (parseUrlPem, parseUrlPemAws, parseXfcc Chain). Without explicit chain
|
|
154
|
+
* linking, calling X509Certificate() on a multi-block PEM returns just the leaf with no
|
|
147
155
|
* issuerCertificate (toLegacyObject drops the property), silently losing
|
|
148
156
|
* chain information for `includeChain: true` consumers.
|
|
149
157
|
*
|
|
150
158
|
* @param {string} pem - Concatenated PEM blocks
|
|
151
|
-
* @returns {
|
|
159
|
+
* @returns {ChainedPeerCertificate | null} Leaf cert with chain via issuerCertificate, or null if the leaf does not parse
|
|
152
160
|
*/
|
|
153
161
|
function chainFromMultiBlockPem(pem) {
|
|
154
162
|
const pemBlocks = splitPemBlocks(pem);
|
|
155
|
-
// Stryker disable next-line BlockStatement,ConditionalExpression: short-circuit; falling through
|
|
163
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: short-circuit; falling through hands undefined to pemToCertificate, which throws for the same null result
|
|
156
164
|
if (pemBlocks.length === 0) {
|
|
157
165
|
return null;
|
|
158
166
|
}
|
|
159
167
|
|
|
160
|
-
|
|
168
|
+
// Junk ahead of the leaf block means the leaf was damaged past recognition;
|
|
169
|
+
// the next block must not slide into its place. Leading whitespace is fine.
|
|
170
|
+
if (pem.slice(0, pem.indexOf(pemBlocks[0])).trim() !== '') {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// An unparseable leaf rejects the whole blob. Promoting the next block
|
|
175
|
+
// would authenticate the request as whichever certificate parsed first.
|
|
176
|
+
let leaf;
|
|
177
|
+
try {
|
|
178
|
+
leaf = pemToCertificate(pemBlocks[0]);
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const certs = [leaf];
|
|
184
|
+
for (const block of pemBlocks.slice(1)) {
|
|
161
185
|
try {
|
|
162
|
-
|
|
186
|
+
certs.push(pemToCertificate(block));
|
|
163
187
|
} catch {
|
|
164
|
-
//
|
|
165
|
-
return null;
|
|
188
|
+
// Dropped; the chain links past it.
|
|
166
189
|
}
|
|
167
|
-
}).filter(Boolean);
|
|
168
|
-
|
|
169
|
-
if (certs.length === 0) {
|
|
170
|
-
return null;
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
// Stryker disable next-line EqualityOperator: setting issuerCertificate = undefined on last cert is same as not setting it
|
|
@@ -186,7 +205,7 @@ function chainFromMultiBlockPem(pem) {
|
|
|
186
205
|
*
|
|
187
206
|
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html
|
|
188
207
|
* @param {string} headerValue - AWS ALB URL-encoded PEM certificate
|
|
189
|
-
* @returns {
|
|
208
|
+
* @returns {ChainedPeerCertificate | null} Parsed certificate or null on failure
|
|
190
209
|
*/
|
|
191
210
|
export function parseUrlPemAws(headerValue) {
|
|
192
211
|
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
@@ -214,7 +233,7 @@ export function parseUrlPemAws(headerValue) {
|
|
|
214
233
|
*
|
|
215
234
|
* @see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
|
|
216
235
|
* @param {string} headerValue - XFCC formatted header value
|
|
217
|
-
* @returns {
|
|
236
|
+
* @returns {ChainedPeerCertificate | null} Parsed certificate or null on failure
|
|
218
237
|
*/
|
|
219
238
|
export function parseXfcc(headerValue) {
|
|
220
239
|
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
@@ -297,7 +316,7 @@ export function parseXfcc(headerValue) {
|
|
|
297
316
|
*
|
|
298
317
|
* @see https://developers.cloudflare.com/api-shield/security/mtls/configure/
|
|
299
318
|
* @param {string} headerValue - Base64-encoded DER certificate(s)
|
|
300
|
-
* @returns {
|
|
319
|
+
* @returns {ChainedPeerCertificate | null} Parsed certificate or null on failure
|
|
301
320
|
*/
|
|
302
321
|
export function parseBase64Der(headerValue) {
|
|
303
322
|
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
@@ -306,27 +325,25 @@ export function parseBase64Der(headerValue) {
|
|
|
306
325
|
}
|
|
307
326
|
|
|
308
327
|
// Handle comma-separated cert chains (Traefik format)
|
|
309
|
-
// Stryker disable next-line MethodExpression: .trim() no-op (base64 ignores whitespace)
|
|
310
|
-
const certParts = headerValue.split(',').map(s => s.trim())
|
|
328
|
+
// Stryker disable next-line MethodExpression: .trim() no-op (base64 ignores whitespace)
|
|
329
|
+
const certParts = headerValue.split(',').map(s => s.trim());
|
|
311
330
|
|
|
312
|
-
//
|
|
313
|
-
|
|
331
|
+
// An empty or unparseable leaf rejects the whole header. Promoting the next
|
|
332
|
+
// entry would authenticate the request as whichever certificate parsed first.
|
|
333
|
+
let leaf;
|
|
334
|
+
try {
|
|
335
|
+
leaf = derToCertificate(Buffer.from(certParts[0], 'base64'));
|
|
336
|
+
} catch {
|
|
314
337
|
return null;
|
|
315
338
|
}
|
|
316
339
|
|
|
317
|
-
|
|
318
|
-
const
|
|
340
|
+
const certs = [leaf];
|
|
341
|
+
for (const base64 of certParts.slice(1)) {
|
|
319
342
|
try {
|
|
320
|
-
|
|
321
|
-
return derToCertificate(derBuffer);
|
|
343
|
+
certs.push(derToCertificate(Buffer.from(base64, 'base64')));
|
|
322
344
|
} catch {
|
|
323
|
-
//
|
|
324
|
-
return null;
|
|
345
|
+
// Dropped; the chain links past it.
|
|
325
346
|
}
|
|
326
|
-
}).filter(Boolean);
|
|
327
|
-
|
|
328
|
-
if (certs.length === 0) {
|
|
329
|
-
return null;
|
|
330
347
|
}
|
|
331
348
|
|
|
332
349
|
// Link the cert chain via issuerCertificate
|
|
@@ -400,7 +417,7 @@ export function derToCertificate(der) {
|
|
|
400
417
|
*
|
|
401
418
|
* @param {string} headerValue - Raw header value
|
|
402
419
|
* @param {'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' | 'rfc9440'} encoding - Encoding format
|
|
403
|
-
* @returns {
|
|
420
|
+
* @returns {ChainedPeerCertificate | null} Parsed certificate or null on failure
|
|
404
421
|
*/
|
|
405
422
|
export function parseHeaderValue(headerValue, encoding) {
|
|
406
423
|
// Only strings are parseable; a header name like 'constructor' resolves
|
|
@@ -433,7 +450,7 @@ export function parseHeaderValue(headerValue, encoding) {
|
|
|
433
450
|
* @param {string} [config.certificateSource] - Preset name (aws-alb, envoy, cloudflare, traefik)
|
|
434
451
|
* @param {string} [config.certificateHeader] - Custom header name (overrides preset)
|
|
435
452
|
* @param {string} [config.headerEncoding] - Encoding format (required if certificateHeader is set)
|
|
436
|
-
* @returns {
|
|
453
|
+
* @returns {ChainedPeerCertificate | null} Parsed certificate or null if not found/invalid
|
|
437
454
|
*/
|
|
438
455
|
export function getCertificateFromHeaders(headers, config) {
|
|
439
456
|
let headerName;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "client-certificate-auth",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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": {
|