fix(tpm): preserve signature algorithm metadata - #149
Conversation
Signed-off-by: Noah Ingwers <98993329+noah-ing@users.noreply.github.com>
|
🟡 Contributor Check: MEDIUM
Automated check by AgenTrust Contributor Check. |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Ran this against
The parsed-signature change in this PR resolves the issue for the report path; what remains is the legacy The residual I found is in the new compatibility wording rather than the report-path change.
I have a deterministic counterexample to that last part. A truthful
The legacy RSASSA/SHA-256 default is therefore never reached for this input. The signature itself verifies directly against the AK under PKCS#1 v1.5 / SHA-256. This case is already inside the documented RSASSA/SHA-256 profile, so it is separate from the PSS/SHA-384 behaviour this PR is adding. I also checked the tests. The existing bare-byte I have not tried to prescribe the resolution here; the point I wanted to surface is that the unconditional bare-signature compatibility statement has at least this counterexample. I have the frozen vector if it is useful as a regression: synthetic, offline-replayable, and containing no private key. Scope-wise, this is a fail-closed rejection of valid evidence, not a bypass or forged-evidence acceptance. |
Signed-off-by: Noah Ingwers <98993329+noah-ing@users.noreply.github.com>
|
Thank you — this is a real counterexample, and I reproduced the same dispatch
I pushed
That avoids a parse-failure fallback and removes the prefix heuristic from this Verification after the change:
Thanks for running the code and isolating the residual to the legacy form. The |
|
@noah-ing Re-ran my frozen vector against 1b2ec1e — the legacy bytes path now passes, and the upstream second parse count is zero. The cA2A boundary now passes a typed ParsedSignature(0x0014, 0x000b, ...) with the original signature bytes unchanged, so the prefix no longer determines structure. Thanks for reproducing it and for adding the regression fixture. |
carloshvp
left a comment
There was a problem hiding this comment.
Reviewed exact head 1b2ec1eb7d1da5b4ba201194d1952bcf409d16cf against the published dependency floor agent-manifest==0.11.1.
The report path retains ParsedSignature metadata, and the lower-level bare-byte path uses the AK key type rather than a signature-prefix heuristic. The frozen 00 16 RSA regression passes. An independent 36-case signed wire-level matrix covering RSASSA/RSAPSS, SHA-256/384/512, and relabelled scheme/digest combinations accepted only matching declarations.
Validation: unit/conformance suite 545 passed, 2 skipped. The skips are hardware-fixture tests; these results use synthetic signatures and do not claim new hardware validation. No blocking findings. Substantive CI is green; the remaining failure is the maintainer-approval gate.
What
Preserve the parsed
TPMT_SIGNATUREscheme and digest when cA2A delegates TPMreport verification to Agent Manifest. The report path now passes
ParsedSignatureinstead of stripping it to bare signature bytes.The lower-level compatibility API now distinguishes its two accepted forms
explicitly:
ParsedSignaturecarries envelope metadata, whilebytesmeans thehistorical bare-signature form. Bare bytes are tagged as ECDSA/SHA-256 or
RSASSA/SHA-256 according to the AK public-key type before delegation, rather
than being classified from their arbitrary first two bytes.
The regression coverage exercises both direct APIs and the complete
serialize_channel_offer -> parse_channel_offer -> verify_offer -> tpm_verifierpath.
Why
cA2A already parsed
sig_algandhash_alg, butverify_tpm_report()passedonly
parsed.signatureonward. Agent Manifest therefore applied its legacybare-signature defaults instead of the operation declared by the TPM envelope.
For RSA reports, that produced the following inconsistent outcomes on current
main:
The signature still had to be cryptographically valid under the trusted AK, so
this was not arbitrary-signature acceptance. It was algorithm confusion: the
reported scheme/digest and the operation actually verified could disagree.
Review also exposed a separate ambiguity in the legacy lower-level
bytesform. A valid 256-byte RSASSA/SHA-256 signature can begin
00 16; treatingthose arbitrary bytes as a possible marshalled envelope mistakes
0x0016forTPM_ALG_RSAPSSand rejects valid evidence while parsing. The frozen syntheticregression fixture contains such a genuine signature and no private key. cA2A
now makes its documented bare-byte interpretation explicit before delegation.
Agent Manifest 0.11.1 already supports
ParsedSignature, and cA2A's currentdependency floor is already
agent-manifest>=0.11.1, so no dependency changeis needed. This is a follow-up on the TPM signature-parser consolidation noted
in #77; it does not close #77's separate Azure certificate-chain limitation.
Security impact
This changes a security-critical offline attestation path. The signature scheme
and digest declared in
TPMT_SIGNATUREnow select the verification operation,so relabelling either field without producing a matching signature fails.
The algorithm fields are not themselves inside the AK-signed
TPMS_ATTESTbytes. This change establishes consistency between those envelope declarations
and the cryptographic operation; it does not add an algorithm-strength policy.
It does not alter AK-chain trust, quote type/magic checks, PCR appraisal,
qualifying-data binding, key residency, or nonce policy.
The bare-byte prefix collision was a fail-closed rejection of valid evidence,
not a bypass or forged-evidence acceptance. A caller holding a marshalled
TPMT_SIGNATUREmust parse it first or use the report API;bytesat the cA2Alower-level boundary now unambiguously means a legacy bare signature.
The new non-default algorithm vectors are self-consistent synthetic RSA/AK
fixtures. The previously documented live Azure vTPM observation remains
RSASSA/SHA-256; this PR does not claim a live hardware RSAPSS/SHA-384 run.
Test plan
pytest tests/unit/ tests/conformance/ -v --tb=short --cov=src— 545passed, 2 pre-existing hardware-fixture skips, 90.24% coverage
agent-manifest==0.11.1) — 48 focusedTPM tests passed, including the frozen
00 16bare-RSA vectorruff check src/ tests/ruff format --check src/ tests/mypy src/ca2a_runtime/ src/ca2a_verify/— 44 source filesbandit -r src/ -c pyproject.tomlpip-audit— no known dependency vulnerabilities; local unpublishedproject skipped as expected
fresh Python 3.12 wheel and sdist install/import/CLI smokes
not claimed: the existing project-wide config emits unrelated
SPONSORS.md/LICENSEwarnings.synthetic-vs-live boundary is documented above
DCO sign-off
I agree to the Developer Certificate of Origin
(https://developercertificate.org/).