Skip to content

rs1. request-state format carries no kid, so key rotation cannot be targeted #1096

Description

@zs-dima

RequestStateCodec's wire format is rs1.<base64url(expiry ‖ payload)>.<base64url(tag)>.
The rs1 prefix versions the format.
Nothing in the token identifies the key that produced the tag.

A server that rotates its signing key therefore has two options, and both are unpleasant:

  1. Rotate and break. Every requestState in flight becomes unverifiable the moment the new
    key is installed. For a multi-round tool call that is every dialogue a user has open.
  2. Trial-open. Keep the previous key and attempt open_with against each key in turn. This
    works — it is what this demo does — but it costs an extra HMAC verification per request for
    every key in the window, and it obliges every server author to rediscover the pattern.

Why it matters here

MRTR makes the codec's key a deployment-wide shared secret: every replica that might
continue a dialogue must hold it. That is precisely the population most likely to need staged
rotation, and least able to coordinate an atomic switch.

JWS solves this with a kid header, JWE likewise. rs1. has no equivalent field.

Suggested fix

Add an optional key identifier to the format — rs1.<kid>.<body>.<tag>, or a length-prefixed
kid inside the authenticated region — and let RequestStateCodec hold a small keyring keyed
by it. Opening then selects one key instead of trying all of them, and a server can publish a
new key before it starts signing with it.

If a format change is unacceptable, documenting the trial-open pattern in RequestStateCodec's
type-level docs would at least stop each author from working it out alone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: important but non-blocking improvement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions