Skip to content

[RFC] Proper Repository Identities #99

Open

Description

@cyphar

At the moment we only identify repositories by their user-facing names, which will become problematic once we want to start dealing with composability (repository links #73) and providing information about repositories to other parties (such as for systemd-report purposes) because repository names are fundamentally a local-only concept with no real way of validating them.

For links it would be much better to only instantiate one client and only do a single refresh per repository, as well as potentially making it easier to handle problems like auth, and for systemd-report consumers it will be incredibly useful to be able to categorise repositories using globally unique identifiers.


The only really obvious choice is to use a cryptographic hash of 1.root.json, because:

  1. It is transport-agnostic.
  2. Conceptually it defines the initial repository identity. (We already implicitly treat the signing key set as an identity in Quarry, especially on the publishing side.)
  3. It doesn't require any extensions to TUF.
  4. It is not something a malicious repository can manipulate directly without a pre-image attack (other ideas like generating a UUID and putting it in the 1.root.json suffer from malicious repositories being able to publish the same identifier).

Once we do the <meta> stuff for self-describing repositories we will likely want to be able to include the identity hash in the <meta> tag.

However, there are some issues with this proposal:

  1. We need to now store 1.root.json on the client (and transmit it in cases like cross-repo links) so that it can independently derive the identifier. But that is not enough -- a malicious repository could provide an N.root.json that is not actually signed with a valid chain from 1.root.json to impersonate a repository. So we also need to emulate root.json upgrades up to the current N.root.json on first fetch when figuring out the repository identity to make sure that the signature chain is really valid. For root_trust = { type = "bundled", ... } we probably should change the structure so that we keep all historical root.jsons to avoid post-key-compromise attacks.
  2. The ideas for using prefix-based matching for AuthN/AuthZ providers (as mentioned in tufclient: authenticate repo requests as this machine #82 (comment)) is kind of dead in the water. I guess we would need to instead restrict it based on which domains we are contacting...?
  3. We probably should hash the canonicalised JSON rather than just using the original bytes so that we don't end up with fragile setups.
  4. There is now an incentive for an attacker to compromise the initial 1.root.json keys even after rotation, since a fully broken 1.root.json might allow you to impersonate that repository... Maybe this is a serious enough issue to warrant a different design?

Activity

  1. pothos commented on Nov 9, 2026

    @pothos
    Member

    Requiring that different repos use different root keys means disallowing as policy instead of convention. Isn't this too strict and could lead to subtle issues?
    I wonder if a UUID would help or if a fixed free form name that's not mentioning the domain would be good enough?

  2. cyphar commented on Nov 11, 2026

    @cyphar
    MemberAuthor

    Sharing root keys between repositories is an incredibly bad idea -- it means that one repository can masquerade as another and completely lock out the original repository from providing future updates (because clients will reject future updates from the original repository once the bad repository rotates root.json). Other keys can be shared in principle but you should have at least one key be unique to avoid trivial repo replacement attacks (my preference is the snapshot key).

    The problem with UUIDs or static "proper" names is that nothing stops you from using the same UUID in another repository as there is nothing that actually strongly links it to the original repository. For reports the identifier would ideally be something that can't just be faked because otherwise you will need to emulate something equivalent to a proper identifier to figure out which repository something came from. (If we don't care about having /static/ identities then just using the latest root.json hash is basically good enough.)

    Using the current pseudo-URL approach for repo names is /okay/ if we were to implement the <meta> tag stuff discussed elsewhere because it would make the repository much harder to forge but it would mean that custom mirrors of repositories wouldn't work as easily -- how would you distinguish between a legitimate mirror and a fake one as a client? The keys being good is enough to trust it as a mirror but then it has no static identity.

    I think there are kinda two options here:

    1. We say that repositories do not have static identities and just pin things to the latest root.json key set. This is what TUF essentially does and is the simplest option, but it means we cannot track the identity of repositories long-term. But maybe that's fine?
    2. We (ab)use transparency logs -- if you only permit a root key to be used once for any repository then in principle you could validate the identity chain in an unforgable way by searching through the leaves for signing events using the root key. You would only accept the chain if the corresponding N.root.json and N+1.root.json were the first and second signing events.

    Maybe we should go with (1) for now and keep around copies of the old root.jsons just in case we decide to switch to (2) at some point.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions