a2aproject / a2aproject/a2a-python

[Feat]: Add a runnable sample for Agent Card signing and verification

未关闭
#1,197 1 条评论 0 个 reaction 已指派 1 人 已被 @rohityan 认领 在 GitHub 查看
component: core
主要语言
Python
星标
2.1k
派生
496
平均合并
4 天 17 小时
30 天内合并 PR
12

描述

### Is your feature request related to a problem? Please describe.

The SDK ships Agent Card signing support in `a2a.utils.signing`
(`create_agent_card_signer` / `create_signature_verifier`), and both
`A2ACardResolver.get_agent_card` and `ClientFactory.create_from_url` accept a
`signature_verifier`. But `samples/` only contains `hello_world_agent.py` and
`cli.py`, so there is no end-to-end example showing how the two halves fit
together.

That leaves a few things easy to get wrong, and all of them fail in ways that
are hard to diagnose:

- **The verifier is a synchronous callable.** The natural implementation of a
`key_provider` — fetch the JWKS from the signature's `jku` with a sync HTTP
client — does blocking I/O inside async code. Against an in-process server it
deadlocks, and the symptom is a bare `InvalidSignaturesError: No valid
signature found`, which points at the crypto rather than at the I/O.
- **`create_agent_card_signer` mutates the card it signs** (it appends to
`card.signatures`). Wiring it directly into
`create_agent_card_routes(card_modifier=...)` means the served card
accumulates a new signature on every request.
- **`key_provider` failures need to be `PyJWTError` subclasses.** The verifier
only catches `PyJWTError` while iterating signatures, so a provider raising
`ValueError` aborts the whole card instead of moving on to the next
signature.
- **Trusting the card's own `jku` defeats the purpose.** The `jku` header lives
in the untrusted card, so a verifier that fetches keys from whatever URL the
card names will happily verify a forged card against the attacker's own key.
The key source has to be pinned out of band, and nothing in the API signature
makes that obvious.

### Describe the solution you'd like

A runnable sample under `samples/` covering both halves of card signing:

- **Server**: sign the Agent Card with an ES256 key via
`create_agent_card_signer`, serve it through
`create_agent_card_routes(card_modifier=...)`, and publish the matching
public key as a JWKS document at `/.well-known/jwks.json` — the URL the
signature's `jku` points to.
- **Client**: verify the fetched card with `create_signature_verifier` passed
to `A2ACardResolver.get_agent_card(signature_verifier=...)`, resolving keys
by `kid` from an allowlist of pinned JWKS URLs, with the accepted algorithms
pinned too.
- **Negative cases**, so the security property is visible rather than asserted:
a card whose transport URL was rewritten in transit, a card with its
signature stripped, and a genuine card whose `jku` the client does not trust.

Plus a smoke test in the style of `tests/integration/test_samples_smoke.py` and
a `samples/README.md` section.

### Describe alternatives you've considered

Documenting the flow in prose only (e.g. in the SDK docs). The unit tests in
`tests/utils/test_signing.py` already cover the crypto, but they use symmetric
keys and an inline `key_provider`, so they do not surface the async, mutation
or key-pinning pitfalls above — those only show up once a real server and
client are wired together.

### Additional context

_No response_

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。