Skip to content

Commit

Permalink
feat: add non-repudiation signature validation methods
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Sep 15, 2024
1 parent 9c3f1ed commit 0916de2
Show file tree
Hide file tree
Showing 14 changed files with 328 additions and 81 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ jobs:

# FAPI 2.0 Message Signing ID1
- plan: fapi2-message-signing-id1-client-test-plan
- plan: fapi2-message-signing-id1-client-test-plan
variant:
fapi_client_type: 'plain_oauth'

steps:
- name: Checkout
Expand Down
17 changes: 15 additions & 2 deletions conformance/fapi/invalid-signature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { test, skippable, flow, modules } from '../runner.js'
import {
test,
skippable,
rejects,
flow,
modules,
nonRepudiation,
plan,
variant,
} from '../runner.js'

for (const module of modules('invalid-signature')) {
test.serial(skippable(flow()), module)
if (nonRepudiation(plan, variant)) {
test.serial(rejects(flow()), module, 'JWT signature verification failed')
} else {
test.serial(skippable(flow()), module)
}
}
11 changes: 11 additions & 0 deletions conformance/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ function usesPar(plan: Plan) {
return plan.name.startsWith('fapi2') || variant.fapi_auth_request_method === 'pushed'
}

export function nonRepudiation(plan: Plan, variant: Record<string, string>) {
return (
variant.fapi_client_type === 'oidc' &&
(plan.name.startsWith('fapi2-message-signing') || plan.name.startsWith('fapi1'))
)
}

