Skip to content

Support spec_version 1.0.x for better interoperability with other TUF implementations#417

Merged
erickt merged 4 commits into
theupdateframework:developfrom
llbxg:feat/spec-1.0x-compat
Jul 14, 2026
Merged

Support spec_version 1.0.x for better interoperability with other TUF implementations#417
erickt merged 4 commits into
theupdateframework:developfrom
llbxg:feat/spec-1.0x-compat

Conversation

@llbxg

@llbxg llbxg commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

description

I am evaluating rust-tuf as a client-side library for embedded Linux / CIP1 use cases.

As part of that work, I tested a client based on rust-tuf with other TUF repositories and server-side implementations.

I found an interoperability issue. The rust-tuf client currently accepts spec_version values such as 1.0 and 1.0.0.
But current TUF metadata in the ecosystem often uses 1.0.x.

This PR keeps compatibility with the old 1.0 value. It also accepts numeric 1.0.x values such as 1.0.31.

I kept this change small.

  • 1.0 is still accepted for compatibility with existing metadata
  • numeric 1.0.x is accepted
  • unrelated versions such as 1.1.0 and 2.0.0 are still rejected
  • this PR does not change the match policy to all 1.x.y

The TUF specification leaves this point open. It says that spec_version uses SemVer, but adopters can decide what counts as a match 2.
Because of that, I am proposing a small interoperability fix here, not a broader policy change.

I also checked a few other TUF projects.

  • tuf-conformance includes metadata with spec_version: "1.0.31" 3
  • python-tuf accepts numeric X.Y / X.Y.Z and matches on major version 4
  • tuf-js seems to use almost the same policy 5
  • go-tuf writes 1.0.31, and from a quick look at the current source I could not find the same strict spec_version check in the main parse path 6

There is still a bigger design question: should rust-tuf accept 1.x.y more generally, like python-tuf and tuf-js?
I think that question should be decided separately.
For this PR, I wanted to focus on the immediate interoperability issue reported in #410.

If maintainers prefer a different direction, I am happy to adjust this PR or split follow-up interoperability changes into separate PRs.

verification

  • updated the existing spec_version validation test
  • added a regression test that root metadata deserialization accepts spec_version: "1.0.31"
  • ran targeted tests for spec_version
  • ran the full test suite
Click to expand
cargo test -p tuf --lib pouf::pouf1::shims::test::spec_version_validation -- --exact
running 1 test
test pouf::pouf1::shims::test::spec_version_validation ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 166 filtered out; finished in 0.00s
cargo test -p tuf --lib metadata::test::deserialize_json_root_accepts_1_0_x_spec_version -- --exact
running 1 test
test metadata::test::deserialize_json_root_accepts_1_0_x_spec_version ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 166 filtered out; finished in 0.00s
cargo test -q

-> all tests passed

REF

Footnotes

  1. https://cip-project.org/

  2. https://github.com/theupdateframework/specification/blob/v1.0.31/tuf-spec.md?plain=1#L690

  3. https://github.com/theupdateframework/tuf-conformance/blob/eeb82bff9ce7e19133d24bc7bd44a7e8908546ed/tuf_conformance/static_data/tuf-on-ci-0.11/metadata/1.root.json#L60

  4. https://github.com/theupdateframework/python-tuf/blob/9a3c3046d6ffdc9d90ec21ce5237721bcd985652/tuf/api/_payload.py#L137

  5. https://github.com/theupdateframework/tuf-js/blob/a399ce87d8d908f64d53696a673420193424e0de/packages/models/src/base.ts#L41

  6. metadata/types.go, metadata/metadata.go, metadata/marshal.go

@llbxg
llbxg force-pushed the feat/spec-1.0x-compat branch from d25074a to 95c431c Compare July 9, 2026 11:33
Comment thread tuf/src/pouf/pouf1/shims.rs Outdated
// according to the SemVer spec, because it is already baked into some of the old roots.
fn valid_spec_version(other: &str) -> bool {
matches!(other, "1.0" | "1.0.0")
if other == SPEC_VERSION {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! Looking through your research, I think we should follow the spec and just parse the string as a semver string, preferably using the semver crate. With something like:

const SPEC_VERSION = semver::Version::new(1, 0, 0);
...
fn valid_spec_version(version_string: &str) -> bool {
    /////////////////////////////////////////
    // TUF-1.0.34 §4.3 (https://theupdateframework.github.io/specification/v1.0.34/#spec_version):
    //
    //     A string that contains the version number of the TUF specification. Its format follows
    //     the [Semantic Versioning 2.0.0 (semver)](https://semver.org/spec/v2.0.0.html)
    //     specification. Metadata is written according to version "spec_version" of the
    //     specification, and clients MUST verify that "spec_version" matches the expected version
    //     number. Adopters are free to determine what is considered a match (e.g., the version
    //     number exactly, or perhaps only the major version number
    //     (major.minor.fix).https://theupdateframework.github.io/specification/v1.0.34/#file-formats-root

    let version = semver::Version::parse(version_string) else {
        // Support parsing `1.0` for legacy versions
        return version_string == "1.0";
    };

    version.major == SPEC_VERSION.major
}

At this point I think we can switch our spec version to 1.0.0, and eventually update the metadata version to 1.0.x once I finish updating support for the latest spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion.

I updated the implementation to use the semver
crate and defined the supported spec version as Version::new(1, 0, 0).

As part of this change, metadata generated by the test helpers now uses
"1.0.0" instead of "1.0". I updated the affected test fixtures and
expected signatures accordingly. I also changed the spec version to "1.0.0"
in tests that are intended to fail for another validation reason, so that they
continue to exercise the intended failure path.

To preserve compatibility with legacy metadata, I added
deserialize_json_root_accepts_legacy_1_0_spec_version, which verifies that
we still accept "1.0".

The interop metadata fixtures also needed to be regenerated because the
updated spec_version changes the signed metadata. I regenerated them using
interop-tests/tests/metadata/regenerate-metadata.sh. The interop tests pass
for both consistent snapshot configurations.

Please let me know if you see any issues with these changes. I would be happy
to address them.

@llbxg
llbxg force-pushed the feat/spec-1.0x-compat branch from 95c431c to 5f4d442 Compare July 10, 2026 06:31
Update generated interop fixtures after the specification version changed
from 1.0 to 1.0.0.
@erickt

erickt commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Could you do a cargo fmt for this? Otherwise LGTM.

@erickt
erickt merged commit 733a4a3 into theupdateframework:develop Jul 14, 2026
16 checks passed
@llbxg

llbxg commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

My apologies, I forgot to run cargo fmt before submitting the changes.
I see that you've already addressed it. Thank you very much for your support.

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