This document covers what exactly it means for a server to be JWK public keys registry compliant. This is intended as an extension to the JWK set spec.
Term | Definition |
---|---|
private key | The private half of an asymmetric key which can be used to generate unforgeable signatures. |
public key | The public half of an asymmetric key which can be shared and is used to verify a signature made with a private key. |
key id | (kid) A unique opaque string which is used to refer to a key by name. This is often implemented as a fingerprint of the key. |
issuer | (iss) An isssuer is the entity responsible for minting and signing a JWT. In JWK key server parlance, this is the holder of the private key. |
service | This is synonymous with issuer for JWT purposes, but is made more generic in the key server routes |
The following operations can be performed against the specified endpoints of a JWK key server compliant server.
GET /services/<service_name>/keys
Example response:
200 OK
{
keys: [
{
<JWK contents>
},
{
<JWK contents>
}
]
}
This call is guaranteed to always return a response, even if the service is unknown.
GET /services/<service_name>/keys/<key_id>
Response if they key is published and approved:
200 OK
Cache-Control: max-age=<cache time in seconds>
{
<JWK content>
}
Response if the key was published but awaiting approval:
409 Conflict
Response if the key was previously approved, but is now expired:
403 Forbidden
Response if the key is unknown:
404 Unknown
Publishing a new JWK requires that the request be authorized by a signed JWT. The server requires that the key id be specified as a kid
JWT header, and that the iss
issuer claim match the <service_name>
. If this is a brand new key, the request must be self-signed (i.e. the JWT signature was made by the private key for which we're attempting to publish a public key). If this is a key rotation request, the request must be signed by a key which appears in the key list for this service (i.e. is currently approved and unexpired).
The optional argument expiration
specifies the unix time as an integer number of seconds since the epoch at which time the key will expire.
The optional argument rotation
is for guidance only, and specifies the anticipated amount of time as an integer number of seconds between key rotations. A key server can use this value to determine if an origin server missed a rotation request, and therefore may be unhealthy.
NOTE: Successfully rotating a key revokes the key that we signed the rotation request with when successful.
PUT /services/<service_name>/keys/<key_id>[?expiration=<timestamp>[&rotation=<seconds>]]
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
{
<JWK contents>
}
Response when a new key is published and requires approval:
202 Accepted
Response when an existing key is successfully replaced:
200 OK
Response when the request is signed with an inappropriate key:
403 Forbidden
All other failures are interpreted as client errors:
400 Bad Request
NOTE: There is no cache invalidation or revocation list mechanism in place. There are no guarantees that a revoked key will not be interpreted as successful by clients with a cached copy of the key until the key finally expires.
Revoking a public key requires that the request be authorized with a JWT which was signed by the key being revoked.
DELETE /services/<service_name>/keys/<key_id>
Response when the key is successfully revoked:
204 No Content
Response when the request is not properly self signed:
403 Forbidden
All other failures are interpreted as client errors:
400 Bad Request
There are two primary ways that we intend the key server to be used. In the verifier use case, we use the key server to validate a request that we've already received. In the manager use case, we autogenerate a key, publish it, and then autorotate it on a schedule.
This is how the client is expected to use the key server to verify the origin of a JWT:
- Receive a request signed with a JWT
- Extract the
kid
JWT header - Extract the
iss
issuer claim from the JWT body - Fetch the public key from
/services/<iss>/keys/<kid>
- If the public key is unknown, the request fails
- Use the public key to verify the signature on the JWT
- If the signature does not validate with the public key, the request fails
This is roughly how a client can use the publish and fetch endpoints to publish and autorotate a key:
- Generate a new public/private key pair
- Serialize the public portion as a JWK
- Generate a JWT, setting all of the strict jwt claims:
iss
: our own service issuer nameaud
: the scheme and hostname of the key server, e.g.https://mykeyserver.com
exp
: an expiration time a short while from now, but not too shortiat
: the time at which the JWT was generatednbf
: a short time in the past to accommodate clock skew
- Sign the JWT with the private portion of our new key pair
- Set the
kid
claim in our JWT headers to be the opaque ID string for our new key PUT /services/<iss>/keys/<kid>
-> 202- Poll
GET /services/<iss>/keys/<kid>
until 409 responses turn into 200 - Our key is now published and approved
Rotating a key:
- Generate a new public/priate key pair
- Follow the steps above to create a JWT with the required claims
- Sign the JWT with the private portion of our previously approved and still active key
PUT /services/<iss>/keys/<kid>
-> 200- Our new key is now active, and our previous key is revoked.