Skip to content

Commit

Permalink
HMS-2694 doc: Design for hostconf token / JWK
Browse files Browse the repository at this point in the history
Add first draft of a design document that explains host configuration
token and JWK handling.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
  • Loading branch information
tiran authored and frasertweedale committed Oct 6, 2023
1 parent 2daaa64 commit 5a1079e
Showing 1 changed file with 161 additions and 0 deletions.
161 changes: 161 additions & 0 deletions docs/hostconf-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Host configuration token

**work in progress**

Host configuration tokens are used to proof that a host is eligible to enroll
into an identity domain. A host requests a token from the *idmscv-backend*,
then presents it to the enrollment agent on an IPA server. The enrollment
agent verifies the token and on success, enrolls the host. The process uses
standard JSON Web Keys (JWKs) and JSON Web Signatures (JWS).

The signing JWKs are generated by the *idmsvc-backend*. Enrollment agents
download a JWK Set and revoked key ids, whenever they register or update
domain data. This usually happens once a day.


## JSON Web Keys (JWK)

Host configuration tokens are signed with an asymmetric JWK. All JWKs are
elliptic curve (EC) keys with curve P-256, which is a fast and FIPS approved
algorithm. The key identifier (kid) is based on the SHA-256 fingerprint
according [RFC7638](https://datatracker.ietf.org/doc/html/rfc7638),
URL-safe base64 encoded and truncated to 8 characters. Additionally, the keys
have an expiration time `exp` attribute, which has the same mean as `exp` in
[RFC7519](https://datatracker.ietf.org/doc/html/rfc7519) JWT.

* `kid`: 8 characters (SHA-256 fingerprint)
* `kty`: `EC`
* `crv`: `P-256`
* `alg`: `ES256`
* `use`: `sig`
* `exp`: expiration time as Unix time stamp integer

Example:

```json
{
"alg": "ES256",
"crv": "P-256",
"exp": 1704261209,
"kid": "7lkFVyKx",
"kty": "EC",
"use": "sig",
"x": "dGFSfEJTinH76FFXus90CVn6r5F_FGThLjWrnmMZ3Os",
"y": "p4BOLD0REq9BbKpty0nJxZ95nNFeIrxDHH9S4dMsk7M"
}
```

### Encryption at rest

Private JWKs are encrypted with standard AES-GCM AEAD algorithm with a random
nonce. The nonce is pre-pended to the cipher text. The symmetric encryption
key is derived from the app secret using HKDF-SHA256.


### Database schema

* `id` serial
* `created_at` timestamp
* `updated_at` timestamp
* `deleted_at` timestamp
* `kid` unique varchar (not NULL)
* `expiration` timestamp (not NULL)
* `token` text (not NULL)
* `encrypted` byte array (NULL-able)

The `token` field contains the serializes JSON string representation of the
**public** JWK and `encrypted` is the encrypted private JWK. Revoked keys
have a NULL `encrypted` field.


## Signed token (JWS / JWT)

The host configuration token uses
[RFC 7519](https://datatracker.ietf.org/doc/html/rfc7515) JWS (JSON Web
Signature) and [RFC7519](https://datatracker.ietf.org/doc/html/rfc7519)
JWT (JSON Web Token) standards. The tokens are **not** JWTs in compact
notation. Instead they are JWS with one or multiple issuers in JSON string
notation. Multiple issuers enable smooth key rotation. Old keys are slowly
phased out while a new key is introduced.

**Header**
- `kid`: always set
- `alg`: `ES256`

**Registered JWT claims**
- issuer (`iss`) must be `"idmsvc/v1"`
- subject (`sub`) is RHSM cert subject "CN"
- audience (`aud`) must be `"join host"`
- expiration (`exp`), not before (`nbf`), and issued at (`iat`) must be set.
- JWT ID (`jti`) must be set (6 random bytes, URL-safe base64 encoded). The
claim is included for audit logging and for future use.

**Private JWT claims**
- `rhorg` (string) is set to RHSM cert subject "O"
- `rhinvid` (string) is host-based inventory uuid
- `rhdomid` (string) HMSIDM domain id
- `rhfqdn` (string) hosts' fully qualified domain name

Example payload:

```json
{
"aud": ["join host"],
"exp": 1696486077,
"iat": 1696485477,
"iss": "idmsvc/v1",
"jti": "tQBCmPne",
"nbf": 1696485477,
"rhdomid": "772e9618-d0f8-4bf8-bfed-d2831f63c619",
"rhfdqn": "client.ipa.test",
"rhinvid": "1efd5f0e-7589-44ac-a9af-85ba5569d5c3",
"rhorg": "16765486",
"sub": "1ee437bc-7b65-40cc-8a02-c24c8a7f9368"
}
```

## JWK distribution

### Key creation and rotation

TODO

### Key revocation

TODO

## Token flow

### 1. Host configuration request

The host makes a POST request `/host-conf/:inventory_id/:fqdn` with mTLS
authentication using the RHSM cert / key pair. The *API gateway* verifies that
the client cert is valid and the host is still subscribed. The gateway
injects the organization id and subscription manager id of the certificate
into the `X-Rh-Identity` header.

### 2. Backend response to host

The *idmsvc-backend* uses the host's *fqdn* and additional information from
the request body to match an identity domain. Optionally, the backend also
verifies the *inventory_id* and *fqdn* with *HBI*.

All information (org id, cert CN, inventory id, fqdn, and domain id) are
baked into a token claim and signed with the backend's JWKs. The token is
returned to the host along with other information, which are needed by the
client to enroll into the domain. This includes a list of hosts with an
enrollment agent and a CA chain.

### 3. Host enrollment request

The host connects to an enrollment agent on the IPA server with mTLS
authentication using the same RHSM cert / key pair. The enrollment agent
can verify that the cert has been signed by the correct CA chain, but it
has no means to check that the client cert has not been revoked. Instead
it uses the signed token to verify the certificate. An enrollment request
is refused unless the cert's organization and CN matches the values in
the token. The IPA domain's domain id must also match the claim in the token.

If the signature of the token and all claims are correct, then the enrollment
agent creates the IPA host entry. The host now can enroll itself with
Kerberos PKINIT authentication using the RHSM cert / key pair.

0 comments on commit 5a1079e

Please sign in to comment.