mirror of
https://github.com/Jeffrey-Sardina/liberated-systemd.git
synced 2026-10-19 01:29:53 -05:00
test: verify snp attestation report
Sign a system report through the io.systemd.Report pipeline in the SNP guest and export the resulting signed report to the host. The host takes the role of a remote verifier: the tsm signature record must carry the digest of the signed report bytes, that digest must be embedded in the REPORT_DATA field of the enclosed SNP attestation report, and the attestation report must verify against the AMD certificate chain with snpguest, which is added to the Fedora test image. Certificates must be pre-provisioned on the host in a sirectory named 'snp-certs' and bound into the test container via $TEST_BIND_DIRS. Signed-off-by: Paul Meyer <katexochen0@gmail.com>
This commit is contained in:
1 parent
7471c5b02d
commit
36a0a64017
6 files changed
+111
-1
No files matched your search
@@ -16,4 +16,6 @@ Packages=
|
||||
kernel-modules-internal
|
||||
rpmautospec
|
||||
scsi-target-utils
|
||||
# Required for TEST-94-COCO (verifying SNP attestation reports)
|
||||
snpguest
|
||||
util-linux-script
|
||||
@@ -179,6 +179,17 @@ meson test
|
||||
└─ guest-test-runner.sh
|
||||
```
|
||||
|
||||
Verifying SNP attestation reports requires the AMD certificate chain, fetched
|
||||
once on the host into a directory named `snp-certs` and passed to the test via
|
||||
`TEST_BIND_DIRS=/path/to/snp-certs`. The VCEK is per-machine and derived from
|
||||
an attestation report of this host, e.g. one generated with `snpguest report`
|
||||
in a manually booted SNP guest:
|
||||
|
||||
```shell
|
||||
$ snpguest fetch ca pem snp-certs <processor-model>
|
||||
$ snpguest fetch vcek pem snp-certs <attestation-report>
|
||||
```
|
||||
|
||||
## Running the integration tests without building systemd from source
|
||||
|
||||
If you want to run the integration tests against prebuilt systemd packages,
|
||||
|
||||
@@ -32,14 +32,19 @@ trap at_exit EXIT
|
||||
# - creds_vmspawn: credential transport by vmspawn is working (via initrd for SNP)
|
||||
# - creds_cmdline: credential transport via cmdline is working (cmdline is measured on SNP)
|
||||
# - tsm_signer_varlink_snp: the tsm report signer yields an SNP attestation report echoing the digest
|
||||
# - report_pipeline: signing a report via io.systemd.Report; the result is verified below, host-side
|
||||
vmspawn_boot_coco "$MACHINE-honest" "$COCO_TYPE" "$WORKDIR/honest" \
|
||||
'detect_virt creds_vmspawn creds_cmdline tsm_signer_varlink_snp' \
|
||||
'detect_virt creds_vmspawn creds_cmdline tsm_signer_varlink_snp report_pipeline' \
|
||||
--image="$IMAGE_DIR/image.raw" \
|
||||
--linux="$IMAGE_DIR/image.vmlinuz" \
|
||||
--initrd="$IMAGE_DIR/image.initrd" \
|
||||
--set-credential="$COCO_CRED_TRUSTED_ID:$COCO_CRED_TRUSTED_VALUE" \
|
||||
selinux=0 systemd.firstboot=no rw \
|
||||
"systemd.set_credential=$COCO_CRED_CMDLINE_ID:$COCO_CRED_CMDLINE_VALUE"
|
||||
|
||||
# The report_pipeline check exported the signed report; verify it as a real verifier would.
|
||||
test -s "$WORKDIR/honest/artifacts/$COCO_ARTIFACT_SIGNED_REPORT"
|
||||
coco_verify_snp_signed_report "$WORKDIR/honest/artifacts/$COCO_ARTIFACT_SIGNED_REPORT" "$WORKDIR/honest"
|
||||
echo "SEV-SNP direct-boot guest correctly reported confidential virtualization and trusted credentials"
|
||||
|
||||
# Offensive test:
|
||||
|
||||
@@ -15,6 +15,11 @@ fi
|
||||
# Name of the guest self-check unit lib.sh injects.
|
||||
COCO_GUEST_UNIT="coco-guest.service"
|
||||
|
||||
# Artifacts the guest checks export for host-side verification, by file name:
|
||||
# written to $COCO_ARTIFACTS_DIR by the guest (see guest-test-runner.sh)
|
||||
# collected into WORKDIR/artifacts on the host (see lib.sh)
|
||||
COCO_ARTIFACT_SIGNED_REPORT="signed-report.seq"
|
||||
|
||||
# Credentials by channel
|
||||
COCO_CRED_TRUSTED_ID="coco.trusted"
|
||||
COCO_CRED_TRUSTED_VALUE="trusted-via-vmspawn"
|
||||
|
||||
@@ -131,3 +131,20 @@ testcase_coco_tsm_signer_varlink_snp() {
|
||||
test -d /sys/kernel/config/tsm/report
|
||||
assert_eq "$(ls -A /sys/kernel/config/tsm/report)" ""
|
||||
}
|
||||
|
||||
# Sign a real system report through the io.systemd.Report pipeline and export the result (the
|
||||
# report plus its signature records) for the host to check, as a real verifier would. No assertions
|
||||
# here beyond a produced document: what the signatures must prove is decided on the untrusted side.
|
||||
testcase_coco_report_pipeline() {
|
||||
local report_seq="${COCO_ARTIFACTS_DIR:?}/$COCO_ARTIFACT_SIGNED_REPORT"
|
||||
|
||||
systemctl start systemd-report.socket
|
||||
|
||||
# The reply carries the signed report as base64-encoded JSON-SEQ: the first record is the
|
||||
# report itself, each further record is one signer backend's signature.
|
||||
varlinkctl call /run/systemd/io.systemd.Report io.systemd.Report.GenerateSigned \
|
||||
'{"matches":["io.systemd.Manager.UnitsTotal"]}' |
|
||||
jq -er .reportData | base64 -d >"$report_seq"
|
||||
|
||||
test -s "$report_seq"
|
||||
}
|
||||
@@ -9,6 +9,9 @@ if [[ "${BASH_SOURCE[0]}" -ef "$0" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util.sh (assert_*) lives one level up in the shared units dir.
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")/util.sh"
|
||||
# shellcheck source=test/units/TEST-94-COCO/fixtures.sh
|
||||
. "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/fixtures.sh"
|
||||
|
||||
@@ -78,6 +81,73 @@ _coco_collect_artifacts() {
|
||||
done <"$results"
|
||||
}
|
||||
|
||||
# coco_verify_snp_report REPORT
|
||||
# Verify the SNP attestation report REPORT with snpguest: the report parses, the AMD certificate
|
||||
# chain (ARK -> ASK -> VCEK) is valid, and the report is signed by the VCEK. The certificates must
|
||||
# be pre-provisioned in a host directory named 'snp-certs', bound in via $TEST_BIND_DIRS: the
|
||||
# harness runs the container without network, and the AMD KDS rate-limits hard enough that
|
||||
# fetching them per test run would be flaky anyway.
|
||||
coco_verify_snp_report() {
|
||||
local report="${1:?}"
|
||||
local certs_dir=/work/snp-certs
|
||||
|
||||
# snpguest is part of the test image (see mkosi.conf.d/fedora): its absence is a broken image,
|
||||
# not a reason to silently skip the verification.
|
||||
if ! command -v snpguest >/dev/null; then
|
||||
echo "snpguest is not installed, cannot verify the SNP attestation report" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
snpguest display report "$report"
|
||||
|
||||
if [[ ! -d "$certs_dir" ]]; then
|
||||
echo "no AMD certificates at $certs_dir; fetch them once on the host" \
|
||||
"(snpguest fetch ca/vcek) and pass the directory via \$TEST_BIND_DIRS" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
snpguest verify certs "$certs_dir"
|
||||
snpguest verify attestation "$certs_dir" "$report"
|
||||
}
|
||||
|
||||
# coco_verify_snp_signed_report REPORT_SEQ WORKDIR
|
||||
# Check a signed report produced by io.systemd.Report's GenerateSigned the way a remote verifier
|
||||
# would: the tsm signature record must carry the digest of the signed report bytes, that digest must
|
||||
# be embedded in the REPORT_DATA field of the enclosed SNP attestation report, and the attestation
|
||||
# report must verify against the AMD certificate chain (see coco_verify_snp_report).
|
||||
coco_verify_snp_signed_report() {
|
||||
local report_seq="${1:?}" workdir="${2:?}"
|
||||
local message="$workdir/message.bin"
|
||||
local report_file="$workdir/attestation-report.bin"
|
||||
local sig digest report
|
||||
|
||||
# The first JSON-SEQ record is the report itself: exactly the byte sequence that got signed,
|
||||
# including the leading record separator (0x1e) and the trailing newline, so 'head -n1'
|
||||
# reproduces it verbatim. Each further record is one signer backend's signature.
|
||||
head -n1 "$report_seq" >"$message"
|
||||
tr -d '\036' <"$message" | jq -e '.mediaType == "application/vnd.io.systemd.report"' >/dev/null
|
||||
digest="$(sha256sum "$message" | cut -d' ' -f1)"
|
||||
|
||||
# Pick the tsm backend's signature record (other backends may be enabled too).
|
||||
sig="$(tail -n +2 "$report_seq" | tr -d '\036' | jq -c 'select(.mechanism == "tsm")')"
|
||||
test -n "$sig"
|
||||
assert_eq "$(jq -r .mediaType <<<"$sig")" "application/vnd.io.systemd.report.signature"
|
||||
assert_eq "$(jq -r .sha256 <<<"$sig")" "$digest"
|
||||
assert_eq "$(jq -r '.data.provider' <<<"$sig")" "sev_guest"
|
||||
|
||||
jq -r '.data.outblob' <<<"$sig" | base64 -d >"$report_file"
|
||||
|
||||
# The attestation report must bind the report digest, not some other value. Hex-encode the
|
||||
# report so fields can be sliced by string offset (2 hex chars per byte).
|
||||
report="$(od -An -vtx1 <"$report_file" | tr -d ' \n')"
|
||||
assert_eq "$((${#report} / 2))" "1184"
|
||||
# REPORT_DATA sits at byte offset 0x50: the digest, zero-padded to 64 bytes.
|
||||
assert_eq "${report:2*0x50:64}" "$digest"
|
||||
assert_eq "${report:2*0x70:64}" "$(printf '00%.0s' {1..32})"
|
||||
|
||||
coco_verify_snp_report "$report_file"
|
||||
}
|
||||
|
||||
# vmspawn_boot_coco MACHINE COCO_TYPE WORKDIR TESTCASES [systemd-vmspawn args...]
|
||||
# Boot a confidential guest with systemd-vmspawn, running the coco guest test runner. TESTCASES is the
|
||||
# space-separated list of guest checks (testcase_coco_* names without the prefix).
|
||||
|
||||
Reference in new issue
Block a user