-
Notifications
You must be signed in to change notification settings - Fork 36
Define did:web verification for signed Entry Trust Manifests #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jonathanhefner
wants to merge
2
commits into
Agent-Card:main
Choose a base branch
from
jonathanhefner:agent/define-did-web-entry-signatures
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # ADR-0025: Bind Signed Trust Manifests to Artifact Releases | ||
|
|
||
| **Status:** Proposed | ||
|
|
||
| **Date:** 2026-09-03 | ||
|
|
||
| ## Context | ||
|
|
||
| A signed entry Trust Manifest currently binds the signer's claims to an | ||
| artifact's media type, content digest, and optionally its URL. The containing | ||
| Catalog Entry's `identifier` and `version` remain outside that signature. | ||
|
|
||
| ADR-0015 established that the two existing identity fields serve different | ||
| purposes. `entry.identifier` is the artifact's logical name in the catalog, | ||
| whereas `trustManifest.identity` is the cryptographic identity associated with | ||
| the Trust Manifest. They need not be the same URI; their domains need only | ||
| align. | ||
|
|
||
| The development work that later introduced `subject` temporarily required | ||
| those two fields to be exactly equal. Because `trustManifest.identity` is | ||
| inside the signed payload, that temporary equality rule also placed | ||
| `entry.identifier` under the signature. The accompanying security analysis | ||
| could therefore focus on the remaining problem: binding the signature to the | ||
| artifact's exact representation through its media type, digest, and optional | ||
| URL. | ||
|
|
||
| Review subsequently restored the domain-alignment rule from ADR-0015, but the | ||
| definition of `subject` remained representation-only. Once those two decisions | ||
| were combined, no field inside the signed payload was required to equal | ||
| `entry.identifier`. The artifact bytes were still protected, but their logical | ||
| catalog identifier was not. | ||
|
|
||
| Catalog Entries that share an `identifier` can represent different releases | ||
| using `version`. The resolution procedure compares those versions when | ||
| selecting the latest release or satisfying a version constraint. A present | ||
| `entry.version` can therefore determine which artifact a consumer receives, | ||
| even though it remains outside the Trust Manifest signature. | ||
|
|
||
| An attacker who can modify a catalog can retain a valid signed manifest and | ||
| its exact artifact bytes while changing only the entry's logical identifier or | ||
| version. For example, the attacker can label an old, vulnerable release as a | ||
| newer version. A consumer requesting the latest release, or requiring a | ||
| minimum version containing a security fix, can then receive the vulnerable | ||
| code while the signature, URL, media type, and digest all verify. | ||
|
|
||
| A top-level catalog signature detects changes to the catalog document, but it | ||
| authenticates a different statement. It establishes that the catalog signer | ||
| published a particular snapshot, including its identifiers and versions. An | ||
| entry Trust Manifest establishes that its signer made claims about a | ||
| particular artifact. When the catalog and Trust Manifest have different | ||
| signers, the catalog signature does not establish that the Trust Manifest | ||
| signer assigned those release coordinates. Conversely, an entry signature | ||
| does not protect the catalog's collection-wide structure. The two controls | ||
| provide complementary guarantees. | ||
|
|
||
| ## Decision | ||
|
|
||
| For a signed Trust Manifest attached to a Catalog Entry, extend `subject` to | ||
| identify both the logical artifact release and its exact representation. | ||
|
|
||
| For a signed entry Trust Manifest: | ||
|
|
||
| - `subject.identifier` is REQUIRED in every Subject and MUST exactly equal | ||
| `entry.identifier` when the Subject appears on a Catalog Entry. | ||
| - `subject.version` is REQUIRED when `entry.version` is present and MUST | ||
| exactly equal it. When `entry.version` is absent, `subject.version` MAY be | ||
| present and no entry-version comparison is required. | ||
| - `subject.type` remains REQUIRED and MUST exactly equal `entry.type`. | ||
| - `subject.digest` remains REQUIRED and MUST match the artifact content. | ||
| - `subject.url` remains OPTIONAL. When present, it MUST exactly equal | ||
| `entry.url`. | ||
|
|
||
| Consumers MUST perform the entry-to-subject comparisons in addition to | ||
| verifying the signature, anchoring its identity, and checking the artifact | ||
| digest. A signature over a mismatched subject does not authenticate the | ||
| containing entry. | ||
|
|
||
| Catalog-level integrity remains complementary. Per-entry signatures cannot | ||
| detect removal of a valid entry, injection of unrelated entries, or | ||
| reordering. A catalog signature or trusted content-addressed distribution can | ||
| protect the catalog snapshot as a whole. Neither an entry signature nor a | ||
| catalog signature alone prevents replay of an older, correctly signed value. | ||
| A consumer that must prevent rollback needs either a trusted source of current | ||
| release information or local state recording the newest release it has already | ||
| accepted. | ||
|
|
||
| The release-coordinate comparison rules in this decision are limited to signed | ||
| Trust Manifests attached to Catalog Entries. Requiring every Subject to include | ||
| an `identifier` does not define what the Subject of a signed Host Trust Manifest | ||
| represents. This decision also does not change the meaning of `identity` or | ||
| define how an identity resolves to an authorized signing key. Those concerns | ||
| require separate decisions. | ||
|
|
||
| The specification's CDDL is updated to represent `subject`, `issuedAt`, and | ||
| `expiresAt`, which were already defined in the normative prose but missing from | ||
| the machine-readable data model. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - A valid signed manifest and artifact cannot be transplanted to another | ||
| logical identifier or relabeled as another version without detection. | ||
| - The same signed manifest remains usable in catalogs that omit the optional | ||
| entry `version`; when a catalog supplies a version, it must agree with the | ||
| signed subject. | ||
| - Mirrors remain possible because `subject.url` stays optional. | ||
| - Catalog signatures continue to protect collection-level decisions and may | ||
| be made by a different principal than the Trust Manifest signer. | ||
| - Signing the release coordinates does not by itself prevent rollback to an | ||
| older, correctly labelled and validly signed release. Consumers still need a | ||
| trusted source of current release information or a record of the newest | ||
| release they have already accepted. | ||
|
|
||
| ## Alternatives Considered | ||
|
|
||
| ### Rely only on catalog-level integrity | ||
|
|
||
| Rejected as the sole mitigation. It protects the catalog operator's snapshot, | ||
| but does not establish that the Trust Manifest signer made claims about the | ||
| listed identifier and version. It also couples that claim to one catalog. | ||
|
|
||
| ### Bind only the artifact digest | ||
|
|
||
| Rejected. It proves which bytes were endorsed but permits those exact bytes and | ||
| claims to be relabeled under another logical artifact or release coordinate. | ||
|
|
||
| ### Require `subject.url` | ||
|
|
||
| Rejected. Location binding would prevent mirrors and does not substitute for a | ||
| stable logical identifier. Publishers may opt into location binding by | ||
| including the existing optional field. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| # ADR-0027: Define a `did:web` Signature Profile for Entry Trust Manifests | ||
|
|
||
| **Status:** Proposed | ||
|
|
||
| **Date:** 2026-09-04 | ||
|
|
||
| ## Context | ||
|
|
||
| The Trust Manifest signature procedure defines a detached JWS over canonical | ||
| JSON, but its identity and key-selection rules do not provide an interoperable | ||
| verification procedure. | ||
|
|
||
| The `identity` field has accumulated several meanings. It is described as the | ||
| artifact's primary subject, as a workload principal, and as the URI from which | ||
| the signer's public key is resolved. The signature instructions separately | ||
| refer to the publisher's key. These roles need not identify the same thing. | ||
|
|
||
| ADR-0025 assigns artifact identity to the signed `subject`. In particular, | ||
| `subject.identifier` names the logical artifact and `subject.version` names the | ||
| release when the Catalog Entry declares one. The remaining role needed for | ||
| `identity` is the issuer to which the Trust Manifest's claims are attributed. | ||
|
|
||
| The existing key-resolution text lists DID, HTTPS, SPIFFE, and DNS mechanisms | ||
| as though a short lookup instruction were sufficient for each. These | ||
| mechanisms have different authorization models and lifecycles. In particular: | ||
|
|
||
| - A DID document can contain several verification methods authorized for | ||
| different purposes. Merely finding a key in `verificationMethod` does not | ||
| authorize it to make assertions. | ||
| - A SPIFFE Workload API returns credentials to the local workload calling the | ||
| API. It is not a public resolver for an arbitrary SPIFFE ID carried in a | ||
| catalog document. | ||
| - A URL or DNS name does not by itself define whether the TLS key, a JWKS key, | ||
| a DNSSEC record, or some other key is authorized to sign a Trust Manifest. | ||
|
|
||
| The existing list therefore creates apparent breadth without giving | ||
| independent implementations enough information to select the same key or reach | ||
| the same verification result. | ||
|
|
||
| AI Catalog also needs to distinguish publisher authentication from publisher | ||
| trustworthiness. A signature profile can prove that the controller of a domain | ||
| authorized claims about an artifact in that domain's namespace. It cannot | ||
| decide whether that publisher is reputable, whether its claims are true, or | ||
| whether an artifact is safe for a particular consumer. | ||
|
|
||
| ## Decision | ||
|
|
||
| ### Identity Roles | ||
|
|
||
| `TrustManifest.identity` identifies the issuer to which the Trust Manifest's | ||
| claims are attributed. It does not identify the artifact and does not identify | ||
| a running instance of the artifact. | ||
|
|
||
| The signed `subject` identifies the artifact release and representation. | ||
| Runtime and workload identities belong to the artifact's protocol or a future | ||
| runtime-security profile. | ||
|
|
||
| ### One Interoperable v1 Profile | ||
|
|
||
| AI Catalog defines one signature profile for signed Entry Trust Manifests: | ||
|
|
||
| - The artifact identifier uses the standard `urn:air` syntax defined in | ||
| [Catalog Entry](../specification/ai-catalog.md#catalog-entry). | ||
| - The `{publisher}` component is a lowercase ASCII DNS domain name. | ||
| - The Trust Manifest issuer is exactly the root `did:web` DID for that domain. | ||
| - The JWS algorithm is ES256. | ||
| - The signing key is a P-256 `publicKeyJwk` authorized by the current DID | ||
| document's `assertionMethod` relationship. | ||
|
|
||
| For example, a Trust Manifest whose signed subject is | ||
| `urn:air:example.com:agent:billing` has the issuer | ||
| `did:web:example.com`. | ||
|
|
||
| This equality is an authorization rule, not merely a consistency check. The | ||
| signed subject identifies the publisher namespace, and the `did:web` method | ||
| authenticates control of the corresponding domain through HTTPS. A catalog | ||
| attacker can publish a separately named artifact under a domain they control, | ||
| but cannot retain the original publisher's `urn:air` identifier without | ||
| producing a signature authorized by that publisher's DID document. | ||
|
|
||
| Under the `did:web` Publisher Profile, the publisher and Trust Manifest issuer | ||
| are the same domain authority. The signing operation can be performed by | ||
| publishing automation whose key is authorized by the publisher's DID document, | ||
| but the profile does not support a separately identified issuer signing on the | ||
| publisher's behalf. | ||
|
|
||
| The profile supports only root `did:web` DIDs. Ports, method-specific paths, | ||
| subdomain matching, corporate-affiliation heuristics, and delegated issuers are | ||
| not inferred. They require an explicit future profile because each changes who | ||
| is authorized to speak for the publisher namespace. | ||
|
|
||
| ### Signature and Verification Semantics | ||
|
|
||
| The normative profile specifies the detached JWS construction, protected | ||
| headers, key representation, DID resolution, verification-method selection, | ||
| and verification result. The important application choices are that `alg` is | ||
| `ES256`, `kid` identifies one method under the exact issuer DID, and that method | ||
| must be authorized by `assertionMethod`. Merely listing a key under | ||
| `verificationMethod`, `authentication`, or `keyAgreement` is insufficient. | ||
|
|
||
| No `typ` header is required. The signature appears in a specifically defined | ||
| Trust Manifest field, so the containing data model supplies the application | ||
| context. A future need for cross-protocol token separation can be addressed by | ||
| a separate profile rather than adding a marker without a demonstrated | ||
| ambiguity. | ||
|
|
||
| A Trust Manifest is verified only after the issuer, signature, signed subject, | ||
| Catalog Entry, and artifact all satisfy their respective checks. Failure does | ||
| not require rejecting the entire catalog, but the consumer cannot rely on the | ||
| manifest's claims or count the entry as Level 3. An expired manifest likewise | ||
| cannot supply current verified claims. | ||
|
|
||
| The profile uses the DID document returned at verification time. `issuedAt` is | ||
| a signed claim rather than a trusted timestamp, so it cannot prove that a | ||
| removed key was authorized in the past. Historical verification requires a | ||
| separate versioning, logging, or timestamping mechanism. | ||
|
|
||
| ### Explicit Scope Boundaries | ||
|
|
||
| This decision does not define: | ||
|
|
||
| - signature profiles for SPIFFE IDs, HTTPS key URLs, DNS records, other DID | ||
| methods, or path-based `did:web` identities; | ||
| - publisher delegation, hosted signing, or corporate-affiliation rules; | ||
| - historical DID document resolution or transparency-log verification; | ||
| - whether an authenticated publisher is trusted for a particular use; | ||
| - runtime or workload identity; | ||
| - the identity and authorization of a top-level catalog signer; | ||
| - signature verification for provenance statement formats; or | ||
| - a credential format for `publisher-identity` attestations. | ||
|
|
||
| Those mechanisms can be added independently when an end-to-end use case and | ||
| authorization model are available. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - Implementations have one mandatory producer-consumer intersection instead of | ||
| an open list from which they can choose incompatible algorithms and keys. | ||
| - A standard `urn:air` identifier can be authenticated without a separately | ||
| pinned DID. The verifier relies on its DNS and Web PKI trust roots to resolve | ||
| the corresponding `did:web` document. | ||
| - Publisher authentication remains distinct from publisher trustworthiness. | ||
| Registries and consumers can layer allowlists, vetting, attestations, or | ||
| organizational policy on top of the authenticated domain. | ||
| - The `identity` field no longer carries artifact or workload semantics. | ||
| - Existing signed Trust Manifests that use another identity form or algorithm | ||
| do not satisfy the `did:web` Publisher Profile. They can still be processed | ||
| under a separately defined implementation policy. | ||
| - Long-term verification is not guaranteed after a signing key is removed from | ||
| the current DID document. | ||
| - The generic key-resolution text for SPIFFE, HTTPS, and DNS is removed rather | ||
| than promising behavior the specification does not define. | ||
|
|
||
| ## Alternatives Considered | ||
|
|
||
| ### Require an independently configured expected DID | ||
|
|
||
| The verifier could require an exact expected issuer DID supplied out of band. | ||
| This is appropriate for private policy and remains available as an additional | ||
| constraint. It was not chosen as the only interoperable path because the | ||
| standard `urn:air` identifier already contains a publisher domain that can | ||
| authorize a matching root `did:web` issuer. | ||
|
|
||
| ### Retain a generic identity-scheme dispatch table | ||
|
|
||
| This would preserve the appearance of supporting more identity mechanisms, but | ||
| each mechanism still needs profile-specific rules for signer authorization, | ||
| key selection, algorithms, lifecycle, and failure behavior. The table is | ||
| removed until those rules exist. | ||
|
|
||
| ### Support multiple mandatory algorithms | ||
|
|
||
| Allowing each producer and consumer to implement any one algorithm from a list | ||
| does not ensure that a conforming consumer can verify a conforming producer. | ||
| ES256 with P-256 `publicKeyJwk` is selected as the required v1 intersection. | ||
| Additional algorithms can be defined in later profiles. | ||
|
|
||
| ### Infer publisher delegation | ||
|
|
||
| Suffix matching, path-based DIDs, and organizational-affiliation rules could | ||
| allow subsidiaries or signing services to act for a publisher. Such inference | ||
| can also authorize an unintended party. Delegation is deferred until the data | ||
| model can express who delegated which namespace to whom. | ||
|
|
||
| ### Use SPIFFE for the Entry Trust Manifest signer | ||
|
|
||
| SPIFFE is designed to authenticate workloads within participating trust | ||
| domains. Its Workload API supplies credentials to the local workload rather | ||
| than resolving arbitrary public SPIFFE IDs. Runtime authentication can use | ||
| SPIFFE without treating it as a public artifact-release signing mechanism. | ||
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why only root
did:web?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To give us a clear and simple starting point for v1 (so that we can implement it in SDKs). More profiles with different support/requirements can be added in the future, but I think each profile should be considered individually (and motivated by concrete use cases).