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
47 changes: 47 additions & 0 deletions .github/workflows/integration-emu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ jobs:
test-command: "./migtdemu.sh --operation rebind-prepare --policy-file ./config/AzCVMEmu/policy_v2_corim.json --servtd-signer-anchor-file ./config/AzCVMEmu/servtd_signer_anchor.bin --servtd-corim-file ./config/AzCVMEmu/tcb_mapping_corim.cose --mock-report --features spdm_attestation --both --no-sudo --log-level info"
artifact-name: "corim-rebind-test-logs"

# The source CoRIM maps the mock TDINFO hash while the destination
# CoRIM does not, requiring lookup through the authenticated peer.
- test-name: "CoRIM Asymmetric Peer SVN Lookup (Mock Report)"
test-type: "corim-asymmetric-migration"
install-jq: 'true'
timeout-seconds: 900
test-command: "./migtdemu.sh --policy-v2 --policy-file ./config/AzCVMEmu/policy_v2_corim_asymmetric.json --servtd-signer-anchor-file ./config/AzCVMEmu/servtd_signer_anchor_asymmetric.bin --src-servtd-corim-file ./config/AzCVMEmu/tcb_mapping_corim_asymmetric_src.cose --dst-servtd-corim-file ./config/AzCVMEmu/tcb_mapping_corim_asymmetric_dst.cose --mock-report --features spdm_attestation --both --no-sudo --log-level info"
artifact-name: "corim-asymmetric-migration-test-logs"

- test-name: "SPDM Rebind Prepare (Skip RA)"
test-type: "spdm-rebind-skip-ra"
install-jq: 'true'
Expand Down Expand Up @@ -249,6 +258,7 @@ jobs:
run: |
chmod +x ./migtdemu.sh
chmod +x ./sh_script/build_AzCVMEmu_policy_and_test.sh
chmod +x ./sh_script/gen_asymmetric_servtd_corim_fixture.sh

- name: Generate policy files
if: matrix.install-jq == 'true'
Expand All @@ -267,6 +277,10 @@ jobs:
config/AzCVMEmu/tcb_mapping_corim.cose
fi

- name: Generate asymmetric CoRIM fixtures
if: matrix.test-type == 'corim-asymmetric-migration'
run: ./sh_script/gen_asymmetric_servtd_corim_fixture.sh

- name: Run test
id: test_run
run: |
Expand Down Expand Up @@ -447,6 +461,35 @@ jobs:
done
fi

# Confirm the peer mappings differ and the policy check succeeded.
if [[ "${{ matrix.test-type }}" == "corim-asymmetric-migration" ]]; then
echo ""
echo "=== Verifying asymmetric peer CoRIM fixtures ==="
if ! jq -e '[.policyData.policy[] | select(has("servtd"))] | length == 0' \
config/AzCVMEmu/policy_v2_corim_asymmetric.json >/dev/null; then
echo "❌ policy_v2_corim_asymmetric.json still contains a servtd isvsvn rule (would mask the decisive check)"
exit 1
fi
if [[ $(wc -c < config/AzCVMEmu/servtd_signer_anchor_asymmetric.bin) -ne 48 ]]; then
echo "❌ Asymmetric ServTD signer anchor is not 48 bytes"
exit 1
fi
if cmp -s config/AzCVMEmu/tcb_mapping_corim_asymmetric_src.cose \
config/AzCVMEmu/tcb_mapping_corim_asymmetric_dst.cose; then
echo "❌ Source and destination CoRIM fixtures are byte-identical -- scenario is not asymmetric"
exit 1
fi
echo "✅ Source/destination CoRIM fixtures differ and share a 48-byte signer anchor"
for ROLE in source destination; do
LOG=$(find . -maxdepth 1 -name "migtd_*_${ROLE}.log" -print -quit)
if [[ -n "$LOG" ]] && grep -aq "UnqualifiedMigTdInfo\|Policy v2 check failed" "$LOG"; then
echo "❌ $ROLE log shows a policy check failure despite the run being reported as passing"
exit 1
fi
done
echo "✅ No UnqualifiedMigTdInfo/policy-check-failed evidence in either role's log"
fi

