Skip to content

Commit

Permalink
docs: client key updates (#516)
Browse files Browse the repository at this point in the history
Updates made to support Rafiki doc issue #2966
  • Loading branch information
melissahenderson authored Nov 1, 2024
1 parent 9e71cd4 commit f259f30
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 51 deletions.
114 changes: 77 additions & 37 deletions docs/src/content/docs/introduction/client-keys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@
title: Client keys
---

import { CodeBlock, LinkOut } from '@interledger/docs-design-system'
import {
CodeBlock,
LinkOut,
Mermaid,
MermaidWrapper
} from '@interledger/docs-design-system'

All client requests in Open Payments are signed using a unique key that identifies the client to the authorization and resource servers. All requests, except for new grant requests, also carry an access token that is bound to the key.

## Key registry

A key registry is a list of keys associated with clients that require access to protected Open Payments resources. Key registries are publicly exposed via a `jwks.json` endpoint and allows servers to verify that a client is who it says it is.
A key registry is a list of keys, generated and stored by the client, for when the client requires access to protected Open Payment resources. Since grant requests are completed over multiple signed HTTP requests, it's important for the client to provide a way to consistently identify itself across these requests to the authorization server. The key registry allows the authorization server to verify that the client is who it says it is.

Each client is represented by a [wallet address](/introduction/wallet-addresses). When the client generates an asymmetric key pair, the public key is added to a key registry while the private key is stored by the client. Servers can then retrieve the client's key registry by accessing `WALLET_ADDRESS/jwks.json`. For example, `https://wallet.example.com/alice/jwks.json`.
Each client is represented by a [wallet address](/introduction/wallet-addresses). A client's key registry is publicly accessible through its wallet address via a `jwks.json` endpoint. An authorization server can retrieve the client's key registry by accessing `WALLET_ADDRESS/jwks.json`.

<CodeBlock title='Example'>
``` https://wallet.example.com/alice/jwks.json ```
</CodeBlock>

### Registry structure

A key registry's JWKS document must contain the following fields and values.
The key registry must expose public keys in the form of JSON Web Key Sets (JWKS). The keys must be generated using the `Ed25519` algorithm and the resultant JWKS document must contain the following fields and values.

```
{
Expand All @@ -24,7 +33,7 @@ A key registry's JWKS document must contain the following fields and values.
}
```

Additionally, the JWK must contain the `x` and `kid` (key ID) fields for the specific client to identify the client in a signature.
Additionally, it must contain the `x` and `kid` (key ID) fields for the specific client to identify itself in a signature.

<CodeBlock title="Example: https://wallet.example.com/alice/jwks.json">
```json
Expand All @@ -42,18 +51,49 @@ Additionally, the JWK must contain the `x` and `kid` (key ID) fields for the spe
```
</CodeBlock>

### Proofing method
### Key generation

Before a client can request a grant for the first time, it must:

1. Generate an asymmetric key pair. Keys must be generated using the <LinkOut href='https://datatracker.ietf.org/doc/html/rfc8032'>ed25519 algorithm</LinkOut>.
:::note
A `keyId` must be generated to identify the pair; however, this is usually the responsibility of the account servicing entity (ASE).
:::
2. Add the public key to its key registry.
3. Store the private key. The private key is used to sign the payload described in the [key proofing method](#key-proofing-method) section below.

## Client requests

Since client requests are completed over multiple signed HTTP requests, it's important for a client to provide a way to consistently identify itself across these requests. As such, clients must include the following when making requests:

The security of client requests follows a profile of the mechanism defined in the <LinkOut href='https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#name-securing-requests-from-the-'>GNAP specification</LinkOut>.
- Headers
- A `Signature-Input` header that includes the `keyId` associated with the client's key pair. This header is a comma-separated list of headers that map to values in the data that was signed.
- A `signature` header generated based on the `Signature-Input`, using the `EdDSA` signing algorithm
- Body
- A `client` property containing the client’s wallet address

Securing client requests follows a profile of what's defined in the <LinkOut href='https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#name-securing-requests-from-the-'>GNAP specification</LinkOut>.

:::note
Open Payments **does not** support bearer tokens.
:::

Open Payments uses the <LinkOut href='https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#name-http-message-signatures'>HTTP message signatures</LinkOut> key proofing method. Clients typically secure their requests to servers by presenting an access token and proof of a key it possesses. The exception is for calls to an authorization server to initiate a grant. In this case, a key proof is used with no access token and is a non-authorized signed request.
### Grant requests

Upon receiving a signed grant request, the authorization server obtains the client’s domain from the `client` property. The authorization server binds the domain to the grant in order to use the domain to acquire the key set for subsequent grant requests.

The authorization server then acquires the client's key registry by making a `GET` request to the client’s JWKS endpoint at `WALLET_ADDRESS/jwks.json`. When the authorization server locates the public key containing the `keyId` included in the request's `Signature-Input` header, the authorization server uses the key to decrypt and validate the request's signature. This binds the client to the grant and allows the authorization server to continue with the grant request.

## Key proofing method

### HTTP message signatures

Open Payments uses the <LinkOut href='https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#name-http-message-signatures'>HTTP message signatures</LinkOut> (`httpsig`) key proofing method.

The `httpsig` proofing method must be declared as part of the key material when directly using a key to request a grant. The key material below is for illustrative purposes. In Open Payments, it's expected that the wallet address be used in the grant request.

<CodeBlock title="Example">

```json
"key": {
"proof": "httpsig",
Expand All @@ -67,33 +107,33 @@ The `httpsig` proofing method must be declared as part of the key material when
}
```

When using `httpsig`, the signer creates an HTTP Message Signature as described in the <LinkOut href='https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures'>HTTP Message Signatures specification</LinkOut>. Review the [HTTP message signatures](/introduction/http-signatures) page for more information about signatures as they relate to Open Payments.

## Key generation

Before a client can request a grant for the first time, it must:

1. Generate an asymmetric key pair
:::note
A `keyId` must be generated to identify the pair; however, this is usually the responsibility of the account servicing entity (ASE).
:::
2. Add the public key to the key registry
3. Store the private key

Keys must be generated using the <LinkOut href='https://datatracker.ietf.org/doc/html/rfc8032'>ed25519 algorithm</LinkOut>.

## Client requests

Since client requests are completed over multiple signed HTTP requests, it's important for a client to provide a way to consistently identify itself across these requests. As such, clients must include the following when making requests:

- Header
- `Signature-Input` that includes the `keyId` associated with the client's key pair
- `signature` generated based on the `Signature-Input`, using the `EdDSA` signing algorithm
- Body
- `client` property containing the client’s wallet address

### Grant requests

Upon receiving a signed grant request, the AS obtains the client’s domain from the `client` property. The AS binds the domain to the grant in order to use it to acquire the key set for subsequent grant requests.
</CodeBlock>

The AS then acquires the client's key registry by making a `GET` request to the client’s JWKS endpoint at `WALLET_ADDRESS/jwks.json`. When it locates the public key containing the `keyId` that was included in the request's `Signature-Input`, the AS uses the key to decrypt and validate the requests signature. This binds the client to the grant and allows the AS to continue with the grant request.
When using `httpsig`, the signer (the client) creates an HTTP message signature. Open Payments clients typically secure their requests to servers by presenting an access token and proof of a key it possesses. The exception is for calls to an authorization server to initiate a grant. In this case, a key proof is used with no access token and is a non-authorized signed request.

See the [HTTP message signatures](/introduction/http-signatures) page for more information specific to Open Payments. Additional information is found in the <LinkOut href='https://datatracker.ietf.org/doc/html/rfc9421'>specification</LinkOut> for HTTP message signatures.

## Sequence diagram

<MermaidWrapper client:load>

{/* prettier-ignore */}
<Mermaid
graph={`sequenceDiagram
Client->>Client: Generate keys, add public key to registry
Client->>Authorization server: Make initial grant request (POST /)<br/> and sign request with private key
Authorization server->>Authorization server: Pull the keyId from the grant request's signature-input header, <br/>get client domain from initial grant request's body
Authorization server->>Client: GET {client_domain/jwks.json}
Client->>Authorization server: Return registry's public key
Authorization server->>Authorization server: Find the registry key with the matching keyId, <br/>validate signature with key, then bind client to grant
Authorization server->>Client: Complete initial grant request
Client->>Authorization server: Make grant continuation request (POST /continue), <br/>sign with private key
Authorization server->>Authorization server: Pull the keyId from the grant request's signature-input header, <br/>get client domain from the database entry for the grant
Authorization server->>Client: GET {client_domain/jwks.json}
Client->>Authorization server: Return registry's public key
Authorization server->>Authorization server: Find the registry key with the matching keyId, <br/>validate signature with key
Authorization server->>Client: Complete grant continuation request
`}
/>

</MermaidWrapper>
22 changes: 8 additions & 14 deletions docs/src/content/docs/introduction/http-signatures.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CodeBlock, LinkOut } from '@interledger/docs-design-system'

HTTP message signatures are cryptographic digital signatures used by the Open Payments APIs to secure HTTP messages exchanged between sender, receiver, or third-party initiating payment systems.

The Open Payments APIs implement the <LinkOut href='https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol-16#section-7.3.1'> HTTP Signatures </LinkOut> section of the GNAP (Grant Negotiation and Authorization Protocol) specification.
The Open Payments APIs implement the <LinkOut href='https://datatracker.ietf.org/doc/html/rfc9635#name-http-message-signatures'> HTTP Signatures </LinkOut> section of the GNAP (Grant Negotiation and Authorization Protocol) specification.

## Purpose

Expand Down Expand Up @@ -56,17 +56,9 @@ Authorization: GNAP 123454321

### Signature base

The signature creation process begins with identifying the fields of the original message to use when creating the signature.
These are called the covered components of the message. The covered components make up the signature base, together with the signing algorithm, and an identifier for the signer's public key.
The signature creation process begins with identifying the fields of the original message to use when creating the signature. These fields are referred to as the covered components of the message. The covered components make up the signature base, together with the signing algorithm, and an identifier for the signer's public key.

The final sub-field of the signature base is an HTTP structured field called signature params, an ordered list of components that make up the signature base.

{/* prettier-ignore */}
{/*
# fyi: contents of codeblock example below were generated using: https://httpsig.org
\*/}
The final sub-field of the signature base is an HTTP-structured field called `signature-params`, which contains an ordered list of components that make up the signature base.

<CodeBlock title="Example: signature base">

Expand All @@ -83,13 +75,15 @@ The final sub-field of the signature base is an HTTP structured field called sig

</CodeBlock>

For more information about required components, see <LinkOut href="https://datatracker.ietf.org/doc/html/rfc9635#name-http-message-signatures">Section 7.3.1 HTTP Message Signatures</LinkOut> in the GNAP specification.

### Generate signature

To generate the http signature:

1. The signature base gets hashed (using SHA-512), producing a digest.
2. The digest gets signed with the signer's private key, producing the signature as a byte string.
3. The byte string gets Base64 encoded, and this results in the final signature value.
1. The signature base is hashed using SHA-512, producing a digest.
2. The digest is signed with the signer's private key, producing the signature as a byte string.
3. The byte string is Base64-encoded, resulting in the final signature value.

### Signed HTTP message

Expand Down

0 comments on commit f259f30

Please sign in to comment.