Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add opt-in live response authentication through `require_authenticated_response`.
The session-MAC profile binds readable provenance or denial to the appraised
peer key, complete request and fresh recipient/session. Verification is expiring
and single-use; failed responses retain unknown execution outcomes without retry.
This is not portable signed evidence, response encryption or a new hardware result.

- Record acceptance-harness operation timing and unfinished work so transport
timeouts remain distinguishable from security rejections. The historical SNP
burst timeout remains unexplained; this change adds diagnostics without retries.

- **Delegation revocation.** Until now a delegated grant could not be withdrawn
inside its validity window (`docs/spec/threat-model.md`). The issuer of a
credential, or any issuer above it in the chain, can now sign a
Expand Down
11 changes: 11 additions & 0 deletions LIMITATIONS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Limitations

## Opt-in response authentication

`send_task(require_authenticated_response=True)` authenticates readable
provenance or denial to the live caller using a request-bound session MAC. The
default legacy path remains unauthenticated. The profile supplies one-use,
expiring client verification, not request-execution deduplication, portable
signed receipts or confidential output encryption. Both session parties know the
MAC key. Hardware assurance still requires explicit hardware appraisal; the new
profile has software/HTTP tests and no new live-hardware validation. See the
[profile and deployment limits](docs/spec/response-authentication.md).

cA2A 0.2 is a Developer Preview with a runnable, tested profile and runtime. This document states plainly what is built, what remains before 1.0, and what is out of scope, so no claim in the documentation runs ahead of the code. This is a deliberate discipline: proof, not promises.