function usesRequestObject(planName: string, variant: Record<string, string>) {
if (planName.startsWith('fapi1')) {
return true
Expand Down Expand Up @@ -480,6 +487,10 @@ export const flow = (options?: MacroOptions) => {
}
}

if (nonRepudiation(plan, variant)) {
await oauth.validateIdTokenSignature(as, result)
}

t.log('token endpoint response body', { ...result })
;({ access_token } = result)
if (result.id_token) {
Expand Down
8 changes: 6 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ Support from the community to continue maintaining and improving this module is
- [processUserInfoResponse](functions/processUserInfoResponse.md)
- [userInfoRequest](functions/userInfoRequest.md)
- [validateAuthResponse](functions/validateAuthResponse.md)
- [validateIdTokenSignature](functions/validateIdTokenSignature.md)
- [validateJwtAuthResponse](functions/validateJwtAuthResponse.md)
- [validateJwtUserinfoSignature](functions/validateJwtUserinfoSignature.md)

## Authorization Server Metadata

Expand Down Expand Up @@ -65,6 +67,7 @@ Support from the community to continue maintaining and improving this module is
## FAPI 1.0 Advanced

- [validateDetachedSignatureResponse](functions/validateDetachedSignatureResponse.md)
- [validateIdTokenSignature](functions/validateIdTokenSignature.md)

## JWT Access Tokens

Expand All @@ -87,6 +90,7 @@ Support from the community to continue maintaining and improving this module is

- [processUserInfoResponse](functions/processUserInfoResponse.md)
- [userInfoRequest](functions/userInfoRequest.md)
- [validateJwtUserinfoSignature](functions/validateJwtUserinfoSignature.md)

## Proof Key for Code Exchange (PKCE)

Expand All @@ -113,6 +117,7 @@ Support from the community to continue maintaining and improving this module is
- [isOAuth2Error](functions/isOAuth2Error.md)
- [parseWwwAuthenticateChallenges](functions/parseWwwAuthenticateChallenges.md)
- [processIntrospectionResponse](functions/processIntrospectionResponse.md)
- [validateJwtIntrospectionSignature](functions/validateJwtIntrospectionSignature.md)

## Token Revocation

Expand Down Expand Up @@ -169,9 +174,8 @@ Support from the community to continue maintaining and improving this module is
- [UserInfoAddress](interfaces/UserInfoAddress.md)
- [UserInfoRequestOptions](interfaces/UserInfoRequestOptions.md)
- [UserInfoResponse](interfaces/UserInfoResponse.md)
- [ValidateDetachedSignatureResponseOptions](interfaces/ValidateDetachedSignatureResponseOptions.md)
- [ValidateJWTAccessTokenOptions](interfaces/ValidateJWTAccessTokenOptions.md)
- [ValidateJwtAuthResponseOptions](interfaces/ValidateJwtAuthResponseOptions.md)
- [ValidateSignatureOptions](interfaces/ValidateSignatureOptions.md)
- [WWWAuthenticateChallenge](interfaces/WWWAuthenticateChallenge.md)
- [WWWAuthenticateChallengeParameters](interfaces/WWWAuthenticateChallengeParameters.md)

Expand Down
2 changes: 1 addition & 1 deletion docs/functions/validateDetachedSignatureResponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ responses.
| `expectedNonce` | `string` | Expected ID Token `nonce` claim value. |
| `expectedState`? | `string` \| *typeof* [`expectNoState`](../variables/expectNoState.md) | Expected `state` parameter value. Default is [expectNoState](../variables/expectNoState.md). |
| `maxAge`? | `number` \| *typeof* [`skipAuthTimeCheck`](../variables/skipAuthTimeCheck.md) | ID Token [`auth_time`](../interfaces/IDToken.md#auth_time) claim value will be checked to be present and conform to the `maxAge` value. Use of this option is required if you sent a `max_age` parameter in an authorization request. Default is [`client.default_max_age`](../interfaces/Client.md#default_max_age) and falls back to [skipAuthTimeCheck](../variables/skipAuthTimeCheck.md). |
| `options`? | [`ValidateDetachedSignatureResponseOptions`](../interfaces/ValidateDetachedSignatureResponseOptions.md) | - |
| `options`? | [`ValidateSignatureOptions`](../interfaces/ValidateSignatureOptions.md) | - |

## Returns

Expand Down
38 changes: 38 additions & 0 deletions docs/functions/validateIdTokenSignature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Function: validateIdTokenSignature()

[💗 Help the project](https://github.com/sponsors/panva)

Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).

***

**validateIdTokenSignature**(`as`, `ref`, `options`?): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\>

Validates the JWS Signature of an ID Token included in results previously resolved from
[processAuthorizationCodeOpenIDResponse](processAuthorizationCodeOpenIDResponse.md), [processRefreshTokenResponse](processRefreshTokenResponse.md), or
[processDeviceCodeResponse](processDeviceCodeResponse.md) for non-repudiation purposes.

Note: Validating signatures of ID Tokens received via direct communication between the Client and
the Token Endpoint (which it is here) is not mandatory since the TLS server validation is used to
validate the issuer instead of checking the token signature. You only need to use this method for
non-repudiation purposes.

Note: Supports only digital signatures.

## Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `as` | [`AuthorizationServer`](../interfaces/AuthorizationServer.md) | Authorization Server Metadata. |
| `ref` | [`TokenEndpointResponse`](../interfaces/TokenEndpointResponse.md) \| [`OpenIDTokenEndpointResponse`](../interfaces/OpenIDTokenEndpointResponse.md) | Value previously resolved from [processAuthorizationCodeOpenIDResponse](processAuthorizationCodeOpenIDResponse.md), [processRefreshTokenResponse](processRefreshTokenResponse.md), or [processDeviceCodeResponse](processDeviceCodeResponse.md). |
| `options`? | [`ValidateSignatureOptions`](../interfaces/ValidateSignatureOptions.md) | - |

## Returns

[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\>

Resolves if the signature validates, rejects otherwise.

## See

[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation)
2 changes: 1 addition & 1 deletion docs/functions/validateJwtAuthResponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Same as [validateAuthResponse](validateAuthResponse.md) but for signed JARM resp
| `client` | [`Client`](../interfaces/Client.md) | Client Metadata. |
| `parameters` | [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) \| [`URL`](https://developer.mozilla.org/docs/Web/API/URL) | JARM authorization response. |
| `expectedState`? | `string` \| *typeof* [`expectNoState`](../variables/expectNoState.md) \| *typeof* [`skipStateCheck`](../variables/skipStateCheck.md) | Expected `state` parameter value. Default is [expectNoState](../variables/expectNoState.md). |
| `options`? | [`ValidateJwtAuthResponseOptions`](../interfaces/ValidateJwtAuthResponseOptions.md) | - |
| `options`? | [`ValidateSignatureOptions`](../interfaces/ValidateSignatureOptions.md) | - |

## Returns

Expand Down
37 changes: 37 additions & 0 deletions docs/functions/validateJwtIntrospectionSignature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Function: validateJwtIntrospectionSignature()

[💗 Help the project](https://github.com/sponsors/panva)

Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).

***

**validateJwtIntrospectionSignature**(`as`, `ref`, `options`?): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\>

Validates the JWS Signature of an JWT [Response](https://developer.mozilla.org/docs/Web/API/Response) body of responses previously processed by
[processIntrospectionResponse](processIntrospectionResponse.md) for non-repudiation purposes.

Note: Validating signatures of JWTs received via direct communication between the Client and a
TLS-secured Endpoint (which it is here) is not mandatory since the TLS server validation is used
to validate the issuer instead of checking the token signature. You only need to use this method
for non-repudiation purposes.

Note: Supports only digital signatures.

## Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `as` | [`AuthorizationServer`](../interfaces/AuthorizationServer.md) | Authorization Server Metadata. |
| `ref` | [`Response`](https://developer.mozilla.org/docs/Web/API/Response) | Response previously processed by [processIntrospectionResponse](processIntrospectionResponse.md). |
| `options`? | [`ValidateSignatureOptions`](../interfaces/ValidateSignatureOptions.md) | - |

## Returns

[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\>

Resolves if the signature validates, rejects otherwise.

## See

[draft-ietf-oauth-jwt-introspection-response-12 - JWT Response for OAuth Token Introspection](https://www.ietf.org/archive/id/draft-ietf-oauth-jwt-introspection-response-12.html#section-5)
37 changes: 37 additions & 0 deletions docs/functions/validateJwtUserinfoSignature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Function: validateJwtUserinfoSignature()

[💗 Help the project](https://github.com/sponsors/panva)

Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).

***

**validateJwtUserinfoSignature**(`as`, `ref`, `options`?): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\>

Validates the JWS Signature of a JWT [Response](https://developer.mozilla.org/docs/Web/API/Response) body of response previously processed by
[processUserInfoResponse](processUserInfoResponse.md) for non-repudiation purposes.

Note: Validating signatures of JWTs received via direct communication between the Client and a
TLS-secured Endpoint (which it is here) is not mandatory since the TLS server validation is used
to validate the issuer instead of checking the token signature. You only need to use this method
for non-repudiation purposes.

Note: Supports only digital signatures.

## Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `as` | [`AuthorizationServer`](../interfaces/AuthorizationServer.md) | Authorization Server Metadata. |
| `ref` | [`Response`](https://developer.mozilla.org/docs/Web/API/Response) | Response previously processed by [processUserInfoResponse](processUserInfoResponse.md). |
| `options`? | [`ValidateSignatureOptions`](../interfaces/ValidateSignatureOptions.md) | - |

## Returns

[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\>

Resolves if the signature validates, rejects otherwise.

## See

[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo)
59 changes: 0 additions & 59 deletions docs/interfaces/ValidateDetachedSignatureResponseOptions.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Interface: ValidateJwtAuthResponseOptions
# Interface: ValidateSignatureOptions

[💗 Help the project](https://github.com/sponsors/panva)

Expand Down
Loading

0 comments on commit 0916de2

Please sign in to comment.