Skip to content

Commit be3f47f

Browse files
committed
feat: mTLS.getCertificate helper can return a X509Certificate object
1 parent 6e5abc4 commit be3f47f

File tree

9 files changed

+65
-165
lines changed

9 files changed

+65
-165
lines changed

certification/fapi/index.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { readFileSync } from 'node:fs';
44
import * as path from 'node:path';
5-
import { randomUUID } from 'node:crypto';
5+
import * as crypto from 'node:crypto';
66
import * as https from 'node:https';
77
import { promisify } from 'node:util';
88
import { URL } from 'node:url';
@@ -81,7 +81,7 @@ const adapter = (name) => {
8181
const [version, ...rest] = id.split('-');
8282

8383
let metadata = {
84-
cacheBuster: randomUUID(),
84+
cacheBuster: crypto.randomUUID(),
8585
};
8686

8787
if (version === '1.0') {
@@ -199,14 +199,14 @@ const fapi = new Provider(ISSUER, {
199199
selfSignedTlsClientAuth: true,
200200
getCertificate(ctx) {
201201
if (SUITE_BASE_URL === OFFICIAL_CERTIFICATION) {
202-
return ctx.get('client-certificate');
202+
try {
203+
return new crypto.X509Certificate(Buffer.from(ctx.get('client-certificate'), 'base64'));
204+
} catch {
205+
return undefined;
206+
}
203207
}
204208

205-
const peerCertificate = ctx.socket.getPeerCertificate();
206-
if (peerCertificate.raw) {
207-
return `-----BEGIN CERTIFICATE-----\n${peerCertificate.raw.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`;
208-
}
209-
return undefined;
209+
return ctx.socket.getPeerX509Certificate();
210210
},
211211
},
212212
jwtResponseModes: { enabled: true },
@@ -317,7 +317,7 @@ fapi.use(async (ctx, next) => {
317317
return next();
318318
});
319319
fapi.use((ctx, next) => {
320-
const id = ctx.get('x-fapi-interaction-id') || randomUUID();
320+
const id = ctx.get('x-fapi-interaction-id') || crypto.randomUUID();
321321
ctx.set('x-fapi-interaction-id', id);
322322
return next();
323323
});

certification/oidc/configuration.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ export default {
5555
certificateBoundAccessTokens: true,
5656
selfSignedTlsClientAuth: true,
5757
getCertificate(ctx) {
58-
return ctx.get('client-certificate');
58+
try {
59+
return new crypto.X509Certificate(Buffer.from(ctx.get('client-certificate'), 'base64'));
60+
} catch {
61+
return undefined;
62+
}
5963
},
6064
},
6165
claimsParameter: { enabled: true },

docs/README.md

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,27 +1239,6 @@ _**default value**_:
12391239
Function used to determine if the client certificate, used in the request, is verified and comes from a trusted CA for the client. Should return true/false. Only used for `tls_client_auth` client authentication method.
12401240

12411241

1242-
<a id="certificate-authorized-when-behind-a-tls-terminating-proxy-nginx-apache"></a><details><summary>(Click to expand) When behind a TLS terminating proxy (nginx/apache)</summary><br>
1243-
1244-
1245-
When behind a TLS terminating proxy it is common that this detail be passed to the application as a sanitized header. This returns the chosen header value provided by nginx's `$ssl_client_verify` or apache's `%{SSL_CLIENT_VERIFY}s`
1246-
1247-
1248-
```js
1249-
function certificateAuthorized(ctx) {
1250-
return ctx.get('x-ssl-client-verify') === 'SUCCESS';
1251-
}
1252-
```
1253-
</details>
1254-
<a id="certificate-authorized-when-using-node's-https-create-server"></a><details><summary>(Click to expand) When using node's `https.createServer`
1255-
</summary><br>
1256-
1257-
```js
1258-
function certificateAuthorized(ctx) {
1259-
return ctx.socket.authorized;
1260-
}
1261-
```
1262-
</details>
12631242

12641243
#### certificateBoundAccessTokens
12651244

@@ -1276,53 +1255,12 @@ false
12761255
Function used to determine if the client certificate, used in the request, subject matches the registered client property. Only used for `tls_client_auth` client authentication method.
12771256

12781257

1279-
<a id="certificate-subject-matches-when-behind-a-tls-terminating-proxy-nginx-apache"></a><details><summary>(Click to expand) When behind a TLS terminating proxy (nginx/apache)</summary><br>
1280-
1281-
1282-
TLS terminating proxies can pass a header with the Subject DN pretty easily, for Nginx this would be `$ssl_client_s_dn`, for apache `%{SSL_CLIENT_S_DN}s`.
1283-
1284-
1285-
```js
1286-
function certificateSubjectMatches(ctx, property, expected) {
1287-
switch (property) {
1288-
case 'tls_client_auth_subject_dn':
1289-
return ctx.get('x-ssl-client-s-dn') === expected;
1290-
default:
1291-
throw new Error(`${property} certificate subject matching not implemented`);
1292-
}
1293-
}
1294-
```
1295-
</details>
12961258

12971259
#### getCertificate
12981260

1299-
Function used to retrieve the PEM-formatted client certificate used in the request.
1300-
1301-
1302-
<a id="get-certificate-when-behind-a-tls-terminating-proxy-nginx-apache"></a><details><summary>(Click to expand) When behind a TLS terminating proxy (nginx/apache)</summary><br>
1303-
1304-
1305-
When behind a TLS terminating proxy it is common that the certificate be passed to the application as a sanitized header. This returns the chosen header value provided by nginx's `$ssl_client_cert` or apache's `%{SSL_CLIENT_CERT}s`
1261+
Function used to retrieve a `crypto.X509Certificate` instance, or a PEM-formatted string, representation of client certificate used in the request.
13061262

13071263

1308-
```js
1309-
function getCertificate(ctx) {
1310-
return ctx.get('x-ssl-client-cert');
1311-
}
1312-
```
1313-
</details>
1314-
<a id="get-certificate-when-using-node's-https-create-server"></a><details><summary>(Click to expand) When using node's `https.createServer`
1315-
</summary><br>
1316-
1317-
```js
1318-
function getCertificate(ctx) {
1319-
const peerCertificate = ctx.socket.getPeerCertificate();
1320-
if (peerCertificate.raw) {
1321-
return `-----BEGIN CERTIFICATE-----\n${peerCertificate.raw.toString('base64')}\n-----END CERTIFICATE-----`;
1322-
}
1323-
}
1324-
```
1325-
</details>
13261264

13271265
#### selfSignedTlsClientAuth
13281266

lib/helpers/certificate_thumbprint.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import { createHash } from 'node:crypto';
1+
import { createHash, X509Certificate } from 'node:crypto';
22

33
import * as base64url from './base64url.js';
44

55
export default function certThumbprint(cert) {
6-
return base64url.encodeBuffer(
7-
createHash('sha256')
6+
let digest;
7+
if (cert instanceof X509Certificate) {
8+
digest = createHash('sha256').update(cert.raw).digest();
9+
} else {
10+
digest = createHash('sha256')
811
.update(
912
Buffer.from(
1013
cert.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s|=)/g, ''),
1114
'base64',
1215
),
1316
)
14-
.digest(),
15-
);
17+
.digest();
18+
}
19+
20+
return base64url.encodeBuffer(digest);
1621
}

lib/helpers/defaults.js

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,31 +1059,8 @@ function makeDefaults() {
10591059
/*
10601060
* features.mTLS.getCertificate
10611061
*
1062-
* description: Function used to retrieve the PEM-formatted client certificate used
1063-
* in the request.
1064-
*
1065-
* example: When behind a TLS terminating proxy (nginx/apache)
1066-
*
1067-
* When behind a TLS terminating proxy it is common that the certificate be passed
1068-
* to the application as a sanitized header. This returns the chosen header value provided
1069-
* by nginx's `$ssl_client_cert` or apache's `%{SSL_CLIENT_CERT}s`
1070-
*
1071-
* ```js
1072-
* function getCertificate(ctx) {
1073-
* return ctx.get('x-ssl-client-cert');
1074-
* }
1075-
* ```
1076-
*
1077-
* example: When using node's `https.createServer`
1078-
*
1079-
* ```js
1080-
* function getCertificate(ctx) {
1081-
* const peerCertificate = ctx.socket.getPeerCertificate();
1082-
* if (peerCertificate.raw) {
1083-
* return `-----BEGIN CERTIFICATE-----\n${peerCertificate.raw.toString('base64')}\n-----END CERTIFICATE-----`;
1084-
* }
1085-
* }
1086-
* ```
1062+
* description: Function used to retrieve a `crypto.X509Certificate` instance,
1063+
* or a PEM-formatted string, representation of client certificate used in the request.
10871064
*
10881065
* @nodefault
10891066
*/
@@ -1096,26 +1073,6 @@ function makeDefaults() {
10961073
* request, is verified and comes from a trusted CA for the client. Should return true/false.
10971074
* Only used for `tls_client_auth` client authentication method.
10981075
*
1099-
* example: When behind a TLS terminating proxy (nginx/apache)
1100-
*
1101-
* When behind a TLS terminating proxy it is common that this detail be passed
1102-
* to the application as a sanitized header. This returns the chosen header value provided
1103-
* by nginx's `$ssl_client_verify` or apache's `%{SSL_CLIENT_VERIFY}s`
1104-
*
1105-
* ```js
1106-
* function certificateAuthorized(ctx) {
1107-
* return ctx.get('x-ssl-client-verify') === 'SUCCESS';
1108-
* }
1109-
* ```
1110-
*
1111-
* example: When using node's `https.createServer`
1112-
*
1113-
* ```js
1114-
* function certificateAuthorized(ctx) {
1115-
* return ctx.socket.authorized;
1116-
* }
1117-
* ```
1118-
*
11191076
* @nodefault
11201077
*/
11211078
certificateAuthorized,
@@ -1127,22 +1084,6 @@ function makeDefaults() {
11271084
* request, subject matches the registered client property. Only used for `tls_client_auth`
11281085
* client authentication method.
11291086
*
1130-
* example: When behind a TLS terminating proxy (nginx/apache)
1131-
*
1132-
* TLS terminating proxies can pass a header with the Subject DN pretty easily, for Nginx
1133-
* this would be `$ssl_client_s_dn`, for apache `%{SSL_CLIENT_S_DN}s`.
1134-
*
1135-
* ```js
1136-
* function certificateSubjectMatches(ctx, property, expected) {
1137-
* switch (property) {
1138-
* case 'tls_client_auth_subject_dn':
1139-
* return ctx.get('x-ssl-client-s-dn') === expected;
1140-
* default:
1141-
* throw new Error(`${property} certificate subject matching not implemented`);
1142-
* }
1143-
* }
1144-
* ```
1145-
*
11461087
* @nodefault
11471088
*/
11481089
certificateSubjectMatches,

test/certificate_bound_access_tokens/certificate_bound_access_tokens.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { X509Certificate } from 'node:crypto';
2+
13
import merge from 'lodash/merge.js';
24

35
import getConfig from '../default.config.js';
@@ -9,7 +11,11 @@ merge(config.features, {
911
enabled: true,
1012
certificateBoundAccessTokens: true,
1113
getCertificate(ctx) {
12-
return ctx.get('x-ssl-client-cert');
14+
try {
15+
return new X509Certificate(Buffer.from(ctx.get('x-ssl-client-cert'), 'base64'));
16+
} catch (e) {
17+
return undefined;
18+
}
1319
},
1420
},
1521
clientCredentials: { enabled: true },

0 commit comments

Comments
 (0)