## What is built
Expand Down
25 changes: 24 additions & 1 deletion docs/mutual-hardware-acceptance.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Policies forbidding SMT or requiring ciphertext hiding correctly rejected the
hosts. Measurement pins came from owner-SSH bootstrap reports, not a precomputed
application image. Initial burst runs timed out; paced runs exited 0. The
sanitized results are in
[the diagnostic record](../experiments/hardware-validation/mutual-snp-2026-09-17/README.md).
[the diagnostic record](https://github.com/agentrust-io/ca2a/blob/main/experiments/hardware-validation/mutual-snp-2026-09-17/README.md).
Raw reports and device-identifying certificates remain private. Both VMs and their
boot disks were deleted after collection. This is not end-to-end inference
confidentiality validation.
Expand Down Expand Up @@ -125,3 +125,26 @@ secure version. These fields are checked after signature, chain and binding
verification. They do not establish firmware TCB currency, revocation status or
safe migration policy. Other VMPL deployments require a separately justified
profile and are deliberately refused here.

## Diagnosing interrupted calls

The harness writes `operation_started`, `operation_completed` and
`operation_failed` observations with an operation ID and monotonic elapsed time.
Stages distinguish receiver offer generation, local quote collection, peer quote
verification, receiver task processing and the overall sender call. Nonces are
hashed for correlation; payloads, certificates and raw reports are not logged.

A start without a matching completion identifies unfinished observed work; it
does not identify why it stalled. A sender transport failure records the receiver
outcome as unknown and preserves the original exception. There are no automatic
retries: the receiver may have processed a task before its response was lost.
Collect both hosts' receipts and service/kernel logs before diagnosing a cause.
Operation IDs correlate events within a receipt, not an authenticated distributed
trace. These observations remain unsigned and cannot prove receiver inactivity.

The September 17 burst timeout has not been reproduced locally: 100 consecutive
software-provider calls over the reference HTTP server completed without pacing.
A controlled local test blocks software quote generation to verify that a real
HTTP timeout leaves useful unfinished-stage evidence. That test does not establish
that quote generation caused the historical SNP timeout. Hardware results have
not been rerun or changed by this diagnostic addition.
13 changes: 10 additions & 3 deletions docs/spec/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,16 @@ except CA2AError as exc:

Verification fails closed. `verify_chain`, `verify_dag`, and `cross_check_chain` raise the first error they find rather than returning a partial result, so a caught `CA2AError` means the chain or DAG was rejected.

## See also

- [Delegation Chain](delegation-chain.md) for the checks behind `ScopeEscalation`, `BrokenDelegationLink`, `DelegationDepthExceeded`, `CredentialReplay`, `CredentialNotYetValid`, `CredentialExpired`, `CredentialRevoked`, `RevocationStatusUnknown`, and `InvalidRevocation`.
## See also

The opt-in [response authentication profile](response-authentication.md) adds
`ResponseAuthenticationFailed` (`RESPONSE_AUTHENTICATION_FAILED`, HTTP 400).
At the caller it means no authenticated response was established and execution
may already have occurred. `response.AuthenticatedPeerError` instead carries a
MAC-authenticated denial's status/code and locally verified response metadata;
it does not by itself prove absence of side effects.

- [Delegation Chain](delegation-chain.md) for the checks behind `ScopeEscalation`, `BrokenDelegationLink`, `DelegationDepthExceeded`, `CredentialReplay`, `CredentialNotYetValid`, `CredentialExpired`, `CredentialRevoked`, `RevocationStatusUnknown`, and `InvalidRevocation`.
- [Provenance DAG](provenance-dag.md) for the checks behind `ProvenanceLinkBroken`.
- [Verification Library](verification-library.md) for `verify_chain`, `verify_chain_file`, `verify_dag`, and `cross_check_chain`.
- [Failure Modes](failure-modes.md) for how these errors map to observable runtime behavior.
6 changes: 6 additions & 0 deletions docs/spec/mutual-attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ that was wrong in this protocol.
If a genuinely confidential response is ever added, sealing *that* to the
caller's key is the right move. Encrypting the provenance record is not.

Authentication of the returned provenance is a separate extension. See the
[proposed response-binding requirements](response-binding-requirements.md)
for its acceptance matrix and the opt-in
[live response authentication profile](response-authentication.md) for the
implemented session-MAC path. This does not encrypt provenance or sign lineage.

## The state problem

A challenge is worth nothing unless it is single-use and expiring, and the
Expand Down
132 changes: 132 additions & 0 deletions docs/spec/response-authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Live response authentication

`ca2a-response-mac-v1` implements the live-caller branch of
[the response requirements](response-binding-requirements.md), tracked in
[#188](https://github.com/agentrust-io/ca2a/issues/188). This is an opt-in
reference-transport extension. It does not sign the execution lineage or create
third-party-verifiable receipts.

## Using the profile

Call `transport.client.send_task` with `require_authenticated_response=True`.
Configure `require_hardware=True` and an independently pinned verifier when a
hardware-appraised peer is required. These are separate controls: authenticated
responses to a software-only handshake still have `peer_assurance="none"`.

The caller uses the existing handshake and holder-bound request, then submits an
envelope to `POST /ca2a/task/authenticated`. The server validates its response
context before admitting the nested task. An old server or unsigned reply cannot
downgrade a strict caller to the legacy endpoint. The legacy endpoint and default
client behavior remain available, without authenticated-response assurance.

Successful strict calls return the ordinary provenance body plus locally added
`response_authentication` metadata containing `profile`, `peer_assurance`,
`request_sha256` and `audience="live-caller-only"`. Legacy calls strip this
reserved field so a peer cannot inject local verification metadata. Applications
must set the strict option themselves; a dict received from another source is
not a verified object merely because it carries the same field names.

Authenticated denials raise `response.AuthenticatedPeerError`, with the bound
HTTP status, peer error code and `response_authentication`. Unauthenticated,
malformed, expired or tampered replies raise `ResponseAuthenticationFailed`.
Transport failure after submission has an unknown execution outcome. Neither a
timeout nor a MAC failure proves non-execution. The client does not retry or
follow POST redirects and ignores ambient proxy settings for this submission.

## Wire and key derivation

The request envelope has exactly these fields:

| Field | Value |
|---|---|
| `profile` | `ca2a-response-mac-v1` |
| `peer` | Appraised responder X25519 public key, 32 bytes in lowercase hex. |
| `recipient` | Fresh caller X25519 public key for this occurrence, same encoding. |
| `session` | Fresh random 32-byte occurrence identifier, lowercase hex. |
| `request` | Complete cA2A A2A message, including delegation, holder proof, challenge, requested action and sealed payload where present. |

Both endpoints compute `request_sha256 = SHA256(JCS(envelope))`. The key and
session fields are therefore inside the commitment, alongside the request's
challenge and security-relevant contents. The server requires `peer` to match
its current channel key. Restart/rotation invalidates old contexts; a new call
must appraise the new key.

Use [X25519](https://www.rfc-editor.org/rfc/rfc7748) between the fresh caller key
and appraised responder key. Reject an invalid/all-zero shared secret. Derive
32 bytes with [HKDF-SHA256](https://www.rfc-editor.org/rfc/rfc5869): salt is the
raw request digest; info is ASCII `ca2a/response-mac/v1/key`. The request digest
commits both public keys. No Ed25519 key is inferred from the X25519 key.

The response statement has exactly `profile`, `request_sha256`, `status`,
`kind` and `body`. `kind` is `provenance` for status 200 and `denial` for
400 through 599. Other kinds, including confidential output, are unsupported.
`body` is an object: provenance has `accepted: true`; denial has an `error`
object. The reference server never echoes the opened task payload.

Append `mac`, the lowercase hex HMAC-SHA256 under the derived key over:

```text
ASCII("ca2a/response-mac/v1/statement") || 0x00 || JCS(statement)
```

The verifier checks the exact field set, profile, request digest, integer status,
actual HTTP status, MAC, kind and body shape before returning content. Merely
changing the outer HTTP status cannot turn an authenticated result into a denial.

This uses the repository's integer-only [RFC 8785 JCS](https://www.rfc-editor.org/rfc/rfc8785)
subset: strings, objects, arrays, booleans, null and safe integers. Floating
point, nonfinite values, duplicate object fields, non-string object keys and
invalid Unicode are refused. The authenticated request/response path is bounded
to 1 MiB and nesting depth 64; unknown envelope fields are refused. HTTP input
is byte-bounded before decoding. These limits do not provide admission rate limits.

## Replay, time and compatibility

`PendingResponse` is process-local state for one occurrence. It consumes its
verification opportunity atomically even on failure, and checks its monotonic
deadline before and after authentication. The default is 30 seconds, configurable
on the primitive within `(0, 300]`. Nonfinite or backward clock observations
fail closed. Simultaneous deliveries have at most one accepted response per
pending object. Copying or serializing pending state is rejected.

A fresh call generates a new recipient key and session, even if record IDs or
request content repeat. A response captured for an earlier call cannot satisfy
the new commitment. There is no pending-state persistence, multi-process recovery
or automatic resubmission after restart. Losing the pending object loses the
ability to authenticate its reply. Cloning a whole process/VM snapshot is outside
this software guarantee and requires rollback-resistant deployment controls.

This is response replay protection. The server's holder challenge remains
stateless and replayable within its documented window. Duplicating an incoming
request can still execute it again. Applications needing idempotent/exactly-once
effects must supply an authoritative request-execution store; neither record ID
reuse nor a response MAC establishes that property.

## Evidence and limits

The committed `tests/fixtures/response-mac-v1.json` contains public synthetic test
keys, canonical request bytes, a derived key, a valid response and negative
mutations. Its HKDF/MAC values were calculated independently using direct HMAC
extract/expand and ASCII JSON canonicalization. Unit tests also cross-check that
derivation independently of the production HKDF helper. These fixtures contain
no hardware evidence or production secrets.

Real-HTTP tests exercise successful provenance, authenticated denial, legacy
behavior, content/status substitution, cross-request replay, unsigned replies,
redirect refusal and reply loss after successful execution. Unit tests cover
concurrent delivery, expiry, malformed input, key rotation and unsupported output
kinds. Removing MAC verification, single-use enforcement or deadline checks makes
their regressions fail. This software result has not been rerun on live hardware.

Both session parties can compute the MAC. It authenticates a live reply against
network substitution for the caller that retains its secret; it cannot convince
an independent third party which party authored the statement. It does not fix
unsigned execution history in #168. The MAC also does not encrypt provenance,
whose metadata can be sensitive. HTTPS and access controls remain deployment
requirements where that metadata needs confidentiality.

Hardware assurance inherits the configured verifier and its approved measurement
and policy. No measured application identity, protected clock, rollback-resistant
memory, secure erasure, model execution or business completion follows from the
MAC itself. A future confidential-output profile needs explicit recipient-bound
encryption and disclosure rules; this profile does not add one.
Loading
Loading