fix(attestation)!: bind caller offers to their role and require proof of possession, release 0.4.0 - #221
Merged
Conversation
… of possession
A delegate with no enclave could be recorded as caller_attestation="hardware"
by relaying an honest hardware peer's attestation. It fetched challenge C from
target T, asked peer V's public /.well-known/ca2a/channel endpoint to attest
under C, and presented V's offer as its caller_offer. T appraised the report but
never asked the presenter to prove it held the attested channel key, and callee
and caller offers were signed over the same binding.
Role separation: every offer carries a role (attestation.role on the wire) that
selects the binding prefix the hardware signs. Callee offers keep the v1
prefixes, so handshakes and archived captures still verify; caller offers use
ca2a-{tpm,snp,tdx}-caller-v2|. PeerNode.offer, and so /channel, only mints
callee offers. A callee accepts only caller offers, a caller only callee offers.
Proof of possession: with every caller_offer the caller sends
caller_possession {"version": "ca2a-caller-offer-v2", "mac"}, an HMAC-SHA256
over the JCS transcript (challenge, both channel keys, credential_id, subject,
requested_capability, record_id, payload digest, parent_record_hash, holder
proof signature) keyed by HKDF-SHA256 over X25519 between the caller's attested
channel key and the callee's. The callee recomputes it and compares in constant
time before recording any outcome. Missing or invalid proof is refused at every
rung whenever an offer is present.
BREAKING CHANGE: callers that send caller_offer must upgrade. A pre-v2 offer has
no role and no caller_possession and is refused with ATTESTATION_FAILED naming
ca2a-caller-offer-v2. Callers that send no offer and handshake responses are
unchanged on the wire. BaseProvider.attest and the binding helpers take a
keyword role (default callee); custom verifiers must derive the binding under
report.role.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
imran-siddique
requested review from
a team,
carloshvp and
zohebk8s
as code owners
September 30, 2026 22:47
pip-audit flags CVE-2026-97687, CVE-2026-97688 and CVE-2026-97689 in urllib3 2.7.0. The 2.8.0 hashes are the ones requirements/docs.txt already pins and match PyPI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes GHSA-xxj2-p57x-x954.
A callee that sets
require_caller_attestation: hardwarecan be made to record a caller with no TEE ascaller_attestation="hardware".The callee appraises the caller's
caller_offer, an X25519 channel key plus a hardware report bound to the callee's challenge, but never asks the caller to prove it holds that channel key. The only link iscaller_channel_keyinside the holder proof, and the delegate signs that itself. Any ca2a node also attests its own channel key under whatever nonce reaches its public/.well-known/ca2a/channel?nonce=endpoint, and caller and callee offers used the same binding prefix.The attack:
/channel?nonce=C.caller_offer.T accepts, and the provenance record says
hardware. Replaying an offer seen on the wire inside the challenge TTL works the same way. Reproduced on 8113abd with a simulated hardware provider and a correct verifier:accepted; caller_attestation = hardware.Fix (0.4.0, breaking for callers that send
caller_offer):attestation.role, which selects the prefix the hardware signs. Caller offers useca2a-{tpm,snp,tdx}-caller-v2|; callee prefixes stay v1, so handshakes and archived captures still verify./channelonly mints callee offers, and a callee only accepts caller offers.caller_offerthe caller sendscaller_possession,{"version": "ca2a-caller-offer-v2", "mac"}: an HMAC-SHA256 over the challenge, both channel keys, the credential, capability, record, payload digest and holder proof signature, keyed by HKDF over X25519 between the attested caller key and the callee key. The callee recomputes it with its own channel key.A relayed or replayed offer now fails on role or on possession. Pre-v2 callers get a clear error naming
ca2a-caller-offer-v2. Callers that send no offer are unaffected.Affected: ca2a-runtime 0.2.0 to 0.3.1. CWE-294 (authentication bypass by capture-replay).
Also releases 0.4.0:
pyproject.toml, the release-artifact test,index.mdand the CHANGELOG section.Generated with Claude Code