# Dump revocation-related log lines for the signer-revocation test.
if [[ "${{ matrix.test-type }}" == "policy-v2-revoked" ]]; then
echo ""
Expand Down Expand Up @@ -477,6 +520,10 @@ jobs:
config/AzCVMEmu/policy_v2_corim.json
config/AzCVMEmu/servtd_signer_anchor.bin
config/AzCVMEmu/tcb_mapping_corim.cose
config/AzCVMEmu/policy_v2_corim_asymmetric.json
config/AzCVMEmu/servtd_signer_anchor_asymmetric.bin
config/AzCVMEmu/tcb_mapping_corim_asymmetric_src.cose
config/AzCVMEmu/tcb_mapping_corim_asymmetric_dst.cose
target/release/migtd
retention-days: 7
if-no-files-found: ignore
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ config/AzCVMEmu/policy_issuer_chain*.pem
config/AzCVMEmu/policy_v2_corim.json
config/AzCVMEmu/servtd_signer_anchor.bin
config/AzCVMEmu/tcb_mapping_corim.cose
# Generated asymmetric CoRIM fixtures
config/AzCVMEmu/policy_v2_corim_asymmetric.json
config/AzCVMEmu/servtd_signer_anchor_asymmetric.bin
config/AzCVMEmu/tcb_mapping_corim_asymmetric_src.cose
config/AzCVMEmu/tcb_mapping_corim_asymmetric_dst.cose
config/Azure/policy_v2_signed*.json
config/Azure/policy_issuer_chain*.pem
# Auto-generated collateral/tcb-mapping (fetched/regenerated by the mock-test builds)
Expand Down
184 changes: 184 additions & 0 deletions sh_script/corim_cli_helpers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env bash

CORIM_CLI_REV="8438b89051aac8e170c753c17540d24c2eb27650"

configure_corim_cli() {
local repo_root="$1"
CORIM_CLI_INSTALL_ROOT="${CORIM_CLI_INSTALL_ROOT:-$repo_root/target/corim-cli-$CORIM_CLI_REV}"
CORIM_CLI_BIN="${CORIM_CLI:-$CORIM_CLI_INSTALL_ROOT/bin/corim-cli}"
}

install_corim_cli() {
if [ ! -x "$CORIM_CLI_BIN" ]; then
if [ -n "${CORIM_CLI:-}" ]; then
echo "CORIM_CLI is not executable: $CORIM_CLI_BIN" >&2
return 1
fi

echo "Installing official Azure/corim CLI at $CORIM_CLI_REV..."
cargo install \
--locked \
--git https://github.com/Azure/corim \
--rev "$CORIM_CLI_REV" \
--root "$CORIM_CLI_INSTALL_ROOT" \
corim-cli
fi

if ! "$CORIM_CLI_BIN" sign --help >/dev/null 2>&1; then
echo "corim-cli does not support the required sign command: $CORIM_CLI_BIN" >&2
echo "Use Azure/corim revision $CORIM_CLI_REV or a newer compatible build." >&2
return 1
fi
}

ecdsa_der_to_p1363() {
local input="$1"
local output="$2"

python3 - "$input" "$output" <<'PY'
import pathlib
import sys

data = pathlib.Path(sys.argv[1]).read_bytes()

def read_length(offset):
if offset >= len(data):
raise ValueError("missing DER length")
first = data[offset]
offset += 1
if first < 0x80:
return first, offset
width = first & 0x7f
if width == 0 or width > 2 or offset + width > len(data):
raise ValueError("invalid DER length")
return int.from_bytes(data[offset:offset + width], "big"), offset + width

def read_integer(offset):
if offset >= len(data) or data[offset] != 0x02:
raise ValueError("expected DER INTEGER")
length, offset = read_length(offset + 1)
end = offset + length
if length == 0 or end > len(data):
raise ValueError("invalid DER INTEGER length")
value = data[offset:end]
if value[0] & 0x80:
raise ValueError("negative ECDSA integer")
value = value.lstrip(b"\x00")
if len(value) > 48:
raise ValueError("P-384 integer exceeds 48 bytes")
return value.rjust(48, b"\x00"), end

if not data or data[0] != 0x30:
raise ValueError("expected DER SEQUENCE")
sequence_length, offset = read_length(1)
if offset + sequence_length != len(data):
raise ValueError("invalid DER SEQUENCE length")
r, offset = read_integer(offset)
s, offset = read_integer(offset)
if offset != len(data):
raise ValueError("trailing data after ECDSA signature")

pathlib.Path(sys.argv[2]).write_bytes(r + s)
PY
}

compute_signer_anchor() {
local root_cert="$1"
local signer_eku_oid="$2"
local output="$3"
local work_dir="$4"
local root_der="$work_dir/corim-root.der"
local root_hash="$work_dir/corim-root.sha384"
local eku_der="$work_dir/corim-signer-eku.der"

openssl x509 -in "$root_cert" -outform DER -out "$root_der"
openssl dgst -sha384 -binary "$root_der" > "$root_hash"
openssl asn1parse -genstr "OID:$signer_eku_oid" -out "$eku_der" -noout

{
printf '%s' 'MIGTD-RTMR1-ANCHOR-V1'
printf '\0'
cat "$root_hash"
printf '\0'
cat "$eku_der"
} | openssl dgst -sha384 -binary > "$output"

if [ "$(wc -c < "$output")" -ne 48 ]; then
echo "Generated signer anchor is not 48 bytes" >&2
return 1
fi
}

generate_signed_corim() {
local tdinfo_hash="$1"
local svn="$2"
local generation="$3"
local cert_chain="$4"
local private_key="$5"
local output="$6"
local work_dir="$7"
local template="$work_dir/tcb_mapping_corim.json"
local unsigned="$work_dir/tcb_mapping_corim.cbor"
local staging="$work_dir/tcb_mapping_corim.staging.cose"
local tbs="$work_dir/tcb_mapping_corim.tbs"
local signature_der="$work_dir/tcb_mapping_corim.sig.der"
local signature_raw="$work_dir/tcb_mapping_corim.sig"
local hash_base64

hash_base64=$(printf '%s' "$tdinfo_hash" | xxd -r -p | openssl base64 -A)
jq -n \
--arg hash "$hash_base64" \
--argjson svn "$svn" \
--argjson generation "$generation" \
'{
"corim-id": "Microsoft/TDX/tcb-mapping",
"comids": [{
"tag-identity": {
"id": "1F2E3D4C-5B6A-4798-8A9B-0C1D2E3F4A5B",
"version": $generation
},
"triples": {
"reference-triples": [{
"ref-env": {
"class": {"vendor": "Intel", "model": "TDX"},
"instance": {"type": "bytes", "value": "bWlncmF0aW9uLXRk"}
},
"ref-claims": [{
"value": {"digests": [[7, $hash]]}
}]
}],
"conditional-endorsement-series-triples": [{
"common-condition": {
"environment": {
"class": {"vendor": "Intel", "model": "TDX"},
"instance": {"type": "bytes", "value": "bWlncmF0aW9uLXRk"}
},
"claims-list": []
},
"series": [{
"condition": [{
"value": {"digests": [[7, $hash]]}
}],
"addition": [{
"value": {"svn": {"type": "svn", "value": $svn}}
}]
}]
}]
}
}]
}' > "$template"

"$CORIM_CLI_BIN" generate "$template" -o "$unsigned"
"$CORIM_CLI_BIN" sign prepare "$unsigned" \
--alg ES384 \
--signer-name "MigTD TCB Mapping Endorsement" \
--x5chain "$cert_chain" \
--out-staging "$staging" \
--out-tbs "$tbs"
openssl dgst -sha384 -sign "$private_key" -out "$signature_der" "$tbs"
ecdsa_der_to_p1363 "$signature_der" "$signature_raw"
"$CORIM_CLI_BIN" sign finalize "$staging" \
--signature "$signature_raw" \
-o "$output"
"$CORIM_CLI_BIN" validate --skip-expiry "$output"
}
97 changes: 97 additions & 0 deletions sh_script/gen_asymmetric_servtd_corim_fixture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
#
# Copyright (c) 2026 Microsoft Corporation
#
# SPDX-License-Identifier: BSD-2-Clause-Patent

set -euo pipefail

PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/config/AzCVMEmu}"
WORK_DIR="$PROJECT_ROOT/target/servtd-corim-asymmetric-fixture-work"
BASE_POLICY="$OUTPUT_DIR/policy_v2_corim.json"
BASE_GEN_SCRIPT="$PROJECT_ROOT/sh_script/build_AzCVMEmu_policy_and_test.sh"
SIGNER_EKU_OID="${MIGTD_SIGNER_EKU_OID:-1.3.6.1.4.1.311.76.59.1.43}"

# shellcheck source=corim_cli_helpers.sh
source "$PROJECT_ROOT/sh_script/corim_cli_helpers.sh"
configure_corim_cli "$PROJECT_ROOT"

if [[ ! -f "$BASE_POLICY" ]]; then
echo "Missing CoRIM-only policy: $BASE_POLICY" >&2
exit 1
fi

MOCK_TDINFO_HASH="$(grep -oP 'CORIM_MOCK_TDINFO_HASH="\K[^"]+' "$BASE_GEN_SCRIPT")"
if [[ -z "$MOCK_TDINFO_HASH" ]]; then
echo "Missing mock TDINFO hash in $BASE_GEN_SCRIPT" >&2
exit 1
fi

rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR/src" "$WORK_DIR/dst"
trap 'rm -rf "$WORK_DIR"' EXIT

install_corim_cli

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 \
-out "$WORK_DIR/root.key"
openssl req -new -x509 \
-key "$WORK_DIR/root.key" \
-days 3650 \
-out "$WORK_DIR/root.pem" \
-subj "/CN=MigTD Asymmetric CoRIM Test Root/O=Microsoft" \
-sha384

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 \
-out "$WORK_DIR/leaf.key"
openssl req -new \
-key "$WORK_DIR/leaf.key" \
-out "$WORK_DIR/leaf.csr" \
-subj "/CN=MigTD Asymmetric CoRIM Test Signer/O=Microsoft"
openssl x509 -req \
-in "$WORK_DIR/leaf.csr" \
-CA "$WORK_DIR/root.pem" \
-CAkey "$WORK_DIR/root.key" \
-CAcreateserial \
-out "$WORK_DIR/leaf.pem" \
-days 3650 \
-sha384 \
-extensions v3_signer \
-extfile <(printf '[v3_signer]\nkeyUsage = digitalSignature\nextendedKeyUsage = %s\n' "$SIGNER_EKU_OID")
cat "$WORK_DIR/leaf.pem" "$WORK_DIR/root.pem" > "$WORK_DIR/chain.pem"

POLICY_SVN="$(jq -er '.policyData.policySvn | select(type == "number")' "$BASE_POLICY")"
compute_signer_anchor \
"$WORK_DIR/root.pem" \
"$SIGNER_EKU_OID" \
"$OUTPUT_DIR/servtd_signer_anchor_asymmetric.bin" \
"$WORK_DIR"

# Only the source maps the shared mock hash.
generate_signed_corim \
"$MOCK_TDINFO_HASH" \
2 \
"$POLICY_SVN" \
"$WORK_DIR/chain.pem" \
"$WORK_DIR/leaf.key" \
"$OUTPUT_DIR/tcb_mapping_corim_asymmetric_src.cose" \
"$WORK_DIR/src"
generate_signed_corim \
"$(printf 'DEADBEEF%.0s' {1..12})" \
1 \
"$POLICY_SVN" \
"$WORK_DIR/chain.pem" \
"$WORK_DIR/leaf.key" \
"$OUTPUT_DIR/tcb_mapping_corim_asymmetric_dst.cose" \
"$WORK_DIR/dst"

jq '.policyData.policy |= [.[] | select(has("servtd") | not)]' \
"$BASE_POLICY" > "$OUTPUT_DIR/policy_v2_corim_asymmetric.json"

if cmp -s \
"$OUTPUT_DIR/tcb_mapping_corim_asymmetric_src.cose" \
"$OUTPUT_DIR/tcb_mapping_corim_asymmetric_dst.cose"; then
echo "Source and destination CoRIMs must differ" >&2
exit 1
fi
Loading
Loading