Skip to content

feat(policy): add stable RTMR1 signer anchor and revocation - #1035

Open
haitaohuang wants to merge 8 commits into
intel:mainfrom
haitaohuang:rtmr1-signer-anchor-port
Open

haitaohuang wants to merge 8 commits into
intel:mainfrom
haitaohuang:rtmr1-signer-anchor-port

Conversation

@haitaohuang

@haitaohuang haitaohuang commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements the RTMR1 signer-anchor proposal in #916 as a continuation of #1032:

  • measure a stable signer anchor derived from the root certificate DER and leaf Subject DER instead of the raw policy issuer chain bytes;
  • use the same anchor calculation in MigTD runtime measurement and migtd-hash;
  • accept an authenticated servTD signer CRL and enforce it for local policy, identity, and mapping signer chains;
  • apply the local CRL when validating peer signer chains during migration;
  • expose a monotonic servtdCrlNum policy floor so peers cannot regress revocation state.

The full policy issuer chain remains enrolled in CFV and recorded in the event payload. This changes only the RTMR1 measurement input and signer-revocation enforcement. The existing JSON TCB mapping and TD Identity model from #1032 are retained.

This PR is stacked on #1032 and is intended as its focused continuation.

Validation

  • formatting, cargo check, clippy, cargo-deny, library build, and library tests;
  • all 32 firmware image build combinations and all six standalone tool builds;
  • all 14 emulation workflow scenarios;
  • Azure policy-v2 IGVM package and hash generation.

Follow-up work

See the future-work section in #1032 for the broader roadmap. Remaining related items identified while checking the TCB-mapping and signer-anchor work are:

  • support CoRIM as an alternate TCB endorsement format, including direct trust-anchor enrollment and CoRIM-only policy builds (Proposal: Use CoRIM as the MigTD endorsement format #929);
  • authenticate and retain CoRIM x5chain material, apply the local servTD CRL to it, and consume authenticated peer CoRIM mappings during migration;
  • reject CoRIM nbf and exp claims because MigTD does not have a trusted wall clock;
  • make simplified TD Identity optional for SVN-only evaluation (Proposal: Make the TD Identity artifact optional #930);
  • add release tooling and validation for independent policy, mapping, and identity signer rotation and measurement stability;
  • strengthen peer policy error propagation and remove obsolete Init-TDINFO/local-TDINFO paths after the one-hash transition is complete.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved correctness issues remain in signer-anchor binding, CRL-floor enforcement, and cumulative mapping updates.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds stable RTMR1 signer anchors, servTD signer-CRL enforcement, monotonic CRL floors, and cumulative TCB mapping support across runtime, tooling, migration, and emulator workflows.

Changes:

  • Updates signer-anchor measurement and offline hash generation.
  • Adds CRL validation, revocation checks, and CRL-number policy enforcement.
  • Updates migration handling, collateral tooling, fixtures, build scripts, and configuration artifacts.
File summaries
File Reviewed change
tools/servtd-collateral-generator/src/main.rs Collateral generator CLI updates
tools/servtd-collateral-generator/src/build.rs Collateral serialization and build logic
tools/servtd-collateral-generator/readme.md Generator usage documentation
tools/migtd-hash/src/main.rs Hash tool CLI updates
tools/migtd-hash/src/lib.rs TDINFO hashing and cumulative mapping updates
tools/migtd-hash/Cargo.toml Tool package metadata
src/policy/test/policy_v2/tcb_mapping.json Cumulative TCB mapping fixture
src/policy/test/policy_v2/servtd_collateral.json ServTD collateral fixture
src/policy/test/policy_v2/cert_chain/policy_issuer_chain.pem Policy issuer-chain fixture
src/policy/src/v2/servtd_collateral.rs Collateral and one-hash mapping validation
src/policy/src/v2/policy.rs Policy verification, CRL checks, and CRL floors
src/policy/src/v2/mod.rs v2 policy module integration
src/policy/src/v2/measurement.rs Canonical measurement and signer anchors
src/policy/src/lib.rs Policy and event definitions
src/migtd/src/spdm/spdm_rsp.rs Peer attestation response handling
src/migtd/src/ratls/server_client.rs Authenticated RA-TLS certificate exchange
src/migtd/src/migration/session.rs SERVTD extension transport
src/migtd/src/migration/rebinding.rs Rebinding continuity handling
src/migtd/src/migration/mod.rs Migration wire compatibility
src/migtd/src/mig_policy.rs Peer policy and migration validation
src/migtd/src/lib.rs MigTD module integration
src/migtd/src/event_log.rs Policy-data event handling
src/migtd/src/bin/migtd/main.rs Runtime policy measurements
src/crypto/src/lib.rs Certificate and signer-chain validation
src/crypto/src/crl.rs CRL parsing and verification
sh_script/build_policy_v2.sh Policy build workflow
sh_script/build_AzCVMEmu_policy_and_test.sh Emulator policy workflow
sh_script/Azure/build_azure_mock_test.sh Azure mock policy workflow
deps/td-shim-AzCVMEmu/tdx-tdcall/src/tdx_emu.rs TDX emulator support
config/templates/td_identity.json TD Identity template
config/templates/td_identity_signed.json Signed TD Identity template
config/templates/tcb_mapping.json TCB mapping template
config/templates/tcb_mapping_signed.json Signed TCB mapping template
config/templates/servtd_collateral.json ServTD collateral template
config/templates/policy_issuer_chain.pem Policy issuer-chain template
config/AzCVMEmu/tcb_mapping.json Emulator TCB mapping
Cargo.lock Dependency lockfile updates
Review details

Suppressed comments (4)

src/policy/src/v2/policy.rs:558

  • servtd_crl_num is evaluated only through GlobalPolicy, but both rebinding directions call the policy evaluators with skip_global = true. Consequently the new CRL floor is enforced for ordinary migration but silently skipped during rebinding, allowing a peer policy with a regressed CRL number in that path. Apply this floor separately when global platform checks are skipped, or stop skipping this constraint.
        if let Some(property) = &self.servtd_crl_num {
            let servtd_crl_num = value.servtd_crl_num.ok_or(PolicyError::CrlEvaluation)?;
            if !property.evaluate_integer(servtd_crl_num, relative_reference.servtd_crl_num)? {
                return Err(PolicyError::CrlEvaluation);
            }
        }

src/policy/src/v2/policy.rs:255

  • Although this PR adds CRL authentication and revocation, the v2 policy tests do not exercise servtd_crl with a valid CRL, a revoked leaf/intermediate, or an issuer mismatch. Because this path controls signer authorization, add fixture-backed tests covering acceptance and fail-closed rejection before relying on the implementation.
        if let Some(crl) = servtd_crl.as_deref() {
            crypto::verify_signer_chain_not_revoked(issuer_chain, crl.as_bytes())
                .map_err(|_| PolicyError::SignerRevoked)?;
            crypto::verify_signer_chain_not_revoked(
                servtd_identity_issuer_chain.as_bytes(),

tools/migtd-hash/src/lib.rs:263

  • Measurements explicitly accepts the legacy tdinfoHash spelling, but this cumulative updater only reads tdinfo_hash. Updating a legacy mapping therefore fails with a missing-field error instead of retaining its history, even though the policy parser supports that artifact. Read both spellings here.
    tools/servtd-collateral-generator/src/main.rs:31
  • This removes the --mapping-chain option, but doc/policy_v2.md:42 still uses that option and describes the output as containing both issuer chains. Following the repository's policy-v2 instructions now fails with an unknown-argument error; update that documentation or retain a compatibility alias.
  • Files reviewed: 36/41 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/migtd/src/mig_policy.rs Outdated
Comment thread tools/migtd-hash/src/lib.rs
Comment thread src/policy/src/v2/measurement.rs
@jyao1

jyao1 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Question: Is there any new test to validate the new revocation path?

  • src/crypto/src/crl.rs (verify_crl_signature, is_serial_revoked, get_crl_issuer_der)
  • src/crypto/src/lib.rs (verify_signer_chain_not_revoked/verify_issuer_ca_constraints)
  • src/policy/src/v2/policy.rs exercising PolicyError::SignerRevoked or servtd_crl_num evaluation
  • src/migtd/src/mig_policy.rs for the peer-chain-vs-local-CRL check.

@jyao1

jyao1 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

On Implicit constraint: the same CRL-issuing CA must be common to both chains it's checked against. servtd_crl is a single field checked against both issuer_chain and servtd_identity_issuer_chain. verify_signer_chain_not_revoked independently looks for a CA cert matching the CRL's issuer inside whichever chain it's given, so this only works if both the policy-signer chain and the identity-signer chain actually share a common CA that issued the CRL. Reasonable for the expected deployment (single root/intermediate authority), but it's an assumption the code enforces implicitly (fails closed with "CRL issuer does not match any CA in the signer chain" if violated) rather than one stated anywhere near the servtd_crl field or in the design doc's schema section.
A one-line doc/comment note would save a future reader from re-deriving this.

@jyao1

jyao1 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Comment: RawPolicyData::verify's local-CRL peer check silently no-ops if the local policy has no CRL. By design the CRL is optional, so this is expected, but it means revocation enforcement for peers is entirely dependent on whether the verifying side happens to carry a CRL — a peer's own compromised/revoked chain is otherwise accepted.
Already implied by "optional field" in the design doc; flagging only so it's an explicit, acknowledged operational requirement (ship a CRL if you want revocation enforced) rather than something a deployer discovers later.

@haitaohuang
haitaohuang force-pushed the rtmr1-signer-anchor-port branch from cf2bee7 to 7ea4a3a Compare September 11, 2026 23:33
@haitaohuang

Copy link
Copy Markdown
Contributor Author

Question: Is there any new test to validate the new revocation path?

  • src/crypto/src/crl.rs (verify_crl_signature, is_serial_revoked, get_crl_issuer_der)
  • src/crypto/src/lib.rs (verify_signer_chain_not_revoked/verify_issuer_ca_constraints)
  • src/policy/src/v2/policy.rs exercising PolicyError::SignerRevoked or servtd_crl_num evaluation
  • src/migtd/src/mig_policy.rs for the peer-chain-vs-local-CRL check.
    Added now

@haitaohuang

Copy link
Copy Markdown
Contributor Author

On Implicit constraint: the same CRL-issuing CA must be common to both chains it's checked against. servtd_crl is a single field checked against both issuer_chain and servtd_identity_issuer_chain. verify_signer_chain_not_revoked independently looks for a CA cert matching the CRL's issuer inside whichever chain it's given, so this only works if both the policy-signer chain and the identity-signer chain actually share a common CA that issued the CRL. Reasonable for the expected deployment (single root/intermediate authority), but it's an assumption the code enforces implicitly (fails closed with "CRL issuer does not match any CA in the signer chain" if violated) rather than one stated anywhere near the servtd_crl field or in the design doc's schema section. A one-line doc/comment note would save a future reader from re-deriving this.

Added documentation

@haitaohuang
haitaohuang force-pushed the rtmr1-signer-anchor-port branch from 7ea4a3a to fa8cc95 Compare September 12, 2026 00:59
@haitaohuang

Copy link
Copy Markdown
Contributor Author

Comment: RawPolicyData::verify's local-CRL peer check silently no-ops if the local policy has no CRL. By design the CRL is optional, so this is expected, but it means revocation enforcement for peers is entirely dependent on whether the verifying side happens to carry a CRL — a peer's own compromised/revoked chain is otherwise accepted. Already implied by "optional field" in the design doc; flagging only so it's an explicit, acknowledged operational requirement (ship a CRL if you want revocation enforced) rather than something a deployer discovers later.

Added comments and fail close if CRL is missing.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate findings remain in Azure identity generation, SPDM element counts, and CRL contract compatibility.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (4)

src/migtd/src/spdm/spdm_req.rs:1025

  • This rebind request likewise emits only four elements, but VDM_MESSAGE_EXCHANGE_REBIND_ATTEST_INFO_REQ_WITH_HISTORY_INFO_ELEMENT_COUNT remains 5 and the responder enforces that value. A peer following the element-count contract can reject the rebind request; use the actual v2 count of 4 for this opcode.
    src/migtd/src/spdm/spdm_req.rs:446
  • Under policy_v2, the TdReportInit element is compiled out below, so this request contains four elements (quote, event log, policy hash, and SERVTD_EXT), but the header still advertises the shared count of 5. The responder also validates that value, so this is internally masked but produces a malformed request for any peer that validates the count against the actual payload. Define a policy-v2 request count of 4 (and retain 5 only for the legacy form).
    src/policy/src/v2/servtd_collateral.rs:35
  • The PR description says the authenticated servTD CRL is optional, but this new non-optional String makes serde reject every v2 collateral that omits servtdCrl; the updated generator also requires --servtd-crl. If the CRL is intentionally mandatory, please align the PR contract and compatibility notes; otherwise make the field optional and condition enforcement on its presence.
    tools/servtd-collateral-generator/src/build.rs:18
  • The PR description calls the servTD signer CRL optional, but making this field a required String means any collateral without servtdCrl now fails deserialization (and the CLI also requires --servtd-crl). Please reconcile the implementation with the stated compatibility contract, or update the PR description and migration requirements if all v2 policies are intentionally required to carry a CRL.
  • Files reviewed: 64/69 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread sh_script/build_AzCVMEmu_policy_and_test.sh
Comment thread src/policy/src/v2/measurement.rs
@haitaohuang
haitaohuang force-pushed the rtmr1-signer-anchor-port branch from fa8cc95 to 26ac92d Compare September 12, 2026 07:04
Comment thread src/crypto/src/crl.rs
Comment thread src/policy/src/v2/policy.rs
Comment thread src/crypto/src/lib.rs Outdated
Comment thread src/crypto/src/lib.rs Outdated
Use SHA-384 over the complete unmasked TDINFO as the Policy v2 mapping key.
Canonicalize policyData once with only the circular mapping removed so
runtime verification and offline tooling extend identical RTMR2 bytes.

Verify the signed mapping with the RTMR1-bound policy issuer chain, remove
the separate mapping chain and obsolete mapping identity fields, and ignore
the legacy outer policy signature as required by the proposal.

Resolve the initial SVN through the source's authenticated JSON mapping
and take its current SVN from the authenticated quote or TDREPORT
evaluation. This allows an older destination to accept a newer source
release without predicting its hash. Fail closed on missing authenticated
SVN evidence and prevent the SERVTD_EXT current hash from overriding it.

Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Assisted-by: GitHub Copilot CLI:GPT-5.6 Sol
Assisted-by: GitHub Copilot CLI:gpt-6-astra
Carry authenticated SERVTD_EXT continuity evidence through migration and
rebinding. Bind the peer policy issuer chain to attested RTMR1, reject
lookup misses or SVN rollback, and fail closed when SERVTD_ATTR masking
makes the endorsed unmasked hashes inapplicable.

Match policy-v2 migration and rebinding request headers to their four
encoded elements while retaining the five-element policy-v1 migration
format.

Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Assisted-by: GitHub Copilot CLI:GPT-5.6 Sol
Assisted-by: GitHub Copilot CLI:gpt-6-astra
Retain authority-maintained hash history when adding a release, allow
multiple hashes at one SVN, and reject conflicting duplicate assignments.
Emit deterministic mapping bytes and validate signed mappings before use.

Update the policy-v2 guide and mapping-update example for the cumulative
workflow, including the required --mapping-isvsvn argument. Document
mapping signer authority, trust assumptions, and the distinction between
release-SVN continuity and independent policy-SVN ordering.

Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Assisted-by: GitHub Copilot CLI:GPT-5.6 Sol
Assisted-by: GitHub Copilot CLI:gpt-6-astra
Consume the signed identity used to build the measured release instead of re-signing it after recording tdinfo_hash. Reject any non-mapping policy change before replacing the final policy outputs, including identity, issuer-chain, and platform collateral changes.

Document preparation before measurement, immutable release inputs, and a final image hash comparison against the recorded endorsement.

Assisted-by: GitHub Copilot CLI:gpt-6-astra
Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Remove check_engine_not_older and its dedicated test because runtime continuity uses the MigTD helper instead. Preserve the equal-SVN assertion in the live continuity tests alongside the existing upgrade, downgrade, and lookup-failure coverage.

Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Assisted-by: GitHub Copilot CLI:gpt-6-astra
Measure the policy signer as a stable root-certificate plus leaf-subject anchor in both runtime and offline hashing. Require authenticated, numbered servTD CRLs so omission cannot bypass signer-chain enforcement at initialization or peer validation, and support a monotonic servtd CRL policy floor.\n\nPort only the proposal-specific implementation from ms/integration while retaining tcbmapping's existing JSON mapping and identity model.

Assisted-by: GitHub Copilot CLI:GPT-5.6 Sol
Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Assisted-by: GitHub Copilot CLI:gpt-6-astra
Exercise signed empty CRLs, revoked leaf and intermediate certificates, issuer mismatches, invalid signatures, and CA constraints. Cover policy rejection and peer checks against verifier-owned revocation state, including a rotated local policy signer.

Use public fixtures without retaining private keys. Extract the existing peer revocation block without changing its ordering or logging so tests can use independently verified policies without global state. Add serde_json only as a dev dependency for test policy construction.

Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
Assisted-by: GitHub Copilot CLI:gpt-6-astra
Accept only complete, direct, issuer-wide CRLs signed by the signing leaf\x27s immediate non-root CA with explicit cRLSign permission. Scope serial lookups to the signing leaf, authenticate peer CRL metadata under the same issuer as the local authoritative CRL, and keep peer revocation entries out of local decisions.

Reject delta, partitioned, indirect, reason-scoped, malformed and unsupported critical inputs while retaining non-critical Microsoft metadata. Leave Intel platform CRL parsing unrestricted. Cover signed negative fixtures and update mock issuers and the supported-profile documentation.

Assisted-by: GitHub Copilot CLI:gpt-6-astra
Signed-off-by: Haitao Huang <haitaohuang@microsoft.com>
@haitaohuang
haitaohuang force-pushed the rtmr1-signer-anchor-port branch from 8b4e793 to 8ed42d0 Compare October 1, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants