Skip to content

Commit

Permalink
feat: mTLS.getCertificate helper can return a X509Certificate object
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Jan 23, 2023
1 parent 6e5abc4 commit be3f47f
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 165 deletions.
18 changes: 9 additions & 9 deletions certification/fapi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { readFileSync } from 'node:fs';
import * as path from 'node:path';
import { randomUUID } from 'node:crypto';
import * as crypto from 'node:crypto';
import * as https from 'node:https';
import { promisify } from 'node:util';
import { URL } from 'node:url';
Expand Down Expand Up @@ -81,7 +81,7 @@ const adapter = (name) => {
const [version, ...rest] = id.split('-');

let metadata = {
cacheBuster: randomUUID(),
cacheBuster: crypto.randomUUID(),
};

if (version === '1.0') {
Expand Down Expand Up @@ -199,14 +199,14 @@ const fapi = new Provider(ISSUER, {
selfSignedTlsClientAuth: true,
getCertificate(ctx) {
if (SUITE_BASE_URL === OFFICIAL_CERTIFICATION) {
return ctx.get('client-certificate');
try {
return new crypto.X509Certificate(Buffer.from(ctx.get('client-certificate'), 'base64'));
} catch {
return undefined;
}
}

const peerCertificate = ctx.socket.getPeerCertificate();
if (peerCertificate.raw) {
return `-----BEGIN CERTIFICATE-----\n${peerCertificate.raw.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`;
}
return undefined;
return ctx.socket.getPeerX509Certificate();
},
},
jwtResponseModes: { enabled: true },
Expand Down Expand Up @@ -317,7 +317,7 @@ fapi.use(async (ctx, next) => {
return next();
});
fapi.use((ctx, next) => {
const id = ctx.get('x-fapi-interaction-id') || randomUUID();
const id = ctx.get('x-fapi-interaction-id') || crypto.randomUUID();
ctx.set('x-fapi-interaction-id', id);
return next();
});
Expand Down
6 changes: 5 additions & 1 deletion certification/oidc/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ export default {
certificateBoundAccessTokens: true,
selfSignedTlsClientAuth: true,
getCertificate(ctx) {
return ctx.get('client-certificate');
try {
return new crypto.X509Certificate(Buffer.from(ctx.get('client-certificate'), 'base64'));
} catch {
return undefined;
}
},
},
claimsParameter: { enabled: true },
Expand Down
64 changes: 1 addition & 63 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1239,27 +1239,6 @@ _**default value**_:
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.


<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>


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`


```js
function certificateAuthorized(ctx) {
return ctx.get('x-ssl-client-verify') === 'SUCCESS';
}
```
</details>
<a id="certificate-authorized-when-using-node's-https-create-server"></a><details><summary>(Click to expand) When using node's `https.createServer`
</summary><br>

```js
function certificateAuthorized(ctx) {
return ctx.socket.authorized;
}
```
</details>

#### certificateBoundAccessTokens

Expand All @@ -1276,53 +1255,12 @@ false
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.


<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>


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`.


```js
function certificateSubjectMatches(ctx, property, expected) {
switch (property) {
case 'tls_client_auth_subject_dn':
return ctx.get('x-ssl-client-s-dn') === expected;
default:
throw new Error(`${property} certificate subject matching not implemented`);
}
}
```
</details>

#### getCertificate

Function used to retrieve the PEM-formatted client certificate used in the request.


<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>


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`
Function used to retrieve a `crypto.X509Certificate` instance, or a PEM-formatted string, representation of client certificate used in the request.


```js
function getCertificate(ctx) {
return ctx.get('x-ssl-client-cert');
}
```
</details>
<a id="get-certificate-when-using-node's-https-create-server"></a><details><summary>(Click to expand) When using node's `https.createServer`
</summary><br>

```js
function getCertificate(ctx) {
const peerCertificate = ctx.socket.getPeerCertificate();
if (peerCertificate.raw) {
return `-----BEGIN CERTIFICATE-----\n${peerCertificate.raw.toString('base64')}\n-----END CERTIFICATE-----`;
}
}
```
</details>

#### selfSignedTlsClientAuth

Expand Down
15 changes: 10 additions & 5 deletions lib/helpers/certificate_thumbprint.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { createHash } from 'node:crypto';
import { createHash, X509Certificate } from 'node:crypto';

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

export default function certThumbprint(cert) {
return base64url.encodeBuffer(
createHash('sha256')
let digest;
if (cert instanceof X509Certificate) {
digest = createHash('sha256').update(cert.raw).digest();
} else {
digest = createHash('sha256')
.update(
Buffer.from(
cert.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s|=)/g, ''),
'base64',
),
)
.digest(),
);
.digest();
}

return base64url.encodeBuffer(digest);
}
63 changes: 2 additions & 61 deletions lib/helpers/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -1059,31 +1059,8 @@ function makeDefaults() {
/*
* features.mTLS.getCertificate
*
* description: Function used to retrieve the PEM-formatted client certificate used
* in the request.
*
* example: When behind a TLS terminating proxy (nginx/apache)
*
* 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`
*
* ```js
* function getCertificate(ctx) {
* return ctx.get('x-ssl-client-cert');
* }
* ```
*
* example: When using node's `https.createServer`
*
* ```js
* function getCertificate(ctx) {
* const peerCertificate = ctx.socket.getPeerCertificate();
* if (peerCertificate.raw) {
* return `-----BEGIN CERTIFICATE-----\n${peerCertificate.raw.toString('base64')}\n-----END CERTIFICATE-----`;
* }
* }
* ```
* description: Function used to retrieve a `crypto.X509Certificate` instance,
* or a PEM-formatted string, representation of client certificate used in the request.
*
* @nodefault
*/
Expand All @@ -1096,26 +1073,6 @@ function makeDefaults() {
* 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.
*
* example: When behind a TLS terminating proxy (nginx/apache)
*
* 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`
*
* ```js
* function certificateAuthorized(ctx) {
* return ctx.get('x-ssl-client-verify') === 'SUCCESS';
* }
* ```
*
* example: When using node's `https.createServer`
*
* ```js
* function certificateAuthorized(ctx) {
* return ctx.socket.authorized;
* }
* ```
*
* @nodefault
*/
certificateAuthorized,
Expand All @@ -1127,22 +1084,6 @@ function makeDefaults() {
* request, subject matches the registered client property. Only used for `tls_client_auth`
* client authentication method.
*
* example: When behind a TLS terminating proxy (nginx/apache)
*
* 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`.
*
* ```js
* function certificateSubjectMatches(ctx, property, expected) {
* switch (property) {
* case 'tls_client_auth_subject_dn':
* return ctx.get('x-ssl-client-s-dn') === expected;
* default:
* throw new Error(`${property} certificate subject matching not implemented`);
* }
* }
* ```
*
* @nodefault
*/
certificateSubjectMatches,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { X509Certificate } from 'node:crypto';

import merge from 'lodash/merge.js';

import getConfig from '../default.config.js';
Expand All @@ -9,7 +11,11 @@ merge(config.features, {
enabled: true,
certificateBoundAccessTokens: true,
getCertificate(ctx) {
return ctx.get('x-ssl-client-cert');
try {
return new X509Certificate(Buffer.from(ctx.get('x-ssl-client-cert'), 'base64'));
} catch (e) {
return undefined;
}
},
},
clientCredentials: { enabled: true },
Expand Down

0 comments on commit be3f47f

Please sign in to comment.