Skip to content

spec: s6.1: recommend aggressive timestamp key rotation#316

Draft
cyphar wants to merge 2 commits intotheupdateframework:masterfrom
cyphar:root-key-rotation
Draft

spec: s6.1: recommend aggressive timestamp key rotation#316
cyphar wants to merge 2 commits intotheupdateframework:masterfrom
cyphar:root-key-rotation

Conversation

@cyphar
Copy link
Copy Markdown

@cyphar cyphar commented Mar 27, 2026

The previous text tried to minimise the risk of the root.json freeze
attack, but in practice the expiry of root.json is set to several years
to reduce the usage of the incredibly critical root role keys -- if an
attacker freezes root.json they can buy themselves time to try to access
a threshold of keys even if you try to key rotate your way out of it.

The core issue that allows the root.json freeze attack is that the new
root.json is the only thing signed by the new root keys, so there is no
way for a user to detect that they are being blocked from seeing a new
root.json.

However, a very simple workaround (and arguably good general policy) is
to rotate other keys that are used to sign (comparatively) short-expiry
metadata. By rotating all of the timestamp keys, an attacker blocking
root.json will cause clients to error out as soon as they either see a
new timestamp.json (because it is signed by keys the client does not
recognise) or an old timestamp.json after a short delay (because the
short expiry has elapsed). The client cannot conclusively determine that
the attack was actually a root.json freeze attack or just a malicious
mirror providing completely invalid signatures, but the attack is no
longer silent.

Unlike other alterative workarounds (such as including the hash and
revision number of root.json in snapshot.json), this approach does not
open the door to lower-trust keys being able to cause DoSes or
fast-forward attacks by including bogus information about root.json.
This new recommendation just boils down to simply rotating a few extra
keys when updating root.json.

This does open the door to possible races with naive repository storage
media (such as a client erroring out after seeing a mismatched root.json
and timestamp.json, because one file was updated before another) but
those are already the case with any other key rotation and there are
well-known workarounds (such as using transactions to ensure that all
metadata files are updated together).

Signed-off-by: Aleksa Sarai aleksa@amutable.com

cyphar added 2 commits March 28, 2026 05:34
This better matches the rest of the spec's linking of each *.json
reference.

Signed-off-by: Aleksa Sarai <aleksa@amutable.com>
The previous text tried to minimise the risk of the root.json freeze
attack, but in practice the expiry of root.json is set to several years
to reduce the usage of the incredibly critical root role keys -- if an
attacker freezes root.json they can buy themselves time to try to access
a threshold of keys even if you try to key rotate your way out of it.

The core issue that allows the root.json freeze attack is that the new
root.json is the only thing signed by the new root keys, so there is no
way for a user to detect that they are being blocked from seeing a new
root.json.

However, a very simple workaround (and arguably good general policy) is
to rotate other keys that are used to sign (comparatively) short-expiry
metadata. By rotating all of the timestamp keys, an attacker blocking
root.json will cause clients to error out as soon as they either see a
new timestamp.json (because it is signed by keys the client does not
recognise) or an old timestamp.json after a short delay (because the
short expiry has elapsed). The client cannot conclusively determine that
the attack was actually a root.json freeze attack or just a malicious
mirror providing completely invalid signatures, but the attack is no
longer silent.

Unlike other alterative workarounds (such as including the hash and
revision number of root.json in snapshot.json), this approach does not
open the door to lower-trust keys being able to cause DoSes or
fast-forward attacks by including bogus information about root.json.
This new recommendation just boils down to simply rotating a few extra
keys when updating root.json.

This does open the door to possible races with naive repository storage
media (such as a client erroring out after seeing a mismatched root.json
and timestamp.json, because one file was updated before another) but
those are already the case with any other key rotation and there are
well-known workarounds (such as using transactions to ensure that all
metadata files are updated together).

Signed-off-by: Aleksa Sarai <aleksa@amutable.com>
@JustinCappos
Copy link
Copy Markdown
Member

JustinCappos commented Mar 27, 2026 via email

@cyphar
Copy link
Copy Markdown
Author

cyphar commented Mar 28, 2026

Where does this recommendation come from? Did you set this up in your environment and run into this?

While evaluating TUF for our update scheme at @amutable-systems, this particular attack came to mind and then I re-read the spec and found the operational note for this somewhat underwhelming. For our deployment we plan to use the method I've outlined in this PR, but given that it is a fairly minimal operational change and avoids an attack that the spec currently doesn't really give much guidance about, it seemed reasonable to improve the spec wording and provide this kind of recommendation.

But as discussed on Slack, we can also just put this in a "best practices" doc -- though given that the spec already has this operational section, it makes more sense to me to just adjust the existing text.

My sense is that a short-lived timestamp role is already understood / done by adopters.

Based on what I've seen, that doesn't really seem to be the case (and you could argue the note in s6.1 of the specification kind of implies that there isn't a different workaround, as it effectively says that expiries are TUF's defence against this attack).

I took a look at the reference implementations (and some non-reference ones like the tough rust crate) and none of them appear to have any kind of logic to do this implicitly so an operator would need to do it manually.

FWIW it seems that Sigstore does this in practice (there are several revisions with the same timestamp key but they also contain the same keys for every other role and so the root was probably just signed to refresh the expiry) but I didn't see any mention in their documentation as to this being quite an important aspect of the security of key rollovers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants