Skip to content

Commit

Permalink
feat: add tls_client_auth client authentication method
Browse files Browse the repository at this point in the history
Defined in https://tools.ietf.org/html/draft-ietf-oauth-mtls-11 this
client authentication method uses mutual Transport Layer Security (TLS)
to authenticate a client for token, introspection and revocation
endpoints. It relies on your TLS-offloading proxy to parse, validate and
send metadata about the X.509 certificate, as well as status of the
verification, via headers to the upstream node.js application.

See the configuration doc tokenEndpointAuthMethods section for more
details.
  • Loading branch information
panva committed Sep 26, 2018
1 parent 186de0d commit ce2bf66
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 40 deletions.
53 changes: 44 additions & 9 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ async audiences(ctx, sub, token, use) {

### claims

List of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for.
Array of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for.

_**affects**_: discovery, ID Token claim names, Userinfo claim names

Expand Down Expand Up @@ -1319,7 +1319,7 @@ _**affects**_: discovery

### dynamicScopes

List of the dynamic scope values that the OP supports. These must be regular expressions that the OP will check string scope values, that aren't in the static list, against.
Array of the dynamic scope values that the OP supports. These must be regular expressions that the OP will check string scope values, that aren't in the static list, against.

_**affects**_: discovery, authorization, ID Token claims, Userinfo claims

Expand Down Expand Up @@ -1608,7 +1608,7 @@ async interactionUrl(ctx, interaction) {

### introspectionEndpointAuthMethods

List of Client Authentication methods supported by this OP's Introspection Endpoint
Array of Client Authentication methods supported by this OP's Introspection Endpoint. If no configuration value is provided the same values as for tokenEndpointAuthMethods will be used. Supported values list is the same as for tokenEndpointAuthMethods

_**affects**_: discovery, client authentication for introspection, registration and registration management

Expand Down Expand Up @@ -1708,7 +1708,7 @@ async postLogoutRedirectUri(ctx) {

### prompts

List of the prompt values that the OpenID Provider MAY be able to resolve
Array of the prompt values that the OpenID Provider MAY be able to resolve

_**affects**_: authorization

Expand Down Expand Up @@ -1762,7 +1762,7 @@ async renderError(ctx, out, error) {

### responseTypes

List of response_type values that OP supports
Array of response_type values that OP supports

_**affects**_: authorization, discovery, registration, registration management
<details>
Expand All @@ -1784,7 +1784,7 @@ _**affects**_: authorization, discovery, registration, registration management

### revocationEndpointAuthMethods

List of Client Authentication methods supported by this OP's Revocation Endpoint
Array of Client Authentication methods supported by this OP's Revocation Endpoint. If no configuration value is provided the same values as for tokenEndpointAuthMethods will be used. Supported values list is the same as for tokenEndpointAuthMethods

_**affects**_: discovery, client authentication for revocation, registration and registration management

Expand Down Expand Up @@ -1825,7 +1825,7 @@ _**affects**_: routing

### scopes

List of the scope values that the OP supports
Array of the scope values that the OP supports

_**affects**_: discovery, authorization, ID Token claims, Userinfo claims

Expand All @@ -1836,7 +1836,7 @@ _**default value**_:

### subjectTypes

List of the Subject Identifier types that this OP supports. Valid types are
Array of the Subject Identifier types that this OP supports. Valid types are
- `public`
- `pairwise`

Expand All @@ -1849,7 +1849,7 @@ _**default value**_:

### tokenEndpointAuthMethods

List of Client Authentication methods supported by this OP's Token Endpoint
Array of Client Authentication methods supported by this OP's Token Endpoint

_**affects**_: discovery, client authentication for token endpoint, registration and registration management

Expand All @@ -1861,6 +1861,41 @@ _**default value**_:
'client_secret_post',
'private_key_jwt' ]
```
<details>
<summary>(Click to expand) Supported values list
</summary>
<br>

```js
[
'none',
'client_secret_basic', 'client_secret_post',
'client_secret_jwt', 'private_key_jwt',
'tls_client_auth',
]
```
</details>
<details>
<summary>(Click to expand) Setting up tls_client_auth</summary>
<br>


To enable `tls_client_auth` the provider expects `x-ssl-client-verify` and `x-ssl-client-s-dn` headers to be presented by your TLS-offloading proxy with the variable values set by these proxies. An important aspect is to sanitize the inbound request headers at the proxy. <br/><br/> The most common openssl based proxies are Apache and NGINX, with those you're looking to use <br/><br/> __`SSLVerifyClient` (Apache) / `ssl_verify_client` (NGINX)__ `require` - if you only support tls_client_auth, `optional` if you also support additional non-MTLS based authentication methods, `optional_no_ca` - if you also support additional non-MTLS based authentication methods AND self_signed_tls_client_auth (not implemented yet) <br/><br/> __`SSLCACertificateFile` or `SSLCACertificatePath` (Apache) / `ssl_client_certificate` (NGINX)__ with the values pointing to your accepted CA Certificates <br/><br/> Set the proxy request headers with variables set as a result of enabling MTLS


```nginx
# NGINX
proxy_set_header x-ssl-client-verify $ssl_client_verify;
proxy_set_header x-ssl-client-s-dn $ssl_client_s_dn;
```
```apache
# Apache
RequestHeader set x-ssl-client-verify ""
RequestHeader set x-ssl-client-verify "%{SSL_CLIENT_VERIFY}s"
RequestHeader set x-ssl-client-s-dn ""
RequestHeader set x-ssl-client-s-dn "%{SSL_CLIENT_S_DN}s"
```
</details>

### ttl

Expand Down
2 changes: 1 addition & 1 deletion docs/update-configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const props = [
read.on('line', (line) => {
let strLine = line.trim();

if (strLine.endsWith('```js')) {
if (strLine.endsWith('```js') || strLine.endsWith('```apache') || strLine.endsWith('```nginx')) {
inTicks = true;
}

Expand Down
1 change: 1 addition & 0 deletions lib/consts/client_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const STRING = [
'request_object_signing_alg',
'sector_identifier_uri',
'subject_type',
'tls_client_auth_subject_dn',
'token_endpoint_auth_method',
'tos_uri',
'userinfo_encrypted_response_alg',
Expand Down
37 changes: 28 additions & 9 deletions lib/helpers/client_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ module.exports = function getSchema(provider) {
DEFAULT.subject_type = 'pairwise';
}

const tlsClientAuthEnabled = ['token', 'revocation', 'introspection']
.find(endpoint => configuration[`${endpoint}EndpointAuthMethods`].includes('tls_client_auth'));

if (tlsClientAuthEnabled) {
RECOGNIZED_METADATA.push('tls_client_auth_subject_dn');
}

if (features.introspection) {
RECOGNIZED_METADATA.push('introspection_endpoint_auth_method');
RECOGNIZED_METADATA.push('introspection_endpoint_auth_signing_alg');
Expand Down Expand Up @@ -228,17 +235,26 @@ module.exports = function getSchema(provider) {
return lengths;
}, new Set());

for (const endpoint of ['token', 'introspection', 'revocation']) { // eslint-disable-line no-restricted-syntax
if (
this[`${endpoint}_endpoint_auth_method`] === 'client_secret_jwt'
&& this[`${endpoint}_endpoint_auth_signing_alg`] === undefined
) {
const required = Math.max(...configuration[`${endpoint}EndpointAuthSigningAlgValues`]
.filter(alg => alg.startsWith('HS')).map(alg => parseInt(alg.slice(-3), 10)));
['token', 'introspection', 'revocation'].forEach((endpoint) => {
switch (this[`${endpoint}_endpoint_auth_method`]) {
case 'client_secret_jwt':
if (this[`${endpoint}_endpoint_auth_signing_alg`] === undefined) {
const required = Math.max(...configuration[`${endpoint}EndpointAuthSigningAlgValues`]
.filter(alg => alg.startsWith('HS')).map(alg => parseInt(alg.slice(-3), 10)));

hsLengths.add(required);
}
break;

hsLengths.add(required);
case 'tls_client_auth':
if (!this.tls_client_auth_subject_dn) {
invalidate('tls_client_auth_subject_dn must be provided for tls_client_auth');
}
break;

default:
}
}
});

const required = _.max(Array.from(hsLengths));

Expand Down Expand Up @@ -286,6 +302,9 @@ module.exports = function getSchema(provider) {
const requireJwks = this.token_endpoint_auth_method === 'private_key_jwt'
|| this.introspection_endpoint_auth_method === 'private_key_jwt'
|| this.revocation_endpoint_auth_method === 'private_key_jwt'
// || this.token_endpoint_auth_method === 'self_signed_tls_client_auth'
// || this.introspection_endpoint_auth_method === 'self_signed_tls_client_auth'
// || this.revocation_endpoint_auth_method === 'self_signed_tls_client_auth'
|| (requestSignAlgRequiringJwks.exec(this.request_object_signing_alg))
|| (encAlgRequiringJwks.exec(this.id_token_encrypted_response_alg))
|| (encAlgRequiringJwks.exec(this.userinfo_encrypted_response_alg))
Expand Down
16 changes: 12 additions & 4 deletions lib/helpers/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,24 @@ class Configuration {
}
}

/* eslint-disable no-restricted-syntax */
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'test') {
for (const flag in this.features) {
if (this.features[flag] && !STABLE_FLAGS.includes(flag)) {
Object.entries(this.features).forEach(([flag, value]) => {
if (value && !STABLE_FLAGS.includes(flag)) {
attention.info(`a draft/experimental feature (${flag}) enabled, future updates to \
this feature will be released as MINOR releases`);
}
});

/* eslint-disable no-restricted-syntax */
for (const endpoint of ['token', 'introspection', 'revocation']) {
if (this[`${endpoint}EndpointAuthMethods`].includes('tls_client_auth')) {
attention.info('a draft/experimental feature (tls_client_auth) enabled, future updates to this feature will be released as MINOR releases');
break;
}
}
/* eslint-enable */
}
/* eslint-enable */
}
}

Expand Down
69 changes: 60 additions & 9 deletions lib/helpers/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const DEFAULTS = {
/*
* claims
*
* description: List of the Claim Names of the Claims that the OpenID Provider MAY be able to
* description: Array of the Claim Names of the Claims that the OpenID Provider MAY be able to
* supply values for.
* affects: discovery, ID Token claim names, Userinfo claim names
*/
Expand Down Expand Up @@ -555,7 +555,7 @@ const DEFAULTS = {
/*
* prompts
*
* description: List of the prompt values that the OpenID Provider MAY be able to resolve
* description: Array of the prompt values that the OpenID Provider MAY be able to resolve
* affects: authorization
*/
prompts: ['consent', 'login', 'none'],
Expand All @@ -564,7 +564,7 @@ const DEFAULTS = {
/*
* responseTypes
*
* description: List of response_type values that OP supports
* description: Array of response_type values that OP supports
* affects: authorization, discovery, registration, registration management
*/
responseTypes: [
Expand Down Expand Up @@ -602,7 +602,7 @@ const DEFAULTS = {
/*
* scopes
*
* description: List of the scope values that the OP supports
* description: Array of the scope values that the OP supports
* affects: discovery, authorization, ID Token claims, Userinfo claims
*/
scopes: ['openid', 'offline_access'],
Expand All @@ -611,7 +611,7 @@ const DEFAULTS = {
/*
* dynamicScopes
*
* description: List of the dynamic scope values that the OP supports. These must be regular
* description: Array of the dynamic scope values that the OP supports. These must be regular
* expressions that the OP will check string scope values, that aren't in the static list,
* against.
* affects: discovery, authorization, ID Token claims, Userinfo claims
Expand All @@ -632,7 +632,7 @@ const DEFAULTS = {
/*
* subjectTypes
*
* description: List of the Subject Identifier types that this OP supports. Valid types are
* description: Array of the Subject Identifier types that this OP supports. Valid types are
* - `public`
* - `pairwise`
* affects: discovery, registration, registration management, ID Token and Userinfo sub claim
Expand Down Expand Up @@ -664,9 +664,56 @@ const DEFAULTS = {
/*
* tokenEndpointAuthMethods
*
* description: List of Client Authentication methods supported by this OP's Token Endpoint
* description: Array of Client Authentication methods supported by this OP's Token Endpoint
* affects: discovery, client authentication for token endpoint, registration and
* registration management
* example: Supported values list
* ```js
* [
* 'none',
* 'client_secret_basic', 'client_secret_post',
* 'client_secret_jwt', 'private_key_jwt',
* 'tls_client_auth',
* ]
* ```
* example: Setting up tls_client_auth
* To enable `tls_client_auth` the provider expects `x-ssl-client-verify` and `x-ssl-client-s-dn`
* headers to be presented by your TLS-offloading proxy with the variable values set by these
* proxies. An important aspect is to sanitize the inbound request headers at the proxy.
*
* <br/><br/>
*
* The most common openssl based proxies are Apache and NGINX, with those you're looking to use
*
* <br/><br/>
*
* __`SSLVerifyClient` (Apache) / `ssl_verify_client` (NGINX)__
* `require` - if you only support tls_client_auth,
* `optional` if you also support additional non-MTLS based authentication methods,
* `optional_no_ca` - if you also support additional non-MTLS based authentication methods AND self_signed_tls_client_auth (not implemented yet)
*
* <br/><br/>
*
* __`SSLCACertificateFile` or `SSLCACertificatePath` (Apache) / `ssl_client_certificate` (NGINX)__ with the values pointing to your accepted CA Certificates
*
* <br/><br/>
*
* Set the proxy request headers with variables set as a result of enabling MTLS
*
* ```nginx
* # NGINX
* proxy_set_header x-ssl-client-verify $ssl_client_verify;
* proxy_set_header x-ssl-client-s-dn $ssl_client_s_dn;
* ```
*
* ```apache
* # Apache
* RequestHeader set x-ssl-client-verify ""
* RequestHeader set x-ssl-client-verify "%{SSL_CLIENT_VERIFY}s"
* RequestHeader set x-ssl-client-s-dn ""
* RequestHeader set x-ssl-client-s-dn "%{SSL_CLIENT_S_DN}s"
* ```
*
*/
tokenEndpointAuthMethods: [
'none',
Expand Down Expand Up @@ -1559,7 +1606,9 @@ const DEFAULTS = {
/*
* introspectionEndpointAuthMethods
*
* description: List of Client Authentication methods supported by this OP's Introspection Endpoint
* description: Array of Client Authentication methods supported by this OP's Introspection Endpoint.
* If no configuration value is provided the same values as for tokenEndpointAuthMethods will be
* used. Supported values list is the same as for tokenEndpointAuthMethods.
* affects: discovery, client authentication for introspection, registration and registration
* management
*/
Expand All @@ -1568,7 +1617,9 @@ DEFAULTS.introspectionEndpointAuthMethods = DEFAULTS.tokenEndpointAuthMethods;
/*
* revocationEndpointAuthMethods
*
* description: List of Client Authentication methods supported by this OP's Revocation Endpoint
* description: Array of Client Authentication methods supported by this OP's Revocation Endpoint.
* If no configuration value is provided the same values as for tokenEndpointAuthMethods will be
* used. Supported values list is the same as for tokenEndpointAuthMethods.
* affects: discovery, client authentication for revocation, registration and registration
* management
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/models/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { LOOPBACKS } = require('../consts/client_attributes');
const KEY_ATTRIBUTES = ['crv', 'e', 'kid', 'kty', 'n', 'use', 'x', 'y'];
const KEY_TYPES = ['RSA', 'EC'];

const nonSecretAuthMethods = ['private_key_jwt', 'none'];
const nonSecretAuthMethods = ['private_key_jwt', 'none', 'tls_client_auth'];
const clientEncryptions = [
'id_token_encrypted_response_alg',
'request_object_encryption_alg',
Expand Down
Loading

0 comments on commit ce2bf66

Please sign in to comment.