feat(attestation): signed key documents, so host write access is not root compromise - #119
Open
rrrodzilla wants to merge 1 commit into
Open
rrrodzilla wants to merge 1 commit into
rrrodzilla wants to merge 1 commit into
Conversation
…7.2 (#116) An unsigned key document makes write access to the publication host equivalent to possession of the trust root's signing key: every channel check authenticates where the bytes came from, and a host compromise serves from the right place. Specification 8.12 now names that threat, and 7.2 gains an OPTIONAL signed form against it, modeled on TUF's root role: the response format wrapped in a signature by an offline root key, carrying a monotonic version and an expires, signed over the received octets behind a domain separator rather than over any canonicalization. agent-uri-attestation 0.9.0 reads, verifies, and mints the signed form (SignedKeyDocument, KeyDocumentPayload, DocumentSigner, ServedDocument); agent-uri-attestation-wellknown 0.3.0 turns it into policy: pin_root requires the signed form from that root, refuses a version behind the newest accepted (a floor that outlives the cache and forget()), and stops serving a cached document at its own expires. Roots nobody pins keep working in either form, which is the migration path. The parse_key_document fuzz target covers both forms in the corpus, CI matrix, and Taskfile.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #116.
What this changes
Specification 0.9.0
signedmember carrying two new REQUIRED fields (version, monotonic per publication, andexpires, RFC 3339), plus asignaturesarray of detached Ed25519 signatures. The signature covers"agent-uri-key-document-v1\n"followed by the exact served octets ofsigned, so there is no JSON canonicalization anywhere: signed as serialized, verified as received.keys; a document that vouches for itself vouches for nothing.versionbehind the newest accepted. Unpinned: either form works on the terms of the bare one, which is the migration path.expires, and the version floor).agent-uri-attestation 0.8.0 → 0.9.0
signed_documentmodule:SignedKeyDocument(parse, thenverify/verify_atagainst pinned keys),KeyDocumentPayload(flattens overKeyDocument, sotrust_store()/denylist()/check_belongs_to()are reused unchanged),ServedDocumentto tell the two forms apart, andDocumentSigner/to_signed_jsonfor the publishing side, which signs the exact octets it embeds. The payload's raw octets are kept viaserde_json::RawValue; a reformatted document is a different document and the tests say so.root_kidis advisory and never selects a key. A malformedsignaturesentry is skipped, not fatal, and reported only if nothing verified. New errorsDocumentExpiredandDocumentUnsigned(the enum was already#[non_exhaustive]). TheMAX_KEYScap is enforced on both parse routes.agent-uri-attestation-wellknown 0.2.0 → 0.3.0
KeyDiscovery::pin_root(root, PinnedRootKeys). For a pinned root: the bare form is refused (a fallback an attacker can trigger by deleting a signature is not a defence), the signature must verify, an expired document is refused, and aversionbehind the newest accepted is refused — the floor outlives the cache TTL andforget(), withforget_publication_history()as the deliberate reset. A cached signed document also stops being served at its ownexpires, whichever of that and the TTL comes first. Unpinned roots accept either form, with the signed payload's unauthenticatedexpires/versiondeliberately not enforced. New errorsUnsigned,VersionRegression,VersionUnavailable(a rollback check that cannot run refuses rather than skips),InvalidRootKey. The whole policy lives in a network-freeaccept()seam and is tested against bytes.Verification
cargo fmt --check,cargo clippy --workspace --all-features --all-targets: clean, zero warningscargo nextest run: 1127 passed, 0 failedparse_key_document(both forms in the seed corpus), wired into the CI fuzz matrix andtask fuzz; 1.3M execs clean in a local smoke runcargo deny,cargo audit: cleanNot in this PR
No
agent-uri-clisupport for minting or inspecting signed documents. If operators are expected to run the offline key ceremony by hand, that deserves its own issue.