a2aproject / a2aproject/A2A

Extension proposal: DANE-anchored identity for Agent Cards (DNSSEC TLSA binding for the signing key)

Open
#2,043 12 comments 0 reactions 0 assignees View on GitHub
Dominant language
Shell
Stars
25.7k
Forks
2.6k
Avg merge
3d 6h
Merged PRs (30d)
16

Description

## Abstract

This proposes an A2A extension that lets an Agent Card prove its signing key belongs to the agent's domain by anchoring that key in DNS, as a DNSSEC-signed DANE-EE TLSA record. Verification runs from the DNS root, with no central identity issuer and no prior key exchange. The agent keeps its own self-sovereign key. DANE only answers the question the current design leaves open: does this key provably belong to this domain.

I run [Whisper Security](https://whisper.online) and operate [AS219419](https://as219419.net), and we have this deployed in production DNS today. There is a live endpoint at the bottom of this issue that you can verify in about thirty seconds with `dig` alone. I am new to the A2A community, so please steer me if a fresh issue is not the right venue or if this should take a different shape; I would rather learn your process than assume it.

## Motivation

Section 8.4 lets an Agent Card carry a JWS. The protected header can include `kid` and, optionally, `jku`, a URL to a JWKS fetched over HTTPS. That establishes that the key came from some HTTPS endpoint, and that a WebPKI CA vouched for the TLS certificate on that endpoint. It does not establish that the key belongs to the domain named in the card. A verifier still has to trust whoever controls that endpoint, or whichever CA signed it. The spec is honest about this: it leans on channel security and a client-side trusted key store rather than a domain-level key binding.

The current debate splits into two camps and both hit the same wall. A centralized issuer (getagentid.dev is the clearest example) can vouch for an agent, but then everyone has to trust that issuer, and you have reintroduced a CA. A self-sovereign Ed25519 key (see #1829) is clean and needs no issuer, but a bare public key cannot tell you which domain it speaks for. Both are stuck on "who verifies the verifier."

There is a third anchor that this thread has mostly not considered: DNSSEC. It is already a global, delegated, root-anchored trust hierarchy, and every domain owner already sits inside it.

## Proposed mechanism

Publish the agent's signing key as a DANE-EE TLSA record under a DNSSEC-signed name:

- The record is usage 3, selector 1, matching type 1 (`3 1 1` in RFC 7218 mnemonics: DANE-EE / SPKI / SHA-256). It carries the SHA-256 of the key's SubjectPublicKeyInfo.
- It lives under a DNSSEC-signed name derived from the agent's endpoint, so a validating resolver returns it with the AD bit set.
- Verification is: DNSSEC-validate the TLSA, then confirm the Agent Card's JWS signing key has the SPKI hash the record commits to. Trust chains to the IANA root KSK, not to a WebPKI CA and not to an agent-identity issuer.

DANE base is RFC 6698, operational guidance in RFC 7671. Revocation is removing or replacing the TLSA record, and a live-DNS validator sees the change within the record's TTL. To be precise: that is DNS-timed revocation, not offline or stapled revocation, and I would not claim otherwise.

## How it fits the extension model

This is a data-only extension. The agent declares the extension URI in `capabilities.extensions[]` and carries the anchoring details in `metadata`. It changes no core types and adds no enum values, which keeps it inside the documented extension limitations. It composes with 8.4 rather than replacing it: the JWS stays, DANE just makes the signing key's domain binding checkable from the root. It has no dependency on a specific key algorithm, so it sits fine alongside the Ed25519 direction rather than competing with it.

## Verify the mechanics yourself

We anchor per endpoint (one DNSSEC-signed name and TLSA per address). A live example, sixty-second TTL, so re-verify before relying on it:

`dig +dnssec TLSA _443._tcp.ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online`

You should get a `3 1 1 ...` record returned with the AD flag set, an AAAA of `2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478`, and an active RDAP record for the address. No account, no private CA, no prior exchange.

## Honest state of it

Two things I will not overstate. First, I have written this up as an Internet-Draft, `draft-ranjbar-dane-anchored-identity`, which I am bringing to the IETF DANCE working group. It is not on the datatracker yet, so I am not linking it here; I will share it once it is public. Second, we keep key history in a tamper-evident, signed log, but the independent witness that would make that history externally checkable is still being landed, so I am describing it as tamper-evident and signed, nothing more.

## What I am asking

Interest from a maintainer in sponsoring this as an `experimental-ext`. I am happy to publish the specification under a stable identifier and provide a reference implementation, which I read as the graduation requirements. This also looks relevant to the credential-discovery work in #1990, and I would rather align with it than route around it.

Kaveh
[Whisper Security](https://whisper.security)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.