Skip to content

Commit

Permalink
✨ Let DPoP draft standard supplant client_signing_key (#10)
Browse files Browse the repository at this point in the history
### MINOR Update: Explicitly supporting [IETF DPoP Draft Proposal](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop)

* This proposal aligns our Proof of Possession of the access token with the current IETF DPoP draft proposal. This includes:
   * Deprecating storing the entire key in the `tdf_claims.client_public_signing_key` subclaim
   * Store a SHA-256 hash of the key in the `cnf.jkt` field, per the current draft proposal
   * Submit a DPoP in the `DPoP` header, which will include the complete key and request identifying information
* Not included:
  * To get complete support for the missing functionality we may desire to add a body hash to the DPoP claims list, which will provides an additional check for partial message integrity.


A reference implementation of this change is now included with opentdf/backend and opentdf/client-web. If accepted we will port this to the remaining opentdf/client-*.


#### Reference Implementation Details

##### Client Implementation

For the client, we use [an existing DPoP implementation](https://www.npmjs.com/package/dpop) to generate proofs. To use, we added a parameter (dpopEnabled) to the clients, and if present make this required.

Implementation: opentdf/client-web#118

##### Identity Provider Implementation (Access Token Generation)

Our reference IdP, Keycloak, provides a java plugin environment for adding claims to access tokens. Our current reference implementation extended our existing plugin, which adds the `tdf_claims` claim, to include the `cnf.jkt` claim when there is a DPoP header in the code exchange.

Reference: https://github.com/opentdf/backend/blob/9603538287e23240c6d40ea596cf42f8b26bfcc4/containers/keycloak-protocol-mapper/custom-mapper/src/main/java/com/virtru/keycloak/TdfClaimsMapper.java#L292

##### Access Point Validation (KAS)

KAS currently uses application level validation of the OIDC header. We extended this to include the DPoP check iff the access token includes the `jkt` claim.

https://github.com/opentdf/backend/blob/9603538287e23240c6d40ea596cf42f8b26bfcc4/containers/kas/kas_core/tdf3_kas_core/dpop.py#L55


Co-authored-by: Ben Leggett <854255+bleggett@users.noreply.github.com>
Co-authored-by: b-long <b-long@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 4, 2023
1 parent 4a4215a commit c6592ba
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 30 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.1.0
4.2.0
56 changes: 29 additions & 27 deletions protocol/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Protocol
# Open TDF Protocol

This document describes the canonical system architecture used to encrypt and decrypt TDF ciphertext.

Expand All @@ -8,46 +8,43 @@ The canonical architecture contains four major components.

* *TDF Client* - Initiates and drives the TDF encryption and decryption workflows. Only component with access to the content (ciphertext or plaintext).
* May be entitled as Non-Person Entity acting on behalf of itself, OR on behalf of a Person Entity.
* *OpenID Connect (OIDC) Identity Provider (IdP)* - This system could be any OIDC IdP software. OpenTDF has chosen Keycloak as its reference implementation IdP.
* From Wikipedia: "Keycloak is an open source software product to allow single sign-on with Identity and Access Management aimed at modern applications and services. As of March 2018 this JBoss community project is under the stewardship of Red Hat. Keycloak is licensed under Apache 2.0."
* Any OIDC-compliant IdP software may be used, provided it supports custom claims, and can:
* Read the TDF Client public key from a custom HTTP header sent with the OIDC authentication request.
* Construct and send an Attribute Provider web service request, including the public key in the payload.
* Return the resulting Claims Object in the signed IdP JWT.
* A list of Certified OpenID Connect applications can be found at: https://openid.net/developers/certified/
* *OpenTDF Protocol Mapper* (PM) is OpenTDF's Keycloak-specific reference implementation of the above functionality.
* *Attribute Provider* (AP) - A web service that receives requests which contain information about the authenticated entities from an OIDC IdP with custom claims support (ex: Keycloak with OpenTDF Protocol Mapper), and returns custom TDF OIDC claims in response. It is the responsibility of Attribute Provider to transform incoming 3rd party IdP claims/metadata to a set of outgoing [Attribute Objects](../schema/AttributeObject.md). It returns a TDF [Claims Object](../schema/ClaimsObject.md).
* *Key Access Service* (KAS) - Responsible for authorizing and granting TDF Clients access to rewrapped data key material. If authorized, TDF Clients (on behalf of themselves, or other entities) can use this rewrapped data key to decrypt TDF ciphertext. A valid OIDC token containing [TDF Claims](../schema/ClaimsObject.md) must be used as a bearer token when communicating with KAS. KAS will verify the authenticity of the bearer token, the request signature, and then the policy claims within that bearer token. An otherwise valid and trusted OIDC token without valid TDF Claims will be rejected.
* *OpenID Connect (OIDC) Identity Provider (IdP)* - This system could be any OIDC IdP software. Any OIDC-compliant IdP software may be used, provided it supports custom claims and *Demonstration of Proof of Possession (DPoP)*.
* Supporting this protocol must include:
* Obtaining a session-bound TDF Client public signing key. This may be via a client side channel, or an addditional request. Implements may choose a custom HTTP header sent with the OIDC authentication request, or an identity provider (IdP) web hook that fires during a token or code exchange.
* Constructing and sending a web service request to an ABAC entitlement PDP (e.g. the OpenTDF Entitlement PDP), including sufficient information to identify all entities requesting the token grant, and any additional IdP context data for those entities.
* Including within the returned signed access token both the evidence of the client's public key in the `cnf` claim, and the entitlements in the `tdf_claims`.[^client_public_signing_key]
* A list of Certified OpenID Connect applications can be found at: https://openid.net/developers/certified/ OpenTDF has chosen [Keycloak] as its reference implementation IdP. [^why-keycloak]
* *Entitlement Policy Decision Point (PDP)* (AP) - A web service that receives requests which contain information about the authenticated entities from an OIDC IdP with custom claims support (e.g. Keycloak with OpenTDF Protocol Mapper), and returns custom TDF OIDC claims in response. It is the responsibility of Entitlement PDP to transform incoming 3rd party IdP claims/metadata to a set of outgoing [Attribute Objects](../schema/AttributeObject.md). It returns a TDF [Claims Object](../schema/ClaimsObject.md).
* *Key Access Service* (KAS) - Responsible for authorizing and granting TDF Clients access to rewrapped data key material. If authorized, TDF Clients (on behalf of themselves, or other entities) can use this rewrapped data key to decrypt TDF ciphertext. A valid OIDC token containing [`tdf_claims`](../schema/ClaimsObject.md) and [`dpop` (Demonstration Proof of Possession)](../schema/ProofOfPossession.md) must be used as a bearer token when communicating with KAS. KAS will verify the authenticity of the bearer token, the request signature, and then the policy claims within that bearer token. An otherwise valid and trusted OIDC token without valid TDF Claims will be rejected.

## General Authentication Protocol

OIDC Auth with a PoP scheme is used for **all** TDF Client interactions with backend TDF services:
OIDC Auth with a DPoP (Demonstration Proof of Possession) scheme is used for **all** TDF Client interactions with backend TDF services:

1. The TDF Client requests an OIDC Bearer Token (either on behalf of itself, or another entity)
by first authenticating via the OpenID Connect (OIDC) Identity Provider (IdP) with Custom Claims
support (in this example, Keycloak). As part of this authentication process, the TDF Client **must** convey its signing public key to the IdP.
* If the TDF Client public signing key is rotated or changed, a new OIDC Bearer Token must be obtained from the IdP, containing the TDF Client's new public signing key.
* It should be assumed that the TDF Client's signing keypair is ephemeral, and that the TDF Client's _private_ signing key is known only to the TDF Client.
support (in this example, Keycloak). As part of this authentication process, the TDF Client **must** convey a DPoP key to the IdP.
* To change (or rotate) its DPoP key, a client must obtain a new OIDC access token from the IdP containing its own new public signing key.
* The TDF Client's signing keypair is ephemeral and the _private_ signing key must be known only to the TDF Client.
* Measures should be taken to protect all TDF Client private keys, but the mechanisms for doing so are outside the scope of this spec.

1. If entity authentication succeeds, a
[TDF Claims Object](../schema/ClaimsObject.md) is obtained from
Attribute Provider and signed by the IdP. The signing public key previously conveyed by the TDF Client is embedded within the [TDF Claims Object](../schema/ClaimsObject.md).
Entitlement PDP.
A hash of the signing public key is embedded within the [JWT's `cnf.jkt` claim](../schema/ProofOfPossession.md).
The signed OIDC Bearer Token is then returned to the TDF Client, containing the complete [TDF Claims Object](../schema/ClaimsObject.md).
* The [TDF Claims Object](../schema/ClaimsObject.md) contains one or more [Entitlement Objects](EntitlementObject.md) entitling all entities
involved in the authentication request.

1. The TDF Client must convey the IdP-signed OIDC Bearer Token to backend services with all requests, and in addition, the TDF Client **must** sign all requests to backend services with its _private signing key_
* The request signature should be a signature of the entire request body, sans the request signature itself.
* For HTTPS, it should be considered best practice to insert the request signature itself in the request body rather than send it as a custom header.

1. Backend services are required to:
1. The TDF Client must convey the IdP-signed OIDC Bearer Token as a JWT to backend services with all requests, and a DPoP proof generated from its _private signing key_
* The DPoP proof claims must include a `body_hash` claim, which is a hash of the request body.
1. All backend services are required to _minimally_:
* Validate AuthN:
* Examine the validity of the OIDC Bearer Token signature by contacting the issuing IdP.
* Validate that the [TDF Claims Object](../schema/ClaimsObject.md) contains a TDF Client public signing key.
* Validate that the request signature in the TDF Client's payload **can be validated** with the TDF Client's public signing key embedded in the OIDC Bearer Token associated with the signed request
* Validate AuthZ (if necessary)
* Determine if all the entities entitled in the presented bearer token have all the required Attributes for a given operation, as per service requirements.
* Examine the validity of the OIDC Bearer Token signature and other assertions by contacting the issuing IdP.
* Validate the [`DPoP`](../schema/ProofOfPossession.md) header
1. Backend services acting as Access PEPs must _additionally_ validate AuthZ:
* Validate that the access token contains a valid [TDF Claims Object](../schema/ClaimsObject.md) under the `tdf_claims` key,
* Validate AuthZ by presenting the attributes for all authenticated entities to an Access PDP.

If these requirements are met, a TDF Client may be considered authenticated and authorized.

Expand All @@ -61,7 +58,7 @@ _Offline mode_ requires that the TDF Client previously obtained a
long-lived TDF JWT with embedded Claims Object via
online OIDC authentication. Or, the TDF Client has otherwise obtained
a valid signed JWT with embedded Claims Object through offline means
(ex: generated by a script using the IdP signing key). TDF Clients
(e.g. generated by a script using the IdP signing key). TDF Clients
running in this mode commit the [authorization
policy](../schema/PolicyObject.md) out-of-band, or when decrypt is
first performed. This significantly reduces latency at the cost of a
Expand All @@ -84,3 +81,8 @@ information must be included).

![IdP Brokered Authentication](../diagrams/OIDC_brokered_auth.png)

[Keycloak]: https://www.keycloak.org/

[^client_public_signing_key]: Clients in version [4..4.2) may include the signing key in the `tdf_claims` object in the `client_public_signing_key` field, instead of the `cnf`.

[^why-keycloak]: [From Wikipedia](https://en.wikipedia.org/wiki/Keycloak): "Keycloak is an open source software product to allow single sign-on with Identity and Access Management aimed at modern applications and services. As of March 2018 this JBoss community project is under the stewardship of Red Hat. Keycloak is licensed under Apache 2.0."
3 changes: 1 addition & 2 deletions schema/ClaimsObject.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,12 @@ If these requirements are met, the KAS will permit a decrypt of the file.
]
}
],
"client_public_signing_key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy18Efi6+3vSELpbK58gC\nA9vJxZtoRHR604yi707h6nzTsTSNUg5mNzt/nWswWzloIWCgA7EPNpJy9lYn4h1Z\n6LhxEgf0wFcaux0/C19dC6WRPd6 ... XzNO4J38CoFz/\nwwIDAQAB\n-----END PUBLIC KEY-----",
"tdf_spec_version:":"x.y.z"
}
```

| Parameter | Type | Description | Required? |
|-----------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
| `entitlements` | Array | An array of [Entitlement Objects](EntitlementObject.md) for each entity (PE or NPE) involved in the authentication request. | Yes |
| `client_public_signing_key` | String | The TDF Client's public signing key, in a PEM-encoded format. | Yes |
| `tdf_spec_version` | String | Semver version number of the TDF spec. | No |
| `client_public_signing_key` | String | [DEPRECATED] The TDF Client's public signing key, in a PEM-encoded format. Replaced by cnf claim and the DPoP protocol. | No |
34 changes: 34 additions & 0 deletions schema/ProofOfPossession.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Proof of Possession

In order to allow extended offline flows, and to deal with potential leaks of bearer tokens,
we enforce the use of proof of token possession, as currently described in
[this IETF draft](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop).

## What is this?

The proof of possession works by having the client generate an ephemeral key pair, which it
then associates with an access_token via a code or token exchange. This produces
an access token with an explicit binding (via the `cnf` claim) to the public
key of the value. Then, the client can issue DPoP proofs that will be allow servers
to trust that the client has possession of the private key.

## How does it work?

1. TDF clients may request an OIDC requests an OIDC Bearer Token (either on behalf of itself, or another entity)
by first authenticating via the
OpenID Connect (OIDC) Identity Provider (IdP),
passing along a signing key with the request.
In some scenarios, a key may be added using an exchange or refresh.

2. The client requests a decrypt from the Key Access Server (KAS),
presenting its annotated JWT with PoP proof and entitlement claims in the access token.

## Example

```javascript
TK
```

## DEPRECATED: Validating with the `client_public_signing_key`

Previously (<4.2) we stored the entire public key within the `tdf_claims` as the client_public_signing_key and used that to sign the request body. IdPs *may* include both the `cnf` hash of the public key and the `client_public_signing_key` subclaim for compatibility with older KAS services.

0 comments on commit c6592ba

Please sign in to comment.