diff --git a/README.md b/README.md index dfd11ed..9f8e006 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The project defines a schema for a secure, extensible AI Catalog. This may inclu * **Common catalog metadata**, such as publisher, documentation links, descriptions, icons, and versioning. * **Verifiable metadata**, such as attestations, certifications, provenance, and trust signals. -* **Identity metadata**, such as Decentralized Identifiers (DID) or Secure Production Identity Framework For Everyone (SPIFFE). +* **Identity metadata**, including a `did:web` profile for authenticating artifact publishers. * **Custom metadata** defined by individual producers or consumers for their own needs. Actual properties for inclusion will be debated in PRs. diff --git a/adr/0015-agent-identifier-naming.md b/adr/0015-agent-identifier-naming.md index 5b555d1..24f7b80 100644 --- a/adr/0015-agent-identifier-naming.md +++ b/adr/0015-agent-identifier-naming.md @@ -8,6 +8,11 @@ Accepted **Participants:** Pamela Dingle (Microsoft), Sam Betts (Cisco), Junjie Bu (Google), Darrel Miller (Microsoft), Alan Blount (Google), Srinivas Krishnan (Google), Krishna Thota (Google), Tadas Antanavicius (Pulse MCP), Ramiz Polic (Cisco), Jeffrey Damick (Amazon) +**Later clarification:** [ADR-0027](0027-did-web-entry-signature-profile.md) +narrows the interoperable v1 signature profile to a root `did:web` issuer and +assigns runtime or workload identity to separate profiles. The logical-name and +cryptographic-issuer separation established here remains unchanged. + ## Context The `ai-catalog` specification originally recommended using URNs or URIs for the `identifier` field in catalog entries, but did not mandate a specific format. This led to inconsistency in examples and potential interoperability issues across different registries and orchestrators. diff --git a/adr/0020-substantive-trust-manifest.md b/adr/0020-substantive-trust-manifest.md index 3ed690e..30ca4f9 100644 --- a/adr/0020-substantive-trust-manifest.md +++ b/adr/0020-substantive-trust-manifest.md @@ -6,6 +6,10 @@ **Participants:** Luca Muscariello (Cisco) +**Later clarification:** [ADR-0027](0027-did-web-entry-signature-profile.md) +supersedes this ADR's description of `identity` as a workload principal. The +manifest-validity rule established here remains unchanged. + ## Context The Trust Manifest is an OPTIONAL companion to a Catalog Entry. Within diff --git a/adr/0025-bind-signed-trust-manifests-to-releases.md b/adr/0025-bind-signed-trust-manifests-to-releases.md new file mode 100644 index 0000000..28263ba --- /dev/null +++ b/adr/0025-bind-signed-trust-manifests-to-releases.md @@ -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. diff --git a/adr/0027-did-web-entry-signature-profile.md b/adr/0027-did-web-entry-signature-profile.md new file mode 100644 index 0000000..c596738 --- /dev/null +++ b/adr/0027-did-web-entry-signature-profile.md @@ -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. diff --git a/docs/examples/multi-protocol-agent.md b/docs/examples/multi-protocol-agent.md index 0f95ac4..d63ef14 100644 --- a/docs/examples/multi-protocol-agent.md +++ b/docs/examples/multi-protocol-agent.md @@ -36,7 +36,7 @@ An agent that supports both MCP and A2A protocols can be represented as a single ] }, "trustManifest": { - "identity": "urn:air:acme-corp.com:agent:finance", + "identity": "did:web:acme-corp.com", "attestations": [ { "type": "SOC2-Type2", diff --git a/docs/guides/adding-trust.md b/docs/guides/adding-trust.md index b084a70..7f52072 100644 --- a/docs/guides/adding-trust.md +++ b/docs/guides/adding-trust.md @@ -43,7 +43,7 @@ Trust builds on the three conformance levels: === "Level 3 — Trusted" - Adds Trust Manifests with verifiable identity, attestations, and optionally signatures. + Adds signed, subject-bound Trust Manifests with verifiable identity, attestations, and provenance. ```json { @@ -55,8 +55,14 @@ Trust builds on the three conformance levels: "type": "application/a2a-agent-card+json", "url": "...", "trustManifest": { - "identity": "urn:air:acme-corp.com:a2a:finance", - "attestations": [...] + "identity": "did:web:acme-corp.com", + "subject": { + "identifier": "urn:air:acme-corp.com:a2a:finance", + "type": "application/a2a-agent-card+json", + "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + }, + "issuedAt": "2026-03-15T10:00:00Z", + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS1jb3JwLmNvbSNyZWxlYXNlLXNpZ25pbmcta2V5In0..detached-jws-signature" } } ] @@ -65,44 +71,47 @@ Trust builds on the three conformance levels: ## Trust Manifest structure -A Trust Manifest is an object on a Catalog Entry (or Host Info object) with one required field: +A Trust Manifest is an object on a Catalog Entry (or Host Info object). It always requires `identity` and must contain substantive trust evidence. A signed Trust Manifest also requires `subject` and `issuedAt`. -`identity` -: A globally unique URI that identifies this artifact. **Its trust domain must align with the publisher domain in the containing entry's `identifier`.** This binding ties trust claims to the authorized publisher. - -All other fields are optional: - -| Field | Description | -|---|---| -| `identityType` | Type hint for the identity URI: `"did"`, `"spiffe"`, `"dns"` | -| `trustSchema` | Describes the trust framework applied | -| `attestations` | Array of compliance and identity attestation objects | -| `provenance` | Array of provenance links (source code, OCI digests) | -| `privacyPolicyUrl` | URL to the privacy policy | -| `termsOfServiceUrl` | URL to the terms of service | -| `signature` | Detached JWS signature over the Trust Manifest content | -| `extensions` | Named extension map for custom trust data; included in the signed payload when the manifest is signed (see [Extensions](creating-a-catalog.md#extensions)) | +| Field | Requirement | Description | +|---|---|---| +| `attestations` | Optional | Array of compliance and identity attestation objects | +| `expiresAt` | Optional | Time after which a signed Trust Manifest is stale | +| `extensions` | Optional | Named extension map for custom trust data; included in the signed payload when the manifest is signed (see [Extensions](creating-a-catalog.md#extensions)) | +| `identity` | Required | Globally unique URI identifying the issuer to which the Trust Manifest's claims are attributed | +| `identityType` | Optional | Descriptive type hint for the identity URI; consumers determine the mechanism from `identity` itself, and a present value must agree with the applicable profile | +| `issuedAt` | Required when signed | Time at which a signed Trust Manifest was issued | +| `privacyPolicyUrl` | Optional | URL to the privacy policy | +| `provenance` | Optional | Array of provenance links (source code, OCI digests) | +| `signature` | Required at Level 3 | Detached JWS signature over the Trust Manifest content | +| `subject` | Required when signed | Logical artifact release and exact representation covered by a signature | +| `termsOfServiceUrl` | Optional | URL to the terms of service | +| `trustSchema` | Optional | Describes the trust framework applied | !!! tip "Attestation document format" Attestation documents are not restricted to any particular format — they can be human-readable (e.g., a PDF audit report) or machine-readable for automated verification (e.g., JWTs, Verifiable Credentials). -## Adding publisher identity +## Identifying the issuer + +`trustManifest.identity` identifies the party to which the Trust Manifest's claims are attributed. The artifact release is identified by the signed `subject`. + +For a signed Entry Trust Manifest, AI Catalog defines one interoperable issuer profile. The entry uses a standard `urn:air` identifier and the issuer uses the root `did:web` DID for the identifier's publisher domain: -The simplest trust step is asserting publisher identity. Use an attestation of type `"publisher-identity"`: +The following excerpt shows only that relationship; a complete Catalog Entry and substantive Trust Manifest require the additional fields shown elsewhere in this guide. ```json -"trustManifest": { - "identity": "urn:air:acme-corp.com:a2a:finance", - "attestations": [ - { - "type": "publisher-identity", - "uri": "https://trust.acme-corp.com/certs/publisher.jwt", - "description": "Verifies did:web:acme-corp.com as publisher" - } - ] +{ + "identifier": "urn:air:acme-corp.com:a2a:finance", + "trustManifest": { + "identity": "did:web:acme-corp.com" + } } ``` +This relationship allows a verifier to authenticate control of the `acme-corp.com` publisher namespace through the domain's DID document. It does not establish that Acme is reputable, that every claim is accurate, or that the artifact is safe. Those decisions remain consumer or registry policy. + +Other identity mechanisms can be used through separately defined profiles or private agreement, but they do not satisfy the interoperable Level 3 issuer-verification requirements defined by AI Catalog. + ## Adding compliance attestations For regulated environments, add compliance evidence: @@ -165,20 +174,33 @@ The `signature` field holds a detached JWS (RFC 7515): ```json "trustManifest": { - "identity": "urn:air:acme-corp.com:a2a:finance", + "identity": "did:web:acme-corp.com", "attestations": [...], - "signature": "eyJhbGciOiJFUzI1NiJ9..detached-jws-signature" + "subject": { + "identifier": "urn:air:acme-corp.com:a2a:finance", + "type": "application/a2a-agent-card+json", + "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + }, + "issuedAt": "2026-03-15T10:00:00Z", + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS1jb3JwLmNvbSNyZWxlYXNlLXNpZ25pbmcta2V5In0..detached-jws-signature" } ``` -The signature is computed over the Trust Manifest content using JCS (RFC 8785) canonicalization before signing. The signing key is identified in the JWS header and resolved via the identity's DID document, HTTPS endpoint, SPIFFE bundle, or DNS TXT record. +The signature is computed over the Trust Manifest content using JCS (RFC 8785) canonicalization. The stored value is a detached compact JWS with a protected `alg` header of `ES256` and an absolute DID URL in `kid`, such as `did:web:acme-corp.com#release-signing-key`. + +The verifier retrieves the current DID document from `https://acme-corp.com/.well-known/did.json`. The key selected by `kid` must be an ES256 P-256 JWK authorized by the DID document's `assertionMethod` relationship. A key listed only for authentication or key agreement cannot sign an AI Catalog Trust Manifest. Clients verifying signatures should: 1. Extract the `signature` field and remove it from the object 2. Canonicalize the remaining Trust Manifest using JCS -3. Resolve the signing key from the identity URI -4. Verify the JWS signature +3. Confirm the signed `urn:air` publisher domain exactly matches the root `did:web` identity +4. Resolve the DID document and select the `kid` verification method authorized by `assertionMethod` +5. Verify the ES256 JWS signature +6. Confirm `subject.identifier` and `subject.type` match the Catalog Entry; when the entry has a `version`, confirm `subject.version` is present and matches; when `subject.url` is present, confirm it matches the entry URL +7. Verify the artifact content against `subject.digest` + +If any step does not succeed, clients must not treat the Trust Manifest's claims as verified. They may still retain or display the Catalog Entry as unverified, retry a temporarily unavailable DID resolution, or reject the entry according to local policy. ## Trust layers @@ -188,7 +210,7 @@ Trust is progressive — use the layer appropriate to your threat model: |---|---|---| | **0 — TLS** | Prevents eavesdropping and casual tampering | HTTPS certificate chain | | **1 — Provenance digests** | Detects artifact tampering in transit | Hash the fetched artifact, compare to `sourceDigest` | -| **2 — Signed Trust Manifest** | Prevents Trust Manifest forgery | Verify JWS signature; rejects forged attestations | +| **2 — Signed Trust Manifest** | Binds signed claims to an artifact release | Verify the JWS and confirm that the signed subject matches the entry and artifact | | **3 — OCI content-addressing** | Makes modification structurally impossible | All content addressed by digest in an OCI registry | For most use cases, Layer 0 (HTTPS) + Layer 2 (signed Trust Manifest) provides a strong baseline. @@ -207,19 +229,14 @@ A Trust Manifest with identity, compliance attestation, provenance, and signatur "displayName": "Acme Financial Corp" }, "trustManifest": { - "identity": "urn:air:acme-corp.com:a2a:finance", + "identity": "did:web:acme-corp.com", "trustSchema": { "identifier": "urn:trust:acme-enterprise-v1", "version": "1.0", "governanceUri": "https://acme-corp.com/trust/governance.pdf", - "verificationMethods": ["did", "x509"] + "verificationMethods": ["did:web"] }, "attestations": [ - { - "type": "publisher-identity", - "uri": "https://trust.acme-corp.com/certs/publisher.jwt", - "description": "Verifies did:web:acme-corp.com as publisher" - }, { "type": "SOC2-Type2", "uri": "https://trust.acme-corp.com/reports/soc2.pdf", @@ -242,11 +259,18 @@ A Trust Manifest with identity, compliance attestation, provenance, and signatur ], "privacyPolicyUrl": "https://acme-corp.com/legal/privacy", "termsOfServiceUrl": "https://acme-corp.com/legal/terms", - "signature": "eyJhbGciOiJFUzI1NiJ9..detached-jws-signature" + "subject": { + "identifier": "urn:air:acme-corp.com:a2a:finance", + "url": "https://agents.acme-corp.com/finance", + "type": "application/a2a-agent-card+json", + "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + }, + "issuedAt": "2026-03-15T10:00:00Z", + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS1jb3JwLmNvbSNyZWxlYXNlLXNpZ25pbmcta2V5In0..detached-jws-signature" } } ``` ## Next steps -For the full normative requirements on Trust Manifests — key resolution procedures, JCS canonicalization details, verification algorithms — see the [Full Specification](../specification.md). +For the full normative requirements on Trust Manifests — including the `did:web` publisher profile, JCS canonicalization, JWS construction, and verification procedure — see the [Full Specification](../specification.md). diff --git a/docs/mappings/mcp-servers.md b/docs/mappings/mcp-servers.md index b332542..29c89cf 100644 --- a/docs/mappings/mcp-servers.md +++ b/docs/mappings/mcp-servers.md @@ -66,7 +66,7 @@ server's Server Card and whose `type` is the known type "displayName": "Acme Financial Corp" }, "trustManifest": { - "identity": "urn:air:acme-corp.com:mcp:finance-server", + "identity": "did:web:acme-corp.com", "attestations": [ { "type": "publisher-identity", diff --git a/docs/mappings/xregistry.md b/docs/mappings/xregistry.md index 0f3592c..9a0028f 100644 --- a/docs/mappings/xregistry.md +++ b/docs/mappings/xregistry.md @@ -23,7 +23,7 @@ are the catalog entries; each entry's artifact is the Resource document. |:---|:---| | AI Catalog document | A Group instance (e.g. in a `aicatalogs` Group type), or the Registry root when serving a single catalog | | Catalog Entry | A Resource within that Group | -| Entry `identifier` | Resource `id` and `xid` | +| Entry `identifier` | Resource-local `id` and `xid`, with the complete URI preserved in the `ai-catalog.identifier` label | | Entry `type` | Version `contenttype` (with `format` when a named format applies) | | Entry artifact content | Resource document — inline (`` / `base64`) or external (`url`) | | Entry metadata (displayName, description, tags) | `name`, `description`, `labels` | @@ -45,7 +45,9 @@ Tooling converts an AI Catalog JSON document into xRegistry resources: artifact content is stored as the Resource document — inline via `` / `base64`, or by reference via `url`. `type` maps to `contenttype`; entry metadata maps to `name`, - `description`, and `labels`. Multiple entry versions map to Versions. + `description`, and `labels`. The complete entry `identifier` URI is + preserved in the `ai-catalog.identifier` label. Multiple entry versions + map to Versions. 3. **Trust Manifests** are carried as an extension attribute on the Resource (for example `aicatalog_trustmanifest`), or as a related @@ -63,13 +65,14 @@ Tooling converts xRegistry resources back to an AI Catalog JSON document: 1. Retrieve the Group in document view (the xRegistry `?doc` projection), which returns a single self-contained JSON document analogous to `ai-catalog.json`. -2. For each Resource, read its document (inline or via `url`) - and `contenttype` to recover the entry's artifact content and - `type`; map `name`, `description`, and `labels` back to entry - metadata. -3. Read the Trust Manifest from the extension attribute or `xref`'d - Resource, and verify its detached JWS and `subject.digest` against the - served bytes. +2. For each Resource, reconstruct `entry.identifier` from the + `ai-catalog.identifier` label, read its document (inline or via + `url`) and `contenttype` to recover the entry's artifact content + and `type`, and map `name`, `description`, and remaining `labels` back to + entry metadata. +3. Read the Trust Manifest from the extension attribute or `xref`'d Resource, + then verify its detached JWS and signed subject bindings against the + reconstructed entry and served bytes. 4. Resolve `xref`'d nested Groups into nested catalog entries. 5. Assemble the logical `application/ai-catalog+json` document. @@ -94,16 +97,18 @@ holding two entries. This is generated by tooling, not authored by hand: "name": "Acme Finance A2A Agent", "contenttype": "application/a2a-agent-card+json", "labels": { - "ai-catalog.identifier": "urn:acme:agent:finance-a2a" + "ai-catalog.identifier": "urn:air:acme.com:agent:finance-a2a" }, "entryurl": "https://cards.acme.com/finance/a2a-card.json", "aicatalog_trustmanifest": { + "identity": "did:web:acme.com", "issuedAt": "2025-01-01T00:00:00Z", "subject": { + "identifier": "urn:air:acme.com:agent:finance-a2a", "type": "application/a2a-agent-card+json", "digest": "sha256:aaa111..." }, - "signature": "eyJhbGciOiJFUzI1NiJ9..detached-JWS.." + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS5jb20jcmVsZWFzZS1zaWduaW5nLWtleSJ9..detached-JWS" } }, "finance-mcp": { diff --git a/specification/ai-catalog.md b/specification/ai-catalog.md index cbc6c18..1b31d1d 100644 --- a/specification/ai-catalog.md +++ b/specification/ai-catalog.md @@ -157,8 +157,12 @@ The following members are OPTIONAL: : A string containing a detached JWS [[RFC7515]] signature computed over the JCS-canonicalized [[RFC8785]] catalog document (excluding the `signature` member itself), providing catalog-level integrity over the - `entries` array and `host`. It is verified exactly as a Trust Manifest - signature (see [Trust Manifest Signatures](#trust-manifest-signatures)). + `entries` array and `host`. This specification defines the bytes covered by + the signature, but does not define an interoperable catalog-signer identity + or key-selection profile. The `did:web` profile for Entry Trust Manifests + does not apply to this field. A consumer MUST NOT treat a catalog signature + as proof of catalog authenticity unless a separate profile or configured + policy identifies and authorizes its signer. See [Trust Manifest Substitution](#trust-manifest-substitution). ## Host Info @@ -508,20 +512,20 @@ carried on the Catalog Entry (see [Publisher Object](#publisher-object)). A Trust Manifest MUST contain: `identity` -: A string containing a globally unique URI [[RFC3986]] that serves as - the primary subject identifier for this artifact. This SHOULD be a - DID, SPIFFE ID, or URL; these are illustrative and the set of - identity schemes is open. - -When a Trust Manifest appears within a Catalog Entry, the `identity` -field's trust domain MUST align with the publisher domain in the -containing entry's `identifier` field. This binding ensures -trust claims are associated with the authorized publisher namespace even -when `identity` and `identifier` use different URI schemes. -Consumers MUST reject a Trust Manifest whose `identity` domain does not -align with the publisher domain in the containing entry's `identifier`. -The `identity` is carried here so domain binding is part of the signed -payload, rather than inferred only from unsigned entry context. +: A string containing a globally unique URI [[RFC3986]] that identifies the + issuer to which the Trust Manifest's claims are attributed. In a signed + Trust Manifest, the signature verification procedure authenticates this + issuer. The artifact described by the Trust Manifest is identified by + `subject`, not by `identity`. + +This specification defines interoperable issuer authentication for signed +Entry Trust Manifests whose `identity` uses the root `did:web` form described +in [The `did:web` Publisher Profile](#the-did-web-publisher-profile). Other +identity URI schemes can be carried and processed through separately defined +profiles or private agreement, but this specification does not define how they +authorize or verify a Trust Manifest signature. Such processing does not +satisfy the interoperable Level 3 issuer-verification requirements defined by +this specification. When a Trust Manifest appears on a Host Info object, `identity` SHOULD match the host's `identifier` field when present. @@ -543,9 +547,9 @@ contain at least one *substantive* trust member: - a non-empty `provenance` array, or - a `trustSchema`. -The members `identity` and `identityType` (which identify the workload -principal) and the informational members `privacyPolicyUrl`, -`termsOfServiceUrl`, and `extensions` do NOT satisfy this requirement. +The members `identity` and `identityType` and the informational members +`privacyPolicyUrl`, `termsOfServiceUrl`, and `extensions` do NOT satisfy this +requirement. `subject`, `issuedAt`, and `expiresAt` are not substantive on their own: an unsigned `subject` digest is attacker-settable and unverifiable, so they count only as part of a `signature`. @@ -561,18 +565,19 @@ present. The following members are OPTIONAL: `identityType` -: A string providing a type hint for the identity URI (e.g., "did", - "spiffe", "dns"). This field is OPTIONAL when the type is evident - from the URI scheme. +: An optional string providing a descriptive type hint for the identity URI + (for example, "did"). Consumers MUST determine the identity mechanism from + the `identity` URI itself. When an applicable profile constrains this value, + a present value MUST agree with that profile. `trustSchema` : A Trust Schema object as defined in [Trust Schema](#trust-schema-object). `attestations` : An array of Attestation objects as defined in [Attestation](#attestation-object). - This is the mechanism for verifiable claims including publisher - identity verification (using attestation type "publisher-identity"), - compliance certifications, and other proofs. + This carries references to evidence such as publisher-identity credentials, + compliance certifications, and other proofs. Verification semantics come + from the attestation's format or an applicable attestation profile. `provenance` : An array of Provenance Link objects as defined in @@ -619,20 +624,15 @@ provenance: ```json { - "identity": "did:web:acme.com:agent:finance", + "identity": "did:web:acme-corp.com", "identityType": "did", "trustSchema": { "identifier": "urn:trust:acme-enterprise-v1", "version": "1.0", "governanceUri": "https://acme-corp.com/trust/governance.pdf", - "verificationMethods": ["did", "x509"] + "verificationMethods": ["did:web"] }, "attestations": [ - { - "type": "publisher-identity", - "uri": "https://trust.acme-corp.com/certs/publisher.jwt", - "description": "Verifies did:web:acme-corp.com as publisher" - }, { "type": "SOC2-Type2", "uri": "https://trust.acme-corp.com/reports/soc2.pdf", @@ -649,26 +649,35 @@ provenance: "privacyPolicyUrl": "https://acme-corp.com/legal/privacy", "termsOfServiceUrl": "https://acme-corp.com/legal/terms", "subject": { + "identifier": "urn:air:acme-corp.com:agent:finance", + "version": "2.1.0", "url": "https://api.acme-corp.com/agents/finance/v2.1.json", "type": "application/a2a-agent-card+json", "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }, "issuedAt": "2026-03-15T10:00:00Z", - "signature": "eyJhbGciOiJFUzI1NiJ9..detached-jws-signature" + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS1jb3JwLmNvbSNyZWxlYXNlLXNpZ25pbmcta2V5In0..detached-jws-signature" } ``` ## Subject Binding -The Subject object binds a Trust Manifest to the specific artifact it -describes, closing the substitution gap in which an attacker who -controls the catalog document leaves a validly-signed Trust Manifest in -place but repoints the entry to a different artifact. Because the -Subject is part of the signed payload, the artifact reference and its -content digest cannot be changed without invalidating the signature. +The Subject object binds a Trust Manifest to the specific artifact release it +describes, closing substitution gaps in which an attacker who controls the +catalog document leaves a validly-signed Trust Manifest in place but repoints +the entry, assigns the manifest to another logical artifact, or relabels the +artifact as another version. Because the Subject is part of the signed +payload, the release coordinates, artifact reference when present, and content +digest cannot be changed without invalidating the signature. A Subject object MUST contain: +`identifier` +: A string containing the logical identifier of the bound artifact. When the + Subject appears in a Trust Manifest on a Catalog Entry, this MUST exactly + equal the containing entry's `identifier`. Consumers MUST reject a signed + entry Trust Manifest when these values differ. + `type` : A string containing the media type of the bound artifact. This MUST equal the containing Catalog Entry's `type`. @@ -680,7 +689,16 @@ A Subject object MUST contain: bytes served. For an artifact embedded in `data`, the digest is computed over the JCS-canonicalized [[RFC8785]] JSON value. -The following member is OPTIONAL: +The following members are OPTIONAL in the Subject data model: + +`version` +: A string containing the version of the bound artifact release. A signed + Trust Manifest on a Catalog Entry MUST include `subject.version` when the + containing entry has a `version`, and the two values MUST be exactly equal. + When the containing entry omits `version`, `subject.version` MAY still + describe the artifact release and no entry-version comparison is required. + Consumers MUST reject a signed entry Trust Manifest that violates these + requirements. `url` : A string containing the URL of the bound artifact. When present, it @@ -688,16 +706,19 @@ The following member is OPTIONAL: a Trust Manifest whose `subject.url` does not match the entry's `url`. A Trust Manifest that carries a `signature` MUST include a `subject`. -When verifying such a manifest, consumers MUST confirm that the fetched -artifact's media type and digest match the `subject` before relying on -any claim in the Trust Manifest. See +When verifying a signed entry Trust Manifest, consumers MUST confirm that the +entry's identifier, version when present, and media type match the `subject`, +and that the artifact content matches `subject.digest`, before relying on any +claim in the Trust Manifest. See [Verifying Artifact Integrity](#verifying-artifact-integrity). -The `subject.type` and `subject.url` intentionally restate the -entry's `type` and `url` so that those values fall within the signed -payload. This is a deliberate duplication, not redundant metadata: -without it, an attacker who controls the catalog document could change -the entry's media type or location without invalidating the signature. +The `subject.identifier`, `subject.type`, and optional `subject.url` +intentionally restate values from the Catalog Entry so that they fall within +the signed payload. When the entry declares a version, `subject.version` +restates it for the same reason. This is deliberate duplication, not redundant +metadata: without it, an attacker who controls the catalog document could +change the artifact's logical identifier, release version, media type, or +location without invalidating the signature. ## Trust Schema Object @@ -816,8 +837,8 @@ that do not need trust assurance can skip this entirely. Verification procedures direct consumers to fetch URLs that originate in the Trust Manifest itself (`attestation.uri`, `statementUri`, -`registryUri`, and key-resolution endpoints). Because a manifest may be -attacker-controlled before its identity is anchored, these fetches are a +`registryUri`, and identity-resolution endpoints). Because a manifest may be +attacker-controlled before its issuer is authenticated, these fetches are a server-side request forgery (SSRF) and denial-of-service surface. Consumers performing verification MUST: @@ -849,100 +870,190 @@ MUST reject digest values using algorithms shorter than SHA-256. ### Trust Manifest Signatures -The `signature` field carries a detached JWS [[RFC7515]] computed over -the Trust Manifest content, including the `subject` and `issuedAt` -members. To create or verify a signature: - -1. **Canonicalize** the Trust Manifest JSON using JCS (JSON - Canonicalization Scheme) [[RFC8785]]. Remove the `signature` field - itself before canonicalization; all other members — including - `subject` and `issuedAt` — remain in the signed payload. -2. **Select an algorithm** from the allowlist in - [Signature Algorithms](#signature-algorithms). The JWS `alg` header - parameter MUST identify the algorithm used. -3. **Sign** (or verify) the canonical bytes as a detached JWS payload - using the publisher's private (or public) key. -4. **Encode** the resulting JWS in compact serialization and store it - in the `signature` field. - -This approach ensures the signature is stable regardless of JSON key -ordering or whitespace. Because the signed payload includes the -`subject` binding, a verified signature commits the publisher to a -specific artifact digest, not merely to the trust claims. - -Producers SHOULD avoid placing numeric values that do not round-trip -under JCS serialization (e.g., integers outside the range exactly -representable as IEEE 754 doubles) in a signed Trust Manifest, as such -values can cause a verifier's canonicalization to differ from the -producer's. Where large integers are required, encode them as strings. - -### Signature Algorithms - -To prevent signature-forgery attacks, producers and consumers MUST -constrain the JWS algorithms used for Trust Manifest signatures. - -- Consumers MUST reject a signature whose JWS `alg` header is `none`. -- Consumers MUST reject symmetric (MAC-based) algorithms such as - `HS256`; Trust Manifest signatures MUST use an asymmetric algorithm so - that verification cannot be performed with attacker-supplied secret - material (preventing public-key-as-HMAC-secret confusion). -- Producers MUST use, and consumers MUST support, one or more of the - following asymmetric algorithms [[RFC7518]]: `ES256`, `ES384`, - `EdDSA`, `PS256`, `PS384`, or `RS256`. -- Consumers MUST determine the expected algorithm and key from the - resolved trust anchor (see [Trust Anchoring](#trust-anchoring)) and - MUST NOT let the `alg` header alone select a verification algorithm in - a way that downgrades security. Consumers SHOULD pin the expected key - via the JWS `kid` header. - -These constraints follow the JSON Web Token current best practices -[[RFC8725]]. - -### Key Resolution - -Consumers resolve the signer's public key based on the `identity` -URI scheme: - -DID (e.g., `did:web:example.com`) -: Resolve the DID Document per the relevant DID method specification - and extract the verification key from the `verificationMethod` - array. - -HTTPS URL (e.g., `https://example.com/.well-known/jwks.json`) -: Fetch the JWK Set [[RFC7517]] at the specified URL and select the - key matching the JWS `kid` header. - -SPIFFE ID (e.g., `spiffe://example.com/service`) -: Obtain the X.509 SVID from the SPIFFE Workload API and extract - the public key from the leaf certificate. - -DNS -: Resolve the domain's TLS certificate and extract the public key, - or look up a DNSKEY/TXT record containing the JWK thumbprint. - -### Trust Anchoring - -Verifying a Trust Manifest signature proves that the manifest was signed -by the holder of the key associated with its `identity`. It does NOT, by -itself, prove that the `identity` is the legitimate publisher of the -artifact. An attacker who controls the catalog document can replace both -the `identity` and the key it resolves to, then sign the forged manifest -with their own key — every internal check would still pass. - -Consumers MUST therefore anchor the `identity` (or signing key) to a -trust root established out of band, independent of the catalog document. -Acceptable anchors include: - -- A pinned allowlist of trusted publisher identities or keys. -- A registry or marketplace that vets publisher identities and serves - the catalog over a channel the consumer independently trusts. -- An identity method that proves control of a name the consumer already - trusts (e.g., a `did:web` whose domain matches an expected publisher, - validated against that domain's TLS-authenticated endpoint). - -A verified signature without an anchored identity establishes integrity -and internal consistency only; consumers MUST NOT treat it as proof of -publisher authenticity. +The `signature` field carries a detached JWS [[RFC7515]] computed over the +Trust Manifest content. The signature authenticates the issuer identified by +`identity` and covers every claim in the Trust Manifest, including `subject` +and `issuedAt`. + +To construct the JWS payload, remove the `signature` member from the Trust +Manifest and canonicalize the remaining JSON object using JCS [[RFC8785]]. The +UTF-8 encoding of the resulting JSON text is the JWS payload. The stored JWS +MUST use compact serialization with its payload segment omitted, as described +for detached content in Appendix F of [[RFC7515]]. Although the payload segment +is omitted from the stored value, the base64url encoding of the payload remains +part of the JWS Signing Input. + +The protected JWS header MUST contain `alg`, identifying the signature +algorithm. + +The protected header MUST NOT contain a `b64` parameter. This specification +uses the ordinary base64url-encoded JWS payload defined by [[RFC7515]] and does +not use the unencoded-payload option from [[RFC7797]]. The protected header +MAY contain additional parameters as permitted by the applicable +issuer-authentication profile, which also defines its key-selection +requirements. + +This construction ensures the signature is stable regardless of JSON member +ordering or insignificant whitespace. Because the signed payload includes the +`subject` binding, a verified Entry Trust Manifest commits its issuer's claims +to a specific logical artifact release and representation. + +Producers SHOULD avoid placing numeric values that do not round-trip under JCS +serialization, such as integers outside the range exactly representable as +IEEE 754 doubles, in a signed Trust Manifest. Such values can cause a +verifier's canonicalization to differ from the producer's. Where large +integers are required, encode them as strings. + +### The `did:web` Publisher Profile + +The `did:web` Publisher Profile defines one interoperable way to authenticate +the issuer of a signed Entry Trust Manifest. It deliberately supports a narrow +combination: + +- the artifact has a standard `urn:air` identifier; +- the Trust Manifest issuer is the root `did:web` DID corresponding to the + publisher domain in that identifier; and +- the issuer signs with an ES256 key authorized for assertions in its current + DID document. + +Other identity schemes, delegated issuers, path-based `did:web` DIDs, and +additional signature algorithms require separate profiles. Implementations +MAY support them by agreement, but such support is not interoperable under this +specification. + +#### Publisher Namespace Authorization + +A Catalog Entry with a signed Trust Manifest MUST use the standard `urn:air` +identifier syntax defined in [Catalog Entry](#catalog-entry). The `{publisher}` +component is the text after the literal `urn:air:` prefix and before the next +colon. The signed `subject.identifier` binds the Trust Manifest to the complete +identifier as described in [Subject Binding](#subject-binding). + +Under the `did:web` Publisher Profile, `{publisher}` MUST be valid as the domain +component of a root `did:web` DID under [[DIDWEB]]. It MUST be serialized as +lowercase ASCII and MUST NOT contain a port, an IP address, a trailing root dot, +or a Unicode U-label. An internationalized domain can be used in its IDNA +A-label form [[RFC5890]] [[RFC5891]]. + +The Trust Manifest's `identity` MUST be exactly the string `did:web:` followed +by the `{publisher}` component. For example, the issuer identity for an +artifact named `urn:air:example.com:agent:billing` is +`did:web:example.com`. The `did:web` Publisher Profile does not support ports +or method-specific paths. Exact equality is required; subdomain, suffix, and +organizational-ownership heuristics MUST NOT be used. + +When `identityType` is present, its value MUST be `did`. Consumers still select +the `did:web` Publisher Profile from the `did:web` identity URI, not from the +type hint. + +The equality rule connects two independently useful mechanisms. The signed +`subject.identifier` states the namespace in which the release is published. +Resolving the matching `did:web` DID through HTTPS authenticates control of +that namespace's domain. Consequently, a consumer does not need a separately +pinned DID merely to determine which issuer is authorized for a standard +`urn:air` identifier. + +This authorization proves control of the publisher namespace. It does not +prove that the publisher is reputable, that its claims are accurate, or that +the artifact is safe to use. Consumers and registries remain responsible for +deciding which publisher domains and claims satisfy their policies. + +#### JWS Algorithm and Key Requirements + +Producers conforming to the `did:web` Publisher Profile MUST use ES256 as +defined by [[RFC7518]]. Consumers implementing the `did:web` Publisher Profile +MUST support ES256 and MUST reject any other `alg` value when applying that +profile. A different algorithm can be introduced by a future signature +profile; an implementation-specific choice does not extend the `did:web` +Publisher Profile. + +The protected JWS header MUST contain `kid`, identifying the verification +method that authorizes the signature. It MUST NOT contain `jku`, `jwk`, `x5u`, +or `x5c`; a verifier applying the `did:web` Publisher Profile selects key +material only through the issuer's DID document, never from a key source named +by the signature itself. + +The protected `kid` value MUST be an absolute DID URL consisting of the +manifest's exact `identity` followed by a non-empty fragment. For example: + + did:web:example.com#release-signing-key + +The `kid` MUST NOT contain a path or query component. It identifies a +verification method in the issuer's DID document; it does not identify another +DID or an external key document. + +#### DID Document Resolution and Key Selection + +The verifier MUST resolve `identity` according to the `did:web` method +[[DIDWEB]] and process the result as a DID document according to DID Core +[[DIDCORE]]. The resolution MUST satisfy the safe-fetching requirements in +[Safe Fetching](#safe-fetching). Resolution fails under the `did:web` Publisher +Profile if the DID document cannot be retrieved and validated or if its `id` is +not exactly equal to `identity`. + +The verification method selected by `kid` MUST be authorized by the DID +document's `assertionMethod` verification relationship. An +`assertionMethod` entry can contain the verification method directly or can +reference a method in the top-level `verificationMethod` collection. After +resolving relative DID URLs as defined by DID Core, the verifier MUST select +exactly one verification method whose `id` exactly equals `kid`. + +A key's presence in the top-level `verificationMethod` collection does not by +itself authorize the key to sign a Trust Manifest. A key used only for another +relationship, such as `authentication` or `keyAgreement`, MUST NOT be accepted. +The selected verification method's `controller` MUST exactly equal `identity`; +the `did:web` Publisher Profile does not support a verification method +controlled by another DID. + +The selected verification method MUST contain `publicKeyJwk` [[RFC7517]]. The +JWK MUST describe a P-256 elliptic-curve public key: `kty` MUST be `EC`, `crv` +MUST be `P-256`, and `x` and `y` MUST contain valid curve coordinates. It MUST +NOT contain private key material. When present, `alg` MUST be `ES256`, `use` +MUST be `sig`, and `key_ops` MUST permit `verify`. A `kid` member inside the +JWK, if present, is not used for verification-method selection; the DID +verification method's `id` is authoritative. + +#### Current-State Verification + +This profile verifies against the DID document returned at verification time. +If the key identified by `kid` is no longer authorized by `assertionMethod`, +verification does not succeed. The Trust Manifest's `issuedAt` value records a +claim by the issuer; it is not an independently trusted timestamp and cannot +prove that a removed key was authorized in the past. + +Historical verification requires a trustworthy record of the DID document and +the relevant key authorization at the time of signing. This specification does +not define such a record. Applications that require durable historical +verification need an additional mechanism, such as a versioned DID method, +transparency log, or independently timestamped signature profile. + +#### Verification Result + +Verification succeeds under the `did:web` Publisher Profile only when all of +the following have been established: + +1. The Trust Manifest and `subject` contain all fields required for a signed + Entry Trust Manifest. +2. The entry identifier, signed subject identifier, and issuer identity satisfy + the publisher namespace authorization rules above. +3. The JWS protected header and detached payload satisfy + [Trust Manifest Signatures](#trust-manifest-signatures). +4. The DID document resolves successfully and authorizes exactly one suitable + verification method for `kid` under `assertionMethod`. +5. The ES256 signature verifies over the reconstructed JWS Signing Input. +6. The signed subject matches the Catalog Entry and artifact as described in + [Verifying Artifact Integrity](#verifying-artifact-integrity). + +If any step does not succeed, the consumer MUST NOT rely on the Trust +Manifest's claims as verified and MUST NOT count the entry as satisfying Level +3. The consumer MAY retain or display the Catalog Entry as unverified, retry a +temporarily unavailable resolution, or reject the entry according to local +policy. + +When `expiresAt` is present and is in the past, the cryptographic signature can +still be valid, but the consumer MUST NOT rely on the Trust Manifest's claims +as current or count the entry as satisfying Level 3. The consumer MAY retain or +display the manifest as expired. ### Verifying Host Identity @@ -952,25 +1063,22 @@ To verify the host of a catalog: domain. 2. If `host.identifier` is a DID, resolve the DID Document and confirm the hosting domain appears in the DID Document's `service` endpoints. -3. If `host.trustManifest` is present and signed, verify the - signature as described above. - -### Verifying Publisher Identity +3. If `host.trustManifest` is present and signed, apply a separately configured + Host signature policy. The `did:web` Publisher Profile applies only to an + Entry Trust Manifest and does not define which party is authorized to sign + Host claims. -To verify the publisher of an artifact: +### Publisher Metadata -1. Locate the `publisher-identity` attestation in the Trust - Manifest's `attestations` array. -2. Fetch the attestation document (typically a JWT) from the `uri`. -3. Verify the JWT signature against the publisher's public key - (resolved from `publisher.identifier`). -4. Confirm the JWT claims bind the `publisher.identifier` to the Trust - Manifest's `identity`. +The `did:web` Publisher Profile authenticates the issuer as the controller of +the publisher domain in the signed `urn:air` identifier. It does not +authenticate the optional `publisher` object's human-readable metadata. The +`publisher` object resides outside the Trust Manifest signature, so consumers +MUST treat its fields as advisory unless another verified mechanism binds them +to the signed issuer. -The `publisher` object resides on the Catalog Entry, outside the Trust -Manifest signature. Consumers MUST treat `publisher` fields as advisory -unless a verified `publisher-identity` attestation cryptographically -binds `publisher.identifier` to the signed manifest's `identity`. +This specification does not define a verification profile for a +`publisher-identity` attestation. ### Verifying Artifact Integrity @@ -978,23 +1086,26 @@ When a Trust Manifest carries a `signature`, it MUST include a `subject` that binds it to the artifact (see [Subject Binding](#subject-binding)). To verify artifact integrity: -1. Verify the Trust Manifest signature - ([Trust Manifest Signatures](#trust-manifest-signatures)) and anchor - the identity ([Trust Anchoring](#trust-anchoring)). -2. Confirm `subject.type` equals the entry's `type`, and, when +1. Authenticate the issuer and verify the Trust Manifest signature using + [The `did:web` Publisher Profile](#the-did-web-publisher-profile). +2. Confirm `subject.identifier` exactly equals the entry's `identifier`. +3. When the entry has a `version`, confirm `subject.version` is present and + exactly equal. +4. Confirm `subject.type` equals the entry's `type`, and, when `subject.url` is present, that it equals the entry's `url`. -3. Fetch the artifact content from the entry's `url`, or take it from +5. Fetch the artifact content from the entry's `url`, or take it from the entry's `data`, observing the limits in [Safe Fetching](#safe-fetching). -4. Compute the digest of the fetched bytes (for `url`) or of the +6. Compute the digest of the fetched bytes (for `url`) or of the JCS-canonicalized value (for `data`) using the algorithm named in `subject.digest`. -5. Compare the computed digest to `subject.digest`. Reject the artifact +7. Compare the computed digest to `subject.digest`. Reject the artifact if they differ. Because the `subject` is part of the signed payload, this check binds -the publisher's signature to the exact artifact, defeating catalog-level -substitution of the artifact URL or content. The OPTIONAL +the verified signature to the logical artifact release and its exact +representation, defeating catalog-level substitution or relabeling of the +artifact identifier, version, URL, media type, or content. The OPTIONAL `provenance[].sourceDigest` records the digest of an upstream *source* (e.g., a Git commit) and is complementary to — not a substitute for — the `subject` digest. @@ -1012,17 +1123,21 @@ For each attestation in the `attestations` array: ### Provenance Statements A Provenance Link MAY reference a signed provenance statement via -`statementUri` and the key that signed it via `signatureRef`. To verify -such a statement: +`statementUri` and the key that signed it via `signatureRef`. The statement's +own format MUST define its signature algorithm, signer authorization, +key-selection rules, and verification procedure. The `did:web` Publisher +Profile for Entry Trust Manifests does not automatically apply to a provenance +statement, whose issuer may be a builder, transparency service, or other party. + +To process such a statement: 1. Fetch the statement document from `statementUri`, observing [Safe Fetching](#safe-fetching). -2. Resolve the key indicated by `signatureRef` using the procedure in - [Key Resolution](#key-resolution) and anchor it per - [Trust Anchoring](#trust-anchoring). -3. Verify the statement's signature using an algorithm from - [Signature Algorithms](#signature-algorithms). -4. Confirm the statement's subject matches the artifact's `subject` +2. Validate the statement and its signature according to the statement + format's verification procedure and the consumer's trust policy. + `signatureRef` can assist key discovery when that format defines how to use + it, but the value is not a trust anchor by itself. +3. Confirm the statement's subject matches the artifact's `subject` digest. Treat an unverifiable statement as absent, not as a failure of the artifact itself. @@ -1333,13 +1448,20 @@ In addition to Level 2 requirements, a Trusted Catalog: - Each such `trustManifest` MUST carry a `signature`, a `subject` binding it to the artifact ([Subject Binding](#subject-binding)), and an `issuedAt` timestamp -- Consumers MUST verify the signature, anchor the identity - ([Trust Anchoring](#trust-anchoring)), and confirm the `subject` - digest before relying on any claim +- A signed Entry Trust Manifest MUST satisfy + [The `did:web` Publisher Profile](#the-did-web-publisher-profile), including + its `urn:air` namespace authorization and issuer-key requirements +- Consumers MUST authenticate the issuer, verify the signature, and confirm + the `subject` digest before relying on any claim. For an Entry Trust + Manifest, consumers MUST also confirm the subject's identifier, version when + the entry declares one, media type, and optional URL against the containing + entry - SHOULD provide catalog-level integrity, either by serving the catalog through a content-addressed channel (see - [Security Considerations](#security-considerations)) or by including a - top-level catalog `signature` + [Security Considerations](#security-considerations)) or by using a + catalog-signature profile that identifies and authorizes the catalog signer. + The top-level `signature` field defines the signed bytes but this + specification does not define such a signer profile - MAY include `publisher` objects on entries with verifiable identifiers - Enables verifiable identity, compliance attestations, and provenance tracking @@ -1377,17 +1499,20 @@ appropriate to their threat model. **Layer 2 — Signed Trust Manifest** : The Trust Manifest includes a `signature` field (detached JWS) and a - `subject` that binds the signature to the artifact's content digest - (see [Subject Binding](#subject-binding)). The consumer verifies the - signature, anchors the signer's identity to a trust root - ([Trust Anchoring](#trust-anchoring)), and confirms the `subject` - digest before trusting any claim. This closes the substitution gap - from Layer 1: because the signed payload commits to the artifact - digest, an attacker cannot repoint the entry to a different artifact - or forge claims without the publisher's private key. Consumers that - rely on trust metadata MUST verify signatures and MUST reject Trust - Manifests whose signature does not validate, whose `subject` does not - match the fetched artifact, or whose identity cannot be anchored. + `subject` that binds the signature to the artifact's logical identifier, + version when present, media type, and content digest (see + [Subject Binding](#subject-binding)). For a standard `urn:air` identifier, + [The `did:web` Publisher Profile](#the-did-web-publisher-profile) authorizes + the root `did:web` identity for the identifier's publisher domain and + verifies a key authorized by that DID for assertions. The consumer then + confirms the `subject` bindings before trusting any claim. This closes the + substitution gap from Layer 1: because the signed payload commits to the + logical release and its representation, an attacker cannot relabel or + repoint the entry to a different artifact or forge claims without control of + the publisher domain and an authorized signing key. Consumers that rely on + trust metadata MUST NOT treat a Trust Manifest as verified when its issuer + cannot be authenticated, its signature does not validate, or its `subject` + does not match the entry and fetched artifact. **Layer 3 — Content-Addressed Distribution (OCI)** : The catalog is distributed through an OCI registry where all content @@ -1427,9 +1552,9 @@ this threat: unauthorized modification. - **Layer 1** enables post-fetch integrity checks but does not prevent whole-entry substitution. -- **Layer 2** binds the signed Trust Manifest to the artifact digest - via `subject`, preventing both Trust Manifest forgery and artifact - substitution under a valid signature. +- **Layer 2** binds the signed Trust Manifest to the logical artifact release + and its representation via `subject`, preventing Trust Manifest forgery and + artifact substitution or relabeling under a valid signature. - **Layer 3** makes modification structurally impossible through content-addressing. @@ -1442,21 +1567,26 @@ both. This specification defends against substitution with three compounding mechanisms: - **Subject binding.** A signed Trust Manifest MUST include a `subject` - that commits to the artifact's media type and content digest (see - [Subject Binding](#subject-binding)). The artifact reference therefore - cannot be changed without invalidating the signature. -- **Trust anchoring.** A signature is only meaningful once the signer's - identity is anchored to a trust root established out of band (see - [Trust Anchoring](#trust-anchoring)); otherwise an attacker can sign a - forged manifest with their own key. + that commits to the artifact's logical identifier, version when present, + media type, and content digest (see [Subject Binding](#subject-binding)). + The artifact release or representation therefore cannot be changed without + invalidating the signature. +- **Publisher namespace authorization.** A verified Entry Trust Manifest uses + the signed `urn:air` identifier to select its publisher domain and requires a + matching root `did:web` issuer. Resolving that DID through HTTPS and + requiring an assertion-authorized key prevents an attacker from substituting + a self-selected issuer while continuing to claim the original publisher's + namespace. - **Catalog-level integrity.** Per-entry signatures do not prevent an attacker from adding, removing, or reordering whole entries. Hosts SHOULD additionally provide catalog-level integrity, either by serving the catalog through a content-addressed channel (Layer 3) or by including a top-level catalog `signature` computed over the JCS-canonicalized [[RFC8785]] catalog document (excluding the - `signature` member itself) and verified exactly as a Trust Manifest - signature. + `signature` member itself). This specification does not define the identity + or authorization of the catalog signer; deployments using that field for + authenticity need a separate catalog-signature profile or configured + policy. ## Identifier Typosquatting @@ -1544,6 +1674,8 @@ classDiagram signature string } class Subject { + identifier string + version string url string type string digest string @@ -1714,10 +1846,21 @@ TrustManifest = { ? provenance: [* ProvenanceLink], ? privacyPolicyUrl: text, ? termsOfServiceUrl: text, + ? subject: Subject, + ? issuedAt: tdate, + ? expiresAt: tdate, ? signature: text, ? extensions: { * text => any } } +Subject = { + identifier: text, + ? version: text, + type: text, + digest: text, + ? url: text +} + TrustSchema = { identifier: text, version: text, @@ -1765,18 +1908,13 @@ artifact types including a nested catalog packaging related artifacts: "description": "A2A agent for financial workflows.", "tags": ["finance", "a2a"], "publisher": { - "identifier": "did:web:acme-corp.com", + "identifier": "did:web:acme.com", "displayName": "Acme Financial Corp" }, "trustManifest": { - "identity": "spiffe://acme.com/ns/finance/sa/finance-a2a-pod", - "identityType": "spiffe", + "identity": "did:web:acme.com", + "identityType": "did", "attestations": [ - { - "type": "publisher-identity", - "uri": "https://trust.acme.com/certs/publisher.jwt", - "description": "Verifies did:web:acme-corp.com as publisher" - }, { "type": "SOC2-Type2", "uri": "https://trust.acme.com/reports/soc2.pdf", @@ -1822,7 +1960,7 @@ artifact types including a nested catalog packaging related artifacts: "type": "application/parquet", "url": "https://data.acme-corp.com/market-2026q1.parquet", "trustManifest": { - "identity": "urn:air:acme.com:data:market-2026q1", + "identity": "did:web:acme.com", "provenance": [ { "relation": "publishedFrom", @@ -1835,8 +1973,14 @@ artifact types including a nested catalog packaging related artifacts: ] }, "trustManifest": { - "identity": "urn:air:acme.com:plugin:finance-suite", - "signature": "eyJhbGciOiJFUzI1NiJ9..detached" + "identity": "did:web:acme.com", + "subject": { + "identifier": "urn:air:acme.com:plugin:finance-suite", + "type": "application/ai-catalog+json", + "digest": "sha256:22223333444455556666777788889999aaaabbbbccccddddeeeeffff00001111" + }, + "issuedAt": "2026-03-20T14:00:00Z", + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS5jb20jcmVsZWFzZS1zaWduaW5nLWtleSJ9..detached" }, "updatedAt": "2026-03-20T14:00:00Z" } @@ -1913,7 +2057,7 @@ containing both protocol-specific entries: "description": "Finance agent accessible via both MCP and A2A protocols.", "tags": ["finance", "dual-protocol"], "publisher": { - "identifier": "did:web:acme-corp.com", + "identifier": "did:web:acme.com", "displayName": "Acme Financial Corp" }, "data": { @@ -1932,8 +2076,8 @@ containing both protocol-specific entries: ] }, "trustManifest": { - "identity": "spiffe://acme.com/ns/finance/sa/finance-agent-pod", - "identityType": "spiffe", + "identity": "did:web:acme.com", + "identityType": "did", "attestations": [ { "type": "SOC2-Type2", diff --git a/specification/examples/ai-catalog.json b/specification/examples/ai-catalog.json index b2989aa..1ae1906 100644 --- a/specification/examples/ai-catalog.json +++ b/specification/examples/ai-catalog.json @@ -32,12 +32,13 @@ } ], "subject": { + "identifier": "urn:air:acme-corp.com:agent:finance", "url": "https://api.acme-corp.com/agents/acme-finance-agent.json", "type": "application/a2a-agent-card+json", "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }, "issuedAt": "2026-02-22T16:30:00Z", - "signature": "eyJhbGciOiJFUzI1NiJ9..detached-jws-signature" + "signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6YWNtZS1jb3JwLmNvbSNyZWxlYXNlLXNpZ25pbmcta2V5In0..detached-jws-signature" } }, { diff --git a/specification/respec-config.json b/specification/respec-config.json index 50c40ea..4ad90be 100644 --- a/specification/respec-config.json +++ b/specification/respec-config.json @@ -29,6 +29,24 @@ "status": "RFC", "publisher": "IETF" }, + "RFC7797": { + "title": "JSON Web Signature (JWS) Unencoded Payload Option", + "href": "https://www.rfc-editor.org/rfc/rfc7797", + "status": "RFC", + "publisher": "IETF" + }, + "DIDCORE": { + "title": "Decentralized Identifiers (DIDs) v1.0", + "href": "https://www.w3.org/TR/did-core/", + "status": "W3C Recommendation", + "publisher": "W3C" + }, + "DIDWEB": { + "title": "did:web Method Specification (8 May 2026 snapshot)", + "href": "https://github.com/w3c-ccg/did-method-web/blob/ea423c114e6f2537498ee6f94e8d794c64f60c18/index.html", + "status": "W3C Community Group Draft Report", + "publisher": "W3C Credentials Community Group" + }, "RFC8725": { "title": "JSON Web Token Best Current Practices", "href": "https://www.rfc-editor.org/rfc/rfc8725", diff --git a/specification/trust-manifest-threat-model.md b/specification/trust-manifest-threat-model.md index 846e598..1a4d5fb 100644 --- a/specification/trust-manifest-threat-model.md +++ b/specification/trust-manifest-threat-model.md @@ -15,7 +15,9 @@ version selection) are in scope only where they affect trust decisions. This document is the security analysis that motivates the normative hardening in [ai-catalog.md](ai-catalog.md) and the decisions recorded -in [ADR-0019](../adr/0019-trust-manifest-artifact-binding.md) and +in [ADR-0019](../adr/0019-trust-manifest-artifact-binding.md), +[ADR-0025](../adr/0025-bind-signed-trust-manifests-to-releases.md), +[ADR-0027](../adr/0027-did-web-entry-signature-profile.md), and [ADR-0009](../adr/0009-trust-manifest-substitution.md). It exists to answer the substitution-attack concern raised in ADR-0009: *"The substitution attack of changing out the trust manifest is very real, @@ -25,9 +27,12 @@ especially if there's no tamper-proofness built in."* ### 1.1 External entities -Publisher -: Produces an artifact and (optionally) signs a Trust Manifest that - makes verifiable claims about it. Holds a signing key. +Publisher / Trust Manifest issuer +: Assigns the artifact's `urn:air` identifier, produces the artifact, and makes + the claims in its Trust Manifest. Under the `did:web` Publisher Profile, the + issuer is the same domain authority as the publisher. Publishing automation + can perform the signing operation with a key authorized by the publisher's + DID document, but a separately identified issuer is not supported. Catalog Host : Serves the AI Catalog document. May or may not be the publisher. @@ -41,9 +46,8 @@ Consumer / Client Third-party endpoints : Servers referenced from a Trust Manifest: attestation documents - (`attestation.uri`), key material (DID documents, JWKS URLs, SPIFFE - Workload API), provenance statements (`statementUri`, - `registryUri`). + (`attestation.uri`), issuer key material (the publisher's `did:web` DID + document), and provenance statements (`statementUri`, `registryUri`). OCI registry : Optional content-addressed distribution channel (Layer 3). @@ -57,7 +61,7 @@ Attacker - Trust Manifest (peer element on an entry or host) - Artifact bytes (served at `entry.url` or inlined in `entry.data`) - Attestation documents -- Key material (DID documents, JWK Sets, X.509 SVIDs) +- Key material (DID documents and JSON Web Keys) - Provenance statements - OCI registry content + signatures (Cosign / Notation) @@ -66,7 +70,7 @@ Attacker 1. Catalog fetch and parse 2. Entry / nested-catalog resolution 3. Trust Manifest signature verification -4. Signer key resolution (from `identity`) +4. Publisher-namespace authorization and issuer-key resolution 5. Artifact fetch and digest computation 6. Attestation fetch and validation 7. Provenance evaluation @@ -84,7 +88,7 @@ flowchart LR Cons --> ArtFetch[Fetch + digest artifact] Cons --> AttFetch[Fetch + validate attestation] end - KeyRes -->|B4| KeyEP[(Key endpoint: DID/JWKS/SPIFFE)] + KeyRes -->|B4| KeyEP[(Publisher did:web document)] ArtFetch -->|B1/B5| Art[(Artifact)] AttFetch -->|B4/B5| AttEP[(Attestation / provenance endpoints)] Reg[(OCI registry)] -. B2 alt .-> Cons @@ -101,8 +105,8 @@ Trust boundaries: catalog document at rest (hosting account, CDN, object store, DNS control, repository). This is the boundary the substitution attack crosses. -- **B3 — Publisher signing key.** The boundary between data an - attacker can author and data that requires the publisher's private +- **B3 — Trust Manifest issuer signing key.** The boundary between data an + attacker can author and data that requires the issuer's private key. - **B4 — Third-party endpoints.** Servers whose URLs appear *inside* the (possibly attacker-controlled) Trust Manifest. The consumer is @@ -115,7 +119,7 @@ Trust boundaries: | # | Asset | Why it matters | |---|-------|----------------| | AS1 | Integrity + authenticity of the artifact ↔ trust binding | The whole point of the Trust Manifest: that *these* claims describe *this* artifact. | -| AS2 | Publisher signing keys | Compromise lets an attacker forge authentic-looking trust. | +| AS2 | Trust Manifest issuer signing keys | Compromise lets an attacker forge authentic-looking trust. | | AS3 | The consumer's trust decision | The ultimate target — install/invoke a malicious artifact under a trusted label. | | AS4 | Availability of catalog resolution | Consumers depend on resolving catalogs to find tools. | | AS5 | Consumer privacy / telemetry | Fetches triggered by verification can leak who is evaluating what, and from where. | @@ -125,7 +129,7 @@ Trust boundaries: | ID | Agent | Capability | In scope | |----|-------|------------|----------| | A1 | Network attacker | On-path, **cannot** break TLS | Yes (bounded) | -| **A2** | **Catalog-write attacker** | Can modify the catalog document (compromised hosting account, CDN edge, object-store creds, DNS hijack, MITM of a non-TLS origin, or a malicious mirror). **Cannot** obtain the publisher's private key. | **Yes — primary** | +| **A2** | **Catalog-write attacker** | Can modify the catalog document (compromised hosting account, CDN edge, object-store credentials, compromise of the catalog origin, or a malicious mirror). **Cannot** control the publisher's `did:web` domain or obtain an authorized issuer key. | **Yes — primary** | | A3 | Malicious publisher | Authors and signs manifests with a key it legitimately controls | Yes | | A4 | Compromised third-party endpoint | Controls an attestation/key/provenance URL referenced by a manifest | Yes | | A5 | Malicious nested/federated author | Authors a sub-catalog that a parent delegates to | Yes | @@ -138,16 +142,18 @@ manifest can only attest to what the publisher saw). ## 4. STRIDE Analysis -Each threat references the finding IDs (F1–F10) carried in the -companion review and maps to a mitigation in section 6. +Each threat references the finding IDs carried in the companion review and +maps to a mitigation in section 6. Pre-mitigation descriptions record the +specification state at the time each gap was found; the corresponding controls +describe the resulting design. ### 4.1 Spoofing | ID | Threat | Boundary | Agent | Finding | |----|--------|----------|-------|---------| -| S1 | **Self-asserted identity.** Key resolution derives the signer's key *from* the `identity` URI in the manifest. A catalog-write attacker substitutes both `identity` and the key endpoint, signs with their own key, and the signature verifies. Verification proves "signed by whoever owns this identity," not "signed by a publisher I trust." | B2/B3 | A2 | F3 | +| S1 | **Self-asserted identity.** If key resolution derives the signer's key only from an unconstrained `identity` URI in the manifest, a catalog-write attacker can substitute both `identity` and the key endpoint and sign with their own key. Verification then proves only "signed by whoever owns this identity." | B2/B3 | A2 | F3 | | S2 | **Publisher spoofing.** `publisher.identifier`/`displayName` live on the entry, outside any signature; an attacker edits them to impersonate a reputable vendor. | B2 | A2 | F9 | -| S3 | **Host spoofing.** `host.identifier` is attacker-controllable when the catalog is compromised; DID-service-endpoint checks only prove internal consistency of attacker-chosen data. | B2 | A2 | F3 | +| S3 | **Host spoofing.** `host.identifier` is attacker-controllable when the catalog is compromised; DID-service-endpoint checks only prove internal consistency of attacker-chosen data. | B2 | A2 | — | ### 4.2 Tampering @@ -156,6 +162,7 @@ companion review and maps to a mitigation in section 6. | **T1** | **Artifact substitution under a valid signature (headline).** The `signature` covers only the Trust Manifest JSON. The artifact is referenced by `entry.url`/`data`/`mediaType`, which sit *outside* the signed bytes. The only binding is `identity == entry.identifier`. An attacker keeps the publisher's validly-signed manifest, leaves `identifier` intact, and repoints `url` to a malicious artifact. Signature still verifies. `provenance.sourceDigest` is OPTIONAL and denotes the *source* (`publishedFrom`), not the served artifact, so it does not close the gap. | B2 | A2 | F1 | | T2 | **Unsigned manifest tampering.** Level 3 ("Trusted Catalog") requires only that a `trustManifest` be *present*, not signed. An attacker freely rewrites every claim in an unsigned manifest. The "Trusted" label implies cryptographic assurance that is not enforced. | B2 | A2 | F2 | | T3 | **Catalog-level structural tampering.** Nothing signs the `entries` array or `host`. Even with per-entry signed manifests, an attacker injects new (malicious, unsigned) entries, deletes entries, or reorders/selects versions. Only OCI Layer 3 addresses this today. | B2 | A2/A5 | F6 | +| T4 | **Release-coordinate relabeling under a valid signature.** A signed `subject` binds the exact artifact representation but not `entry.identifier` or `entry.version`. An attacker keeps the valid manifest and exact bytes while assigning them to another logical artifact in the same publisher namespace or labeling an old release with a higher version. Signature, type, URL, and digest checks still pass. | B2 | A2 | F11 | ### 4.3 Repudiation @@ -188,10 +195,9 @@ companion review and maps to a mitigation in section 6. ## 5. Attack Scenarios **SC-1 — URL swap under a valid signature (primary).** Acme publishes -`urn:acme:agent:finance` with a manifest signed by +`urn:air:acme-corp.com:agent:finance` with a manifest signed by `did:web:acme-corp.com`. An attacker who compromises Acme's CDN leaves -the manifest byte-for-byte intact (so the signature and -`identity == identifier` checks pass) and changes only `entry.url` to a +the manifest byte-for-byte intact and changes only `entry.url` to a look-alike host serving a trojaned agent. A Layer-2 consumer "verifies the signature," sees green, and installs malware. *Closed by F1 mitigation: the signed manifest MUST commit to the artifact digest.* @@ -201,20 +207,26 @@ unsigned manifests. A consumer treats "Trusted Catalog" as cryptographic assurance and accepts attacker-rewritten attestations. *Closed by F2 mitigation: signatures REQUIRED at Level 3.* -**SC-3 — Self-signed substitution.** Attacker replaces `identity` with -`did:web:attacker.example`, repoints the key endpoint to their own -JWKS, and signs the whole (malicious) manifest. Every internal check -passes. *Closed by F3 mitigation: out-of-band trust anchoring — -verified signature ≠ trusted publisher.* +**SC-3 — Self-signed substitution.** An attacker replaces `identity` with +`did:web:attacker.example` and signs a malicious manifest with their own key. +The attacker cannot retain the original `urn:air:example.com:...` identifier: +the `did:web` profile requires `did:web:example.com`, and the signed subject +binds the complete identifier. The attacker can publish a separately named +artifact under `urn:air:attacker.example:...`, but cannot impersonate the +original publisher namespace. *Closed for standard `urn:air` identifiers by +F3.* -**SC-4 — alg:none forgery.** Attacker sets the JWS header `alg` to -`none` (or `HS256` keyed with the publisher's public key) and forges a -manifest with no private key. *Closed by F4 mitigation: algorithm -allowlist.* +**SC-4 — alg:none forgery.** An attacker sets the JWS header `alg` to `none` +(or `HS256` keyed with the publisher's public key) and forges a manifest with +no private key. *Closed by F4: the interoperable profile accepts only ES256 and +requires a matching P-256 public key from the DID document.* **SC-5 — Downgrade.** A patched v2.1 exists, but the attacker re-serves -the still-validly-signed v2.0 manifest + artifact. *Closed by F5 -mitigation: `issuedAt`/`expiresAt` + anti-rollback guidance.* +the still-validly-signed v2.0 manifest and artifact. `issuedAt` and an optional +`expiresAt` can inform consumer policy, but they do not prove that v2.0 is the +publisher's current release. Preventing rollback requires a trusted source of +current release information or consumer state recording a newer accepted +release. *Residual risk described by F5.* **SC-6 — Verification SSRF.** A manifest sets `attestation.uri = http://169.254.169.254/latest/meta-data/…`; the @@ -225,20 +237,29 @@ consumer's verifier fetches it and exfiltrates cloud credentials. entry to a catalog full of legitimately signed entries. *Mitigated by F6: catalog-level signature and/or OCI content-addressing.* +**SC-8 — Version relabeling.** A publisher releases v2.0 and signs its claims +and artifact digest. An attacker leaves the signed manifest and exact artifact +bytes unchanged but changes `entry.version` to v9.0. Because the entry version +is authoritative for catalog-level sorting and selection, the old release can +be selected as latest while every existing per-entry verification check +passes. *Closed by F11: the signed subject MUST commit to the entry version +when one is present.* + ## 6. Controls: Existing vs. Proposed | Finding | Threats | Existing control | Proposed normative mitigation | Spec section | |---------|---------|------------------|-------------------------------|--------------| -| F1 | T1 | Detached JWS over manifest; OPTIONAL `sourceDigest` | Signed `subject` `{url?, mediaType, digest}` committing to the served artifact; REQUIRED whenever signed | Trust Manifest → Subject Binding | +| F1 | T1 | Detached JWS over manifest; OPTIONAL `sourceDigest` | Signed `subject` committing to the served artifact type and digest; REQUIRED whenever signed | Trust Manifest → Subject Binding | | F2 | T2 | Level 3 requires manifest presence | Level 3 MUST carry a signed manifest with subject binding + `issuedAt` | Conformance Level 3 | -| F3 | S1, S3 | Key resolution from `identity` | Trust Anchoring subsection: verified signature ≠ trusted publisher; anchor identity to an out-of-band root | Verification → Trust Anchoring | -| F4 | E1 | "detached JWS" | Algorithm allowlist; reject `alg:none` + symmetric; validate `alg`, pin `kid` | Verification → Signature Algorithms | -| F5 | R1, E2 | none | `issuedAt` (REQUIRED when signed) + `expiresAt`; anti-rollback + revocation guidance | Trust Manifest + Verification | -| F6 | T3 | OCI Layer 3 (informative) | OPTIONAL catalog-level `signature` (RECOMMENDED at L3) + OCI reference | Catalog signature + Security Considerations | +| F3 | S1 | Unconstrained key resolution from `identity` | Require a signed `urn:air` identifier, the exactly corresponding root `did:web` issuer, and an assertion-authorized key resolved through the publisher domain | Verification → `did:web` Publisher Profile | +| F4 | E1 | "detached JWS" | Require ES256 with a P-256 `publicKeyJwk`; require protected `alg` and `kid`; prohibit attacker-selected key-source headers | Verification → `did:web` Publisher Profile | +| F5 | R1, E2 | `issuedAt` is REQUIRED when signed; `expiresAt` is OPTIONAL; the `did:web` profile checks current key authorization | No complete base mitigation: trusted current-release information or consumer state is required to prevent rollback | Trust Manifest + Verification | +| F6 | T3 | OCI Layer 3 (informative) | Define the catalog-signature input but require a separate signer-authorization profile; recommend content-addressed distribution or a fully profiled catalog signature | Catalog signature + Security Considerations | | F7 | I1, I2, D1 | none | Safe-Fetching subsection: size caps, timeouts, no redirects to private ranges, host allowlist | Verification → Safe Fetching | -| F8 | R2 | Fields only | Provenance-statement verification procedure | Verification → Provenance statements | -| F9 | S2 | `identity == identifier` | Bind/flag publisher fields; advisory unless covered by attestation | Verification → Publisher identity | +| F8 | R2 | Fields only | Delegate signer authorization and signature verification to the provenance statement's format; do not reuse the Entry Trust Manifest profile implicitly | Verification → Provenance statements | +| F9 | S2 | Publisher fields outside the signed Trust Manifest | Treat publisher metadata as advisory unless a separately verified mechanism binds it to the authenticated issuer | Verification → Publisher metadata and attestations | | F10 | — | JCS | Note JCS numeric round-trip caveat for signed payloads | Verification → Signatures | +| F11 | T4 | Signed subject contains representation type, digest, and optional URL only | Require signed `subject.identifier` and conditionally require `subject.version`, with exact entry comparisons | Trust Manifest → Subject Binding | ## 7. Comparison with the Sigstore Architecture @@ -269,23 +290,24 @@ proven to come from its expected source and to be untampered. | Sigstore property | Provides | Trust Manifest today | Gap (finding) | |-------------------|----------|----------------------|---------------| | Cosign signs the artifact **digest** | Artifact ↔ signature binding | `subject.digest` in the signed payload (this revision) | Closed (F1) | -| **Fulcio** CA binds identity via OIDC; short-lived cert | Identity is CA-attested, not self-asserted | `identity` is self-declared; key resolved *from* it; anchoring pushed to the consumer | Partially open (F3) — anchoring is required but no CA is specified | +| **Fulcio** CA binds identity via OIDC; short-lived cert | Identity is CA-attested, not self-asserted | A signed `urn:air` publisher domain authorizes the matching root `did:web` issuer; HTTPS and the DID document authenticate domain control | Different assurance — domain control rather than CA-attested OIDC identity | | **Rekor** transparency log | Non-repudiation, freshness, monitoring, rollback detection | No transparency-log equivalent; `issuedAt`/`expiresAt` give weak local freshness only | Open (F5, R1, E2) | | **Keyless / ephemeral keys** | No long-lived key management or revocation problem | Long-lived publisher keys (DID/JWKS); inherits key-management + revocation burden | Open (residual AS2) | -| **TUF** root of trust | Secure distribution + rotation of verification keys | Trust-anchor bootstrapping unspecified | Open (residual) | -| Verify expected identity + cert chain + Rekor inclusion | Full verification chain | Verify signature + `subject` + out-of-band anchor; no inclusion proof | Partially open | +| **TUF** root of trust | Secure distribution + rotation of verification keys | The `did:web` profile inherits DNS and Web PKI roots; it defines no application-specific root distribution | Delegated infrastructure | +| Verify expected identity + cert chain + Rekor inclusion | Full verification chain | Verify the signed namespace, matching `did:web` issuer, current assertion key, signature, and `subject`; no inclusion proof | Partially open | ### 7.2 Implications - **What the Trust Manifest now matches.** With `subject` binding, the - Trust Manifest reproduces Cosign's core property: a signature commits - to a specific artifact digest. This is the single most important - Sigstore guarantee and directly closes the substitution attack (T1). -- **What it delegates.** The Trust Manifest deliberately does not - operate a CA or a transparency log. It is a *format that can carry* - Sigstore-style evidence rather than a replacement for Sigstore - services. Consequently it leans on out-of-band **trust anchoring** - (section 4.1 / F3) to substitute for Fulcio's CA-attested identity. + Trust Manifest reproduces Cosign's core property that a signature commits + to a specific artifact digest and additionally binds the signer's claims to + the logical artifact release. This directly closes the representation + substitution and release-coordinate relabeling attacks (T1, T4). +- **What it delegates.** The Trust Manifest deliberately does not operate a CA + or a transparency log. The `did:web` profile delegates domain authentication + to DNS and the Web PKI, then uses the DID document to authorize the current + assertion key. Consumer policy still determines whether the authenticated + publisher and its claims are trusted for a particular use. - **What is still weaker than Sigstore.** Without a Rekor-equivalent, the Trust Manifest cannot offer public auditability, third-party witnessing, or strong rollback detection; `issuedAt`/`expiresAt` are a @@ -303,9 +325,9 @@ evidence first-class rather than reinventing it: signature + Rekor inclusion proof). Verifying it gives CA-attested identity and log inclusion "for free," and the `subject.digest` already aligns with what Cosign signs. -2. **Anchor via the Sigstore/TUF root.** Permit the trust anchor - (section 4.1) to be Sigstore's TUF-managed root, so identity is - verified against Fulcio rather than a hand-maintained allowlist. +2. **Define a separate Sigstore profile.** A future profile could use + Sigstore's TUF-managed root and Fulcio identity instead of the base + `did:web` domain-control profile. 3. **Prefer transparency-log inclusion over local freshness.** Where a Rekor (or compatible) inclusion proof is available, consumers SHOULD prefer it to `issuedAt`/`expiresAt` for freshness, non-repudiation, @@ -319,11 +341,11 @@ while letting trust-sensitive deployments inherit Sigstore's full chain ## 8. Residual Risks -- **Trust-anchor bootstrapping.** Anchoring shifts trust to an - out-of-band root (pinned allowlist, registry vetting, DID method with - domain control). The strength of the whole system reduces to how that - root is established and maintained; the spec can require anchoring but - cannot supply the root. +- **Domain and policy roots.** The `did:web` profile inherits the consumer's DNS + and Web PKI trust roots. It authenticates control of a publisher namespace; + it does not decide whether that publisher is reputable or authorized by a + particular organization. Allowlisting, registry vetting, and other + application trust decisions remain consumer policy. - **Publisher-key compromise (AS2).** A subject-bound, signed manifest is only as trustworthy as the publisher's key hygiene. Short-lived keys, revocation checking, and OCI/Cosign counter-signatures reduce