ca2a-runtime 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ca2a_runtime-0.4.0/.clusterfuzzlite/Dockerfile +5 -0
- ca2a_runtime-0.4.0/.clusterfuzzlite/README.md +60 -0
- ca2a_runtime-0.4.0/.clusterfuzzlite/build.sh +29 -0
- ca2a_runtime-0.4.0/.clusterfuzzlite/fuzz_attestation_parsers.py +62 -0
- ca2a_runtime-0.4.0/.clusterfuzzlite/fuzz_canonical_json.py +89 -0
- ca2a_runtime-0.4.0/.clusterfuzzlite/fuzz_wire_documents.py +92 -0
- ca2a_runtime-0.4.0/.github/dependabot.yml +39 -0
- ca2a_runtime-0.4.0/.github/workflows/cflite_batch.yml +36 -0
- ca2a_runtime-0.4.0/.github/workflows/cflite_pr.yml +43 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/codeql.yml +3 -3
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/docker.yml +2 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/require-maintainer-approval.yml +5 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/scorecard.yml +1 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CHANGELOG.md +46 -1
- ca2a_runtime-0.4.0/Dockerfile +40 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/PKG-INFO +3 -3
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/attestation.md +8 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/mutual-attestation.md +40 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/transport.md +7 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/index.md +2 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/pyproject.toml +3 -3
- ca2a_runtime-0.4.0/requirements/build.in +6 -0
- ca2a_runtime-0.4.0/requirements/build.txt +26 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/dev.in +2 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/dev.txt +56 -56
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/docs.txt +37 -18
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/release.txt +3 -3
- ca2a_runtime-0.4.0/requirements/runtime.in +12 -0
- ca2a_runtime-0.4.0/requirements/runtime.txt +620 -0
- ca2a_runtime-0.4.0/src/ca2a_runtime/attestation.py +383 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/canonical.py +9 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/challenge.py +10 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/credential.py +7 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/hardware_acceptance.py +7 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/node.py +9 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/peer.py +65 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/base.py +15 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/binding.py +46 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/sev_snp.py +14 -5
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/software.py +5 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/tdx.py +36 -11
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/tpm.py +10 -7
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/a2a_adapter.py +17 -2
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/client.py +34 -7
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/constants.py +6 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/server.py +6 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/wire.py +13 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/dag.py +4 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/sev_snp.py +2 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/tpm.py +5 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/conftest.py +58 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_a2a_sdk_bridge.py +6 -11
- ca2a_runtime-0.4.0/tests/unit/test_caller_offer_relay.py +449 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_canonical.py +13 -0
- {ca2a_runtime-0.3.0/tests → ca2a_runtime-0.4.0/tests/unit}/test_challenge.py +18 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_container_release.py +25 -3
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_example_rejection_with_proof.py +17 -8
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_holder_binding.py +16 -11
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_live_call.py +32 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_mutual_attestation.py +19 -15
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_release_artifacts.py +1 -1
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_tdx.py +62 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_trace_binding.py +11 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_verify.py +13 -0
- ca2a_runtime-0.3.0/.github/dependabot.yml +0 -23
- ca2a_runtime-0.3.0/Dockerfile +0 -30
- ca2a_runtime-0.3.0/src/ca2a_runtime/attestation.py +0 -163
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.dockerignore +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/CODEOWNERS +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/actionlint.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/ci.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/contributor-check.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/docs.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/limitations-parity.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/release-drafter.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/release.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/sbom.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/vouch-check.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/vouch-command.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.gitignore +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/ADOPTERS.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CHARTER.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CNAME +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CONTRIBUTING.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/GOVERNANCE.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/LICENSE +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/LIMITATIONS.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/MAINTAINERS.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/NOTICE +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/PRIVACY.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/ROADMAP.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/SECURITY.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/SPONSORS.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/benchmarks/.gitkeep +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/SPEC.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/assets/icon.svg +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/assets/og.png +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/concepts.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/configuration.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/hardware-validation.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/mutual-hardware-acceptance.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/quickstart.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/call-graph.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/cedar-policy.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/component-model.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/conformance.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/delegation-chain.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/error-codes.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/failure-modes.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/profile.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/provenance-dag.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/response-authentication.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/response-binding-requirements.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/sealed-channel.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/threat-model.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/trace-a2a-profile.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/verification-library.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/authoring-a-delegation-credential.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/emit-and-verify-provenance.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/integrating-with-a2a.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/reproducing-the-claims.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/verify-a-delegation-chain.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/a2a-sdk/loopback.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/a2a-sdk/requirements.txt +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/ca2a-config.yaml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/demo.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/policy.cedar +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/minimal/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/minimal/ca2a-config.yaml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/minimal/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/demo.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/trace-dag/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/trace-dag/demo.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim1-attenuation-soundness/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim1-attenuation-soundness/run.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim2-cross-chain-replay/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim2-cross-chain-replay/run.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim3-scope-policy-intersection/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim3-scope-policy-intersection/run.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim4-sealed-payload-confidentiality/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim4-sealed-payload-confidentiality/run.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim5-provenance-dag-integrity/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim5-provenance-dag-integrity/run.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim6-cross-operator-attestation/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim6-cross-operator-attestation/run.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-attested-peer-azure-2026-07-27/bridge.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-attested-peer-azure-2026-07-27/transcript.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/snp_client.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/tdx_peer.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/transcript.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/a-to-b.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/b-to-a.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/controls.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/governance/ca2a-enforcement.yaml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/hooks/seo.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/mkdocs.yml +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/overrides/main.html +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/agt-overrides.txt +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/agt.in +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/agt.txt +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/release.in +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements-docs.txt +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/robots.txt +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/scripts/gen_action_fixtures.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/scripts/gen_agt_evidence.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/scripts/gen_example_chain.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/agent_manifest.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/benchmarks.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/bootstrap.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/cedar.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/channel/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/channel/sealed.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/cli.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/config.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/holder.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/revocation.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/errors.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/policy.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/provenance.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/response.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/tsm.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/trace_binding.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/_b64url.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/a2a_sdk.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/tdx.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/tpm_roots.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/verify.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/committed_blobs.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/conftest.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/test_action_fixture_bundles.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/test_profile_conformance.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conftest.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/a2a/send_message_ca2a.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-001-verified/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-001-verified/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-001-verified/expected.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-002-parent-hash-mismatch/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-002-parent-hash-mismatch/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-002-parent-hash-mismatch/expected.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-006-policy-denial/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-006-policy-denial/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-006-policy-denial/expected.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-010-scope-escalation/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-010-scope-escalation/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-010-scope-escalation/expected.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-012-credential-expired/chain.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-012-credential-expired/dag.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-012-credential-expired/expected.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/ak.pub +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/akcert_0x01C101D0.der +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/nonce.hex +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/pcrs.bin +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/quote.msg +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/quote.sig +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/response-mac-v1.json +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/sev_snp/amd_milan_cert_chain.pem +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tdx/intel_sgx_root_ca.pem +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/README.md +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/ak-chain.pem +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/attest.hex +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/signature.hex +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/trusted-root.pem +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/__init__.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_a2a_adapter.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_a2a_sdk_loopback.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_agent_manifest_cose.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_bootstrap.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_cedar.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_channel_and_tee.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim1_attenuation.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim2_replay.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim3_scope_policy.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim4_sealed.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim5_provenance.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim6_cross_operator.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_cli_start.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_committed_examples_verify.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_config.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_delegation.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_dependency_security_floors.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_docs_quickstart.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_documented_experiments.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_errors.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_hardware_acceptance.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_lineage_cli.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_peer.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_peer_request.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_provenance.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_response_authentication.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_revocation.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_sealed_channel.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_sev_snp.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_snp_tdx_attest.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_tpm.py +0 -0
- {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_tpm_attest.py +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Fuzzing
|
|
2
|
+
|
|
3
|
+
Coverage-guided fuzzing via [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/),
|
|
4
|
+
running Atheris against the input cA2A reads before it has decided to trust the
|
|
5
|
+
party that sent it. Mirrors the setup in agentrust-io/agent-manifest and
|
|
6
|
+
agentrust-io/cmcp.
|
|
7
|
+
|
|
8
|
+
## What is fuzzed
|
|
9
|
+
|
|
10
|
+
| Target | Surface |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `fuzz_wire_documents.py` | JSON from outside: the A2A request `PeerNode.handle` serves (delegation chain, holder proof, caller offer, sealed payload), the authenticated-response envelope, a revocation snapshot, and the chain and TRACE DAG documents the offline verifier loads. |
|
|
13
|
+
| `fuzz_attestation_parsers.py` | TDX quote, SEV-SNP report, TPM quote and TPMT_SIGNATURE parsing, plus `verify_challenge` over caller-supplied challenge text. |
|
|
14
|
+
| `fuzz_canonical_json.py` | The RFC 8785 bytes every credential, holder proof, revocation statement and response MAC is computed over. |
|
|
15
|
+
|
|
16
|
+
## The properties
|
|
17
|
+
|
|
18
|
+
The wire and parser targets assert that each surface fails closed: it returns,
|
|
19
|
+
or raises the error family it documents (`CA2AError`, and `AttestationFailed`
|
|
20
|
+
for evidence). A `TypeError`, `struct.error`, `AttributeError` or
|
|
21
|
+
`RecursionError` escaping means a caller written against the documented
|
|
22
|
+
exception will not catch it, and on the reference server the connection drops
|
|
23
|
+
with no response.
|
|
24
|
+
|
|
25
|
+
`fuzz_canonical_json.py` asserts a round trip: parsing the canonical output must
|
|
26
|
+
reproduce the input exactly, and integers outside +/-(2**53 - 1) must be
|
|
27
|
+
refused. cA2A's data model has no floats, so the comparison is exact.
|
|
28
|
+
|
|
29
|
+
## Standing when added
|
|
30
|
+
|
|
31
|
+
A local plain-Python run of each `TestOneInput` (Atheris does not install on the
|
|
32
|
+
Windows machine it was written on) found three escapes that are fixed in the
|
|
33
|
+
same change, with regression tests: `TdxQuote.parse` raising `struct.error` on a
|
|
34
|
+
quote whose signature section is shorter than its declared contents,
|
|
35
|
+
`verify_challenge` raising `TypeError` on a non-ASCII MAC, and
|
|
36
|
+
`DelegationCredential.from_dict` raising `TypeError` on a hop that is not an
|
|
37
|
+
object.
|
|
38
|
+
|
|
39
|
+
## Bundling gotcha
|
|
40
|
+
|
|
41
|
+
`compile_python_fuzzer` bundles each target with PyInstaller, which follows
|
|
42
|
+
static imports only and bundles code, not package data. `build.sh` passes
|
|
43
|
+
`--collect-submodules=email` for the lazy `email.mime` import in the
|
|
44
|
+
cryptography and pydantic stacks, and `--collect-data` for `agentrust_trace`,
|
|
45
|
+
whose TRACE schema `ca2a_verify.dag` loads at import time.
|
|
46
|
+
|
|
47
|
+
## Running locally
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
git clone https://github.com/google/clusterfuzzlite --depth 1 /tmp/clusterfuzzlite
|
|
51
|
+
python /tmp/clusterfuzzlite/infra/helper.py build_image --external $PWD
|
|
52
|
+
python /tmp/clusterfuzzlite/infra/helper.py build_fuzzers --external --sanitizer address $PWD
|
|
53
|
+
python /tmp/clusterfuzzlite/infra/helper.py run_fuzzer --external $PWD fuzz_wire_documents
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## In CI
|
|
57
|
+
|
|
58
|
+
`cflite_pr.yml` fuzzes code the pull request touched, for five minutes.
|
|
59
|
+
`cflite_batch.yml` runs every target for thirty minutes, nightly. Both are
|
|
60
|
+
read-only and time out at 45 minutes; the oss-fuzz base image build dominates.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash -eu
|
|
2
|
+
# Build the fuzz targets for ClusterFuzzLite.
|
|
3
|
+
#
|
|
4
|
+
# Installed rather than put on the path so the targets exercise the same import
|
|
5
|
+
# surface a consumer gets. Third-party dependencies come from the same
|
|
6
|
+
# hash-pinned locks the container image uses, and the package goes in with
|
|
7
|
+
# --no-deps and without build isolation, so nothing here resolves unpinned.
|
|
8
|
+
|
|
9
|
+
cd "$SRC/ca2a"
|
|
10
|
+
pip3 install --no-cache-dir --require-hashes -r requirements/build.txt
|
|
11
|
+
pip3 install --no-cache-dir --require-hashes -r requirements/runtime.txt
|
|
12
|
+
pip3 install --no-cache-dir --no-deps --no-build-isolation .
|
|
13
|
+
|
|
14
|
+
# compile_python_fuzzer bundles each target with PyInstaller, which follows
|
|
15
|
+
# static imports only and bundles code, not package data.
|
|
16
|
+
PYI_ARGS=(
|
|
17
|
+
# The cryptography and pydantic stacks reach email.mime lazily; without this
|
|
18
|
+
# the bundled target dies with ModuleNotFoundError, which libFuzzer reports
|
|
19
|
+
# as a crash in the target.
|
|
20
|
+
--collect-submodules=email
|
|
21
|
+
# ca2a_verify.dag validates records against the TRACE JSON schema, which
|
|
22
|
+
# agentrust_trace loads from inside its package at import time.
|
|
23
|
+
--collect-data=agentrust_trace
|
|
24
|
+
--collect-data=agent_manifest
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
for target in "$SRC"/ca2a/.clusterfuzzlite/fuzz_*.py; do
|
|
28
|
+
compile_python_fuzzer "$target" "${PYI_ARGS[@]}"
|
|
29
|
+
done
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""Fuzz the attestation evidence parsers.
|
|
3
|
+
|
|
4
|
+
A callee reads a caller's evidence (``raw_evidence`` in a ``caller_offer``)
|
|
5
|
+
before deciding to trust anything about it, and an auditor feeds recorded
|
|
6
|
+
quotes to ``ca2a_verify`` offline. These parsers read that binary at fixed
|
|
7
|
+
offsets under declared lengths the blob itself supplies: a TDX quote with four
|
|
8
|
+
nested size fields, an SNP report, a TPM quote and a TPMT_SIGNATURE.
|
|
9
|
+
|
|
10
|
+
The property is that each fails closed: it returns, or raises
|
|
11
|
+
AttestationFailed, the error the verifier documents. A struct.error, IndexError
|
|
12
|
+
or OverflowError reaching the caller means a declared length was believed. That
|
|
13
|
+
is the bug this target was written against: TdxQuote.parse used to raise
|
|
14
|
+
struct.error on a quote whose signature section was shorter than it claimed.
|
|
15
|
+
|
|
16
|
+
The challenge verifier is included because its input, the offer nonce and the
|
|
17
|
+
holder proof's challenge, is caller text read before anything authenticates it.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import atheris
|
|
23
|
+
|
|
24
|
+
with atheris.instrument_imports():
|
|
25
|
+
from ca2a_runtime.challenge import verify_challenge
|
|
26
|
+
from ca2a_runtime.errors import AttestationFailed
|
|
27
|
+
from ca2a_runtime.tee.sev_snp import SevSnpReport
|
|
28
|
+
from ca2a_runtime.tee.tdx import TdxQuote
|
|
29
|
+
from ca2a_runtime.tee.tpm import TpmQuote
|
|
30
|
+
from ca2a_verify.tpm import parse_tpmt_signature
|
|
31
|
+
|
|
32
|
+
_SECRET = b"\x00" * 32
|
|
33
|
+
|
|
34
|
+
_PARSERS = [
|
|
35
|
+
TdxQuote.parse,
|
|
36
|
+
SevSnpReport.parse,
|
|
37
|
+
TpmQuote.parse,
|
|
38
|
+
parse_tpmt_signature,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def TestOneInput(data: bytes) -> None:
|
|
43
|
+
if not data:
|
|
44
|
+
return
|
|
45
|
+
fdp = atheris.FuzzedDataProvider(data)
|
|
46
|
+
choice = fdp.ConsumeIntInRange(0, len(_PARSERS))
|
|
47
|
+
try:
|
|
48
|
+
if choice == len(_PARSERS):
|
|
49
|
+
verify_challenge(_SECRET, fdp.ConsumeUnicode(fdp.remaining_bytes()))
|
|
50
|
+
else:
|
|
51
|
+
_PARSERS[choice](fdp.ConsumeBytes(fdp.remaining_bytes()))
|
|
52
|
+
except AttestationFailed:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> None:
|
|
57
|
+
atheris.Setup(sys.argv, TestOneInput)
|
|
58
|
+
atheris.Fuzz()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
main()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""Fuzz the RFC 8785 canonicalizer for the invariant its signatures depend on.
|
|
3
|
+
|
|
4
|
+
canonicalize() produces the bytes every delegation credential, holder proof,
|
|
5
|
+
revocation statement and response MAC is computed over, so the bytes must be a
|
|
6
|
+
faithful, lossless encoding of the input. The property asserted is a round trip:
|
|
7
|
+
parsing the canonical output back must reproduce the input exactly.
|
|
8
|
+
|
|
9
|
+
A crash-only check would pass over the bugs that matter here. The ones found in
|
|
10
|
+
sibling canonicalizers were silent: two distinct keys serialized as one key
|
|
11
|
+
twice, a parser kept one of the pair, and a field vanished from a document whose
|
|
12
|
+
signature claimed to cover it.
|
|
13
|
+
|
|
14
|
+
Structure is built from the fuzz data rather than by mutating JSON text, so the
|
|
15
|
+
budget goes on key and value shapes (combining marks, surrogate pairs, control
|
|
16
|
+
characters, integers straddling 2**53) rather than on producing valid JSON.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import atheris
|
|
23
|
+
|
|
24
|
+
with atheris.instrument_imports():
|
|
25
|
+
from ca2a_runtime.canonical import canonicalize
|
|
26
|
+
|
|
27
|
+
_MAX_DEPTH = 4
|
|
28
|
+
_MAX_ITEMS = 6
|
|
29
|
+
_SAFE = 2**53 - 1
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _build(fdp, depth=0):
|
|
33
|
+
if depth >= _MAX_DEPTH or fdp.remaining_bytes() == 0:
|
|
34
|
+
return fdp.ConsumeUnicodeNoSurrogates(16)
|
|
35
|
+
kind = fdp.ConsumeIntInRange(0, 6)
|
|
36
|
+
if kind == 0:
|
|
37
|
+
return None
|
|
38
|
+
if kind == 1:
|
|
39
|
+
return fdp.ConsumeBool()
|
|
40
|
+
if kind == 2:
|
|
41
|
+
# Straddle the safe-integer boundary: outside it canonicalize() must
|
|
42
|
+
# refuse rather than emit digits a conforming verifier would not.
|
|
43
|
+
return fdp.ConsumeIntInRange(-(2**54), 2**54)
|
|
44
|
+
if kind == 3:
|
|
45
|
+
return fdp.ConsumeUnicodeNoSurrogates(64)
|
|
46
|
+
if kind == 4:
|
|
47
|
+
return [_build(fdp, depth + 1) for _ in range(fdp.ConsumeIntInRange(0, _MAX_ITEMS))]
|
|
48
|
+
return {
|
|
49
|
+
fdp.ConsumeUnicodeNoSurrogates(24): _build(fdp, depth + 1)
|
|
50
|
+
for _ in range(fdp.ConsumeIntInRange(0, _MAX_ITEMS))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _has_unsafe_int(value):
|
|
55
|
+
if isinstance(value, bool):
|
|
56
|
+
return False
|
|
57
|
+
if isinstance(value, int):
|
|
58
|
+
return not -_SAFE <= value <= _SAFE
|
|
59
|
+
if isinstance(value, dict):
|
|
60
|
+
return any(_has_unsafe_int(v) for v in value.values())
|
|
61
|
+
if isinstance(value, list):
|
|
62
|
+
return any(_has_unsafe_int(v) for v in value)
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def TestOneInput(data: bytes) -> None:
|
|
67
|
+
fdp = atheris.FuzzedDataProvider(data)
|
|
68
|
+
value = _build(fdp)
|
|
69
|
+
try:
|
|
70
|
+
out = canonicalize(value)
|
|
71
|
+
except ValueError:
|
|
72
|
+
# Declared: integers outside the RFC 8785 safe domain, and only those.
|
|
73
|
+
assert _has_unsafe_int(value), "canonicalize() refused an in-domain value"
|
|
74
|
+
return
|
|
75
|
+
assert not _has_unsafe_int(value), "an out-of-domain integer was serialized"
|
|
76
|
+
|
|
77
|
+
# cA2A's data model has no floats, so every number re-parses as the int it
|
|
78
|
+
# was and the round trip can be asserted exactly, bools included.
|
|
79
|
+
assert json.loads(out) == value, f"canonical bytes did not round-trip: {out!r}"
|
|
80
|
+
assert canonicalize(json.loads(out)) == out, "canonical form is not a fixed point"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main() -> None:
|
|
84
|
+
atheris.Setup(sys.argv, TestOneInput)
|
|
85
|
+
atheris.Fuzz()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
main()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""Fuzz the JSON documents cA2A accepts from outside.
|
|
3
|
+
|
|
4
|
+
Each surface takes arbitrary JSON from a party that has not been authenticated
|
|
5
|
+
yet, and each documents one error family for anything malformed:
|
|
6
|
+
|
|
7
|
+
- the A2A request a callee serves (``PeerNode.handle``), which parses the
|
|
8
|
+
delegation chain, holder proof, caller offer and sealed payload, then runs
|
|
9
|
+
the inbound pipeline up to the first refusal: CA2AError;
|
|
10
|
+
- the authenticated-response envelope (``response.decode``):
|
|
11
|
+
ResponseAuthenticationFailed, a CA2AError;
|
|
12
|
+
- a revocation snapshot (``RevocationSnapshot.from_dict``): InvalidRevocation;
|
|
13
|
+
- a delegation chain document and a TRACE DAG handed to the offline verifier:
|
|
14
|
+
CA2AError.
|
|
15
|
+
|
|
16
|
+
Anything else escaping (TypeError, AttributeError, KeyError, UnicodeError,
|
|
17
|
+
RecursionError) is a crash: on the live path it drops the connection with no
|
|
18
|
+
response, and offline it escapes the verifier's contract. The input is decoded
|
|
19
|
+
the way the reference server decodes a body, so what reaches the parsers is
|
|
20
|
+
exactly what the wire can deliver.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import contextlib
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
import atheris
|
|
28
|
+
|
|
29
|
+
with atheris.instrument_imports():
|
|
30
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
31
|
+
|
|
32
|
+
from ca2a_runtime import response
|
|
33
|
+
from ca2a_runtime.delegation import RevocationSnapshot
|
|
34
|
+
from ca2a_runtime.errors import CA2AError
|
|
35
|
+
from ca2a_runtime.node import PeerNode
|
|
36
|
+
from ca2a_runtime.policy import LocalPolicy
|
|
37
|
+
from ca2a_verify.dag import verify_trace_dag
|
|
38
|
+
from ca2a_verify.verify import _parse_chain, verify_delegation_chain
|
|
39
|
+
|
|
40
|
+
_ROOT = Ed25519PrivateKey.from_private_bytes(b"\x01" * 32).public_key().public_bytes_raw().hex()
|
|
41
|
+
_NODE = PeerNode(LocalPolicy.of(["read", "write"]), trusted_root_issuers={_ROOT})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _handle(doc):
|
|
45
|
+
if isinstance(doc, dict):
|
|
46
|
+
_NODE.handle(doc)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _revocations(doc):
|
|
50
|
+
RevocationSnapshot.from_dict(doc)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _chain(doc):
|
|
54
|
+
verify_delegation_chain(_parse_chain(doc), trusted_root_issuers={_ROOT})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _dag(doc):
|
|
58
|
+
if isinstance(doc, dict):
|
|
59
|
+
doc = doc.get("records", doc)
|
|
60
|
+
if isinstance(doc, list) and doc:
|
|
61
|
+
verify_trace_dag(doc, trusted_keys=[])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_JSON_TARGETS = [_handle, _revocations, _chain, _dag]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def TestOneInput(data: bytes) -> None:
|
|
68
|
+
if not data:
|
|
69
|
+
return
|
|
70
|
+
fdp = atheris.FuzzedDataProvider(data)
|
|
71
|
+
choice = fdp.ConsumeIntInRange(0, len(_JSON_TARGETS))
|
|
72
|
+
raw = fdp.ConsumeBytes(fdp.remaining_bytes())
|
|
73
|
+
if choice == len(_JSON_TARGETS):
|
|
74
|
+
with contextlib.suppress(CA2AError):
|
|
75
|
+
response.decode(raw)
|
|
76
|
+
return
|
|
77
|
+
try:
|
|
78
|
+
doc = json.loads(raw)
|
|
79
|
+
except (ValueError, RecursionError):
|
|
80
|
+
# The reference server answers 400 before any parser runs.
|
|
81
|
+
return
|
|
82
|
+
with contextlib.suppress(CA2AError):
|
|
83
|
+
_JSON_TARGETS[choice](doc)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main() -> None:
|
|
87
|
+
atheris.Setup(sys.argv, TestOneInput)
|
|
88
|
+
atheris.Fuzz()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
main()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
day: "monday"
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
ignore:
|
|
10
|
+
- dependency-name: "*"
|
|
11
|
+
update-types:
|
|
12
|
+
- "version-update:semver-major"
|
|
13
|
+
|
|
14
|
+
- package-ecosystem: "github-actions"
|
|
15
|
+
directory: "/"
|
|
16
|
+
schedule:
|
|
17
|
+
interval: "weekly"
|
|
18
|
+
day: "monday"
|
|
19
|
+
open-pull-requests-limit: 5
|
|
20
|
+
groups:
|
|
21
|
+
codeql-action:
|
|
22
|
+
patterns:
|
|
23
|
+
- "github/codeql-action*"
|
|
24
|
+
|
|
25
|
+
# Refreshes the digest the Dockerfile pins its base image to. Without this the
|
|
26
|
+
# pin would hold the image at whatever it was on the day it was written.
|
|
27
|
+
- package-ecosystem: "docker"
|
|
28
|
+
directory: "/"
|
|
29
|
+
schedule:
|
|
30
|
+
interval: "weekly"
|
|
31
|
+
day: "monday"
|
|
32
|
+
open-pull-requests-limit: 5
|
|
33
|
+
# Digest refreshes and 3.11 patch releases only. Moving the image to a new
|
|
34
|
+
# Python minor is a deliberate change with its own PR and test run.
|
|
35
|
+
ignore:
|
|
36
|
+
- dependency-name: "python"
|
|
37
|
+
update-types:
|
|
38
|
+
- "version-update:semver-major"
|
|
39
|
+
- "version-update:semver-minor"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: ClusterFuzzLite batch
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
# 04:40 UTC daily, off the hour and off agent-manifest's 04:20 slot.
|
|
6
|
+
- cron: '40 4 * * *'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions: read-all
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: cflite-batch
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
fuzz:
|
|
17
|
+
name: Batch fuzz
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 45
|
|
20
|
+
steps:
|
|
21
|
+
- name: Build fuzzers
|
|
22
|
+
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
|
|
23
|
+
with:
|
|
24
|
+
language: python
|
|
25
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
26
|
+
sanitizer: address
|
|
27
|
+
|
|
28
|
+
- name: Run fuzzers
|
|
29
|
+
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
|
|
30
|
+
with:
|
|
31
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
32
|
+
# Every target, not just what changed. Thirty minutes of fuzzing
|
|
33
|
+
# leaves room for the base image build inside the 45 minute timeout.
|
|
34
|
+
fuzz-seconds: 1800
|
|
35
|
+
mode: batch
|
|
36
|
+
sanitizer: address
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: ClusterFuzzLite PR
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- 'src/**'
|
|
7
|
+
- 'requirements/build.txt'
|
|
8
|
+
- 'requirements/runtime.txt'
|
|
9
|
+
- '.clusterfuzzlite/**'
|
|
10
|
+
- '.github/workflows/cflite_pr.yml'
|
|
11
|
+
|
|
12
|
+
# Read-only. Findings surface in the job log and the uploaded crash artifact
|
|
13
|
+
# rather than as code-scanning alerts, so no security-events: write is needed.
|
|
14
|
+
permissions: read-all
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: cflite-pr-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
fuzz:
|
|
22
|
+
name: Fuzz changed code
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
timeout-minutes: 45
|
|
25
|
+
steps:
|
|
26
|
+
# Only the address sanitizer. The targets are pure Python under Atheris,
|
|
27
|
+
# where undefined-behaviour instrumentation has nothing to instrument.
|
|
28
|
+
- name: Build fuzzers
|
|
29
|
+
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
|
|
30
|
+
with:
|
|
31
|
+
language: python
|
|
32
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
sanitizer: address
|
|
34
|
+
|
|
35
|
+
- name: Run fuzzers
|
|
36
|
+
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
|
|
37
|
+
with:
|
|
38
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
+
fuzz-seconds: 300
|
|
40
|
+
# code-change fuzzes only what the PR touched, which is what keeps
|
|
41
|
+
# this inside a PR's time budget. The nightly batch covers the rest.
|
|
42
|
+
mode: code-change
|
|
43
|
+
sanitizer: address
|
|
@@ -28,16 +28,16 @@ jobs:
|
|
|
28
28
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
29
29
|
|
|
30
30
|
- name: Initialize CodeQL
|
|
31
|
-
uses: github/codeql-action/init@
|
|
31
|
+
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
32
32
|
with:
|
|
33
33
|
languages: python
|
|
34
34
|
queries: +security-extended
|
|
35
35
|
|
|
36
36
|
- name: Autobuild
|
|
37
|
-
uses: github/codeql-action/autobuild@
|
|
37
|
+
uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
38
38
|
|
|
39
39
|
- name: Perform CodeQL Analysis
|
|
40
|
-
uses: github/codeql-action/analyze@
|
|
40
|
+
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
41
41
|
continue-on-error: true
|
|
42
42
|
with:
|
|
43
43
|
category: /language:python
|
|
@@ -13,12 +13,15 @@ on:
|
|
|
13
13
|
|
|
14
14
|
permissions:
|
|
15
15
|
contents: read
|
|
16
|
-
pull-requests: read
|
|
17
|
-
statuses: write
|
|
18
16
|
|
|
19
17
|
jobs:
|
|
20
18
|
gate:
|
|
21
19
|
runs-on: ubuntu-latest
|
|
20
|
+
# Reads the PR and its reviews, then fails the job if no maintainer
|
|
21
|
+
# approved the head commit. It writes nothing, so no write scope.
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pull-requests: read
|
|
22
25
|
if: github.event.pull_request.base.ref == 'main'
|
|
23
26
|
steps:
|
|
24
27
|
- name: Check for maintainer approval
|
|
@@ -38,6 +38,6 @@ jobs:
|
|
|
38
38
|
publish_results: true
|
|
39
39
|
|
|
40
40
|
- name: Upload SARIF
|
|
41
|
-
uses: github/codeql-action/upload-sarif@
|
|
41
|
+
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
42
42
|
with:
|
|
43
43
|
sarif_file: scorecard-results.sarif
|
|
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-09-30
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- A delegate with no enclave could be recorded as `caller_attestation="hardware"`
|
|
15
|
+
by relaying an honest hardware peer's attestation: it asked that peer's public
|
|
16
|
+
`/.well-known/ca2a/channel` endpoint to attest under the target's challenge and
|
|
17
|
+
presented the result as its `caller_offer`. Caller offers are now minted in a
|
|
18
|
+
separate role, signed under their own binding prefixes (`ca2a-*-caller-v2|`),
|
|
19
|
+
and the handshake endpoint only mints callee-role offers. The caller must also
|
|
20
|
+
send `caller_possession`, a MAC over the request transcript keyed from X25519
|
|
21
|
+
between its attested channel key and the callee's, which the callee verifies
|
|
22
|
+
before recording any outcome.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Breaking for callers that send `caller_offer`** (`ca2a-caller-offer-v2`). A
|
|
27
|
+
pre-v2 caller offer has no `role` and no `caller_possession` and is refused with
|
|
28
|
+
`ATTESTATION_FAILED`. Callers that send no offer, and the handshake response,
|
|
29
|
+
are unchanged on the wire. `BaseProvider.attest` and the binding helpers
|
|
30
|
+
(`tpm_qualifying_data`, `snp_report_data`, `tdx_report_data`) take a keyword
|
|
31
|
+
`role`, defaulting to the callee role; a custom `Verifier` must derive the
|
|
32
|
+
binding under `report.role`.
|
|
33
|
+
|
|
34
|
+
## [0.3.1] - 2026-09-26
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Malformed input from outside now fails with the documented error instead of
|
|
39
|
+
escaping as a Python exception. `TdxQuote.parse` confines reads to the declared
|
|
40
|
+
signature section and checks each nested length (it raised `struct.error` on a
|
|
41
|
+
short section); `verify_challenge` refuses a MAC that is not 64 lowercase hex
|
|
42
|
+
(a non-ASCII one raised `TypeError`); `DelegationCredential.from_dict` and
|
|
43
|
+
`verify_trace_dag` refuse entries that are not objects; the reference server
|
|
44
|
+
answers 400 to bodies that hit the JSON parser's nesting or integer-digit
|
|
45
|
+
limits instead of dropping the connection (#202).
|
|
46
|
+
- `canonicalize` refuses non-string object keys. It coerced them with `str()`, so
|
|
47
|
+
`{1: "a", "1": "b"}` serialized one key twice (#202).
|
|
48
|
+
- The container image installs its dependencies from hash-pinned locks
|
|
49
|
+
(`requirements/runtime.txt`, `requirements/build.txt`) and pins the base image
|
|
50
|
+
by digest. ClusterFuzzLite targets cover the wire documents, the attestation
|
|
51
|
+
parsers and the canonicalizer (#202).
|
|
52
|
+
|
|
10
53
|
## [0.3.0] - 2026-09-25
|
|
11
54
|
|
|
12
55
|
### Changed
|
|
@@ -391,7 +434,9 @@ built/stubbed boundary.
|
|
|
391
434
|
- The sealed channel does not by itself establish the enclave-held-private-key property; that is a hardware attestation guarantee that lands with real-hardware validation.
|
|
392
435
|
- Alpha schemas: the delegation credential and TRACE link schemas are not yet stable or versioned, and peer attestation evidence is not yet RATS/EAT conformant.
|
|
393
436
|
|
|
394
|
-
[Unreleased]: https://github.com/agentrust-io/ca2a/compare/v0.
|
|
437
|
+
[Unreleased]: https://github.com/agentrust-io/ca2a/compare/v0.4.0...main
|
|
438
|
+
[0.4.0]: https://github.com/agentrust-io/ca2a/compare/v0.3.1...v0.4.0
|
|
439
|
+
[0.3.1]: https://github.com/agentrust-io/ca2a/compare/v0.3.0...v0.3.1
|
|
395
440
|
[0.3.0]: https://github.com/agentrust-io/ca2a/compare/v0.2.0...v0.3.0
|
|
396
441
|
[0.2.0]: https://github.com/agentrust-io/ca2a/compare/v0.1.0a1...v0.2.0
|
|
397
442
|
[0.1.0a1]: https://github.com/agentrust-io/ca2a/releases/tag/v0.1.0a1
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Base image pinned by its multi-arch index digest, so a re-pushed tag cannot
|
|
2
|
+
# change what is built. Refresh the digest and the tag together.
|
|
3
|
+
FROM python:3.11.16-slim-bookworm@sha256:a36c24f9cbdf4fd0f52d67f0823eeac19c2028c637cecc392d97f980d4fec56b AS builder
|
|
4
|
+
|
|
5
|
+
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
6
|
+
PIP_NO_CACHE_DIR=1
|
|
7
|
+
|
|
8
|
+
WORKDIR /build
|
|
9
|
+
COPY requirements/build.txt ./requirements/build.txt
|
|
10
|
+
# The build backend comes from a hash-pinned lock, and the wheel is built
|
|
11
|
+
# without build isolation so pip does not fetch an unpinned hatchling of its own.
|
|
12
|
+
RUN python -m pip install --require-hashes -r requirements/build.txt
|
|
13
|
+
COPY pyproject.toml README.md LICENSE NOTICE ./
|
|
14
|
+
COPY src ./src
|
|
15
|
+
RUN python -m pip wheel --no-deps --no-build-isolation --wheel-dir /wheels .
|
|
16
|
+
|
|
17
|
+
FROM python:3.11.16-slim-bookworm@sha256:a36c24f9cbdf4fd0f52d67f0823eeac19c2028c637cecc392d97f980d4fec56b AS runtime
|
|
18
|
+
|
|
19
|
+
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
20
|
+
PIP_NO_CACHE_DIR=1 \
|
|
21
|
+
PYTHONUNBUFFERED=1
|
|
22
|
+
|
|
23
|
+
RUN groupadd --gid 10001 ca2a \
|
|
24
|
+
&& useradd --uid 10001 --gid 10001 --no-create-home --home-dir /var/lib/ca2a ca2a \
|
|
25
|
+
&& install -d -o ca2a -g ca2a /var/lib/ca2a /etc/ca2a
|
|
26
|
+
|
|
27
|
+
# Third-party runtime dependencies from the hash-pinned lock, then the locally
|
|
28
|
+
# built wheel with --no-deps, so nothing in the image is resolved unpinned.
|
|
29
|
+
COPY requirements/runtime.txt /tmp/runtime.txt
|
|
30
|
+
COPY --from=builder /wheels /wheels
|
|
31
|
+
RUN python -m pip install --require-hashes -r /tmp/runtime.txt \
|
|
32
|
+
&& python -m pip install --no-deps /wheels/ca2a_runtime-*.whl \
|
|
33
|
+
&& rm -rf /wheels /tmp/runtime.txt
|
|
34
|
+
|
|
35
|
+
USER 10001:10001
|
|
36
|
+
WORKDIR /var/lib/ca2a
|
|
37
|
+
EXPOSE 8443
|
|
38
|
+
|
|
39
|
+
ENTRYPOINT ["ca2a"]
|
|
40
|
+
CMD ["--help"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ca2a-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Confidential agent-to-agent runtime: attested, attenuated delegation and sealed peer channels for A2A
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentrust-io/ca2a
|
|
6
6
|
Project-URL: Repository, https://github.com/agentrust-io/ca2a
|
|
@@ -22,7 +22,7 @@ Classifier: Topic :: Security
|
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
23
|
Requires-Dist: agent-manifest>=0.11.1
|
|
24
24
|
Requires-Dist: agentrust-trace>=0.5
|
|
25
|
-
Requires-Dist: cedarpy==4.
|
|
25
|
+
Requires-Dist: cedarpy==4.12.1
|
|
26
26
|
Requires-Dist: cryptography>=50.0
|
|
27
27
|
Requires-Dist: pyyaml>=6.0
|
|
28
28
|
Requires-Dist: rfc8785>=0.1
|
|
@@ -30,7 +30,7 @@ Provides-Extra: a2a-sdk
|
|
|
30
30
|
Requires-Dist: a2a-sdk<2,>=1.1; extra == 'a2a-sdk'
|
|
31
31
|
Requires-Dist: aiohttp>=3.14.3; extra == 'a2a-sdk'
|
|
32
32
|
Provides-Extra: dev
|
|
33
|
-
Requires-Dist: a2a-sdk[http-server]==1.1.
|
|
33
|
+
Requires-Dist: a2a-sdk[http-server]==1.1.5; extra == 'dev'
|
|
34
34
|
Requires-Dist: agentrust-trace-tests<0.6,>=0.4; extra == 'dev'
|
|
35
35
|
Requires-Dist: aiohttp>=3.14.3; extra == 'dev'
|
|
36
36
|
Requires-Dist: bandit[toml]>=1.7; extra == 'dev'
|
|
@@ -23,13 +23,20 @@ binding = sha256(prefix || len32(public_key) || public_key || len32(nonce) || no
|
|
|
23
23
|
| AMD SEV-SNP | `ca2a-snp-v1\|` | `REPORT_DATA` (64 bytes) | the binding, zero-padded on the right |
|
|
24
24
|
| Intel TDX | `ca2a-tdx-v1\|` | `REPORTDATA` (64 bytes) | the binding, zero-padded on the right |
|
|
25
25
|
|
|
26
|
+
Those are the **callee-role** prefixes: what the handshake endpoint signs. A caller
|
|
27
|
+
offer (see [mutual attestation](mutual-attestation.md)) is signed under its own
|
|
28
|
+
prefix per platform, `ca2a-tpm-caller-v2|`, `ca2a-snp-caller-v2|` and
|
|
29
|
+
`ca2a-tdx-caller-v2|`, selected by the report's `role`. A verifier MUST derive the
|
|
30
|
+
binding under the report's role, so an offer relabelled from one role to the other
|
|
31
|
+
recomputes different bytes and is rejected.
|
|
32
|
+
|
|
26
33
|
Committing the nonce alone would sign for freshness only, leaving `public_key` an unsigned assertion, so sealing a payload "to a key from a verified report" would not actually be rooted in hardware. A verifier re-derives this value from the report's own fields and requires equality, which is what promotes `public_key` and `nonce` from claim to signed fact. A report whose key was substituted after collection is rejected.
|
|
27
34
|
|
|
28
35
|
Three encoding details are load-bearing, and each prefix is versioned because this is wire format: a peer and its verifier MUST derive identical bytes.
|
|
29
36
|
|
|
30
37
|
- **Hashed, not raw.** `TPM2B_DATA` is capped below 64 bytes on some platforms (Azure returns `TPM_RC_SIZE`), and 32 bytes always fits. SEV-SNP and TDX reserve 64, so the digest is left-aligned and zero-padded, which is the convention the kernel's own callers and `agent-manifest` use; a report collected by either runtime is then byte-comparable.
|
|
31
38
|
- **Length-prefixed, not delimiter-joined.** With a delimiter a value containing it shifts the split without changing the digest, so `("a|b", "c")` and `("a", "b|c")` would commit identical bytes and a peer could bind a key other than the one it appears to offer. `nonce` is an arbitrary caller-supplied string, so that is reachable rather than theoretical.
|
|
32
|
-
- **Domain-separated per platform.** The
|
|
39
|
+
- **Domain-separated per platform and per role.** The prefixes differ so a report collected on one platform cannot be replayed as another's evidence, and so a callee offer, which anybody can obtain from the public handshake endpoint under a nonce of their choosing, cannot be presented as a caller offer.
|
|
33
40
|
|
|
34
41
|
`ca2a_runtime.tee.binding` holds the single derivation the three providers share.
|
|
35
42
|
|