Skip to content

Add a derived keys test - #242

Open
markg-github wants to merge 9 commits into
AMDEPYC:mainfrom
markg-github:pr/derived-keys
Open

Add a derived keys test#242
markg-github wants to merge 9 commits into
AMDEPYC:mainfrom
markg-github:pr/derived-keys

Conversation

@markg-github

@markg-github markg-github commented May 13, 2026

Copy link
Copy Markdown
Contributor

The test invokes snpguest repeatedly, especially the key subcommand, to test the SEV-SNP derived keys feature. The test mostly verifies that the derived keys are different when they're supposed to be.

More complete testing could be achieved by including an ID block. Including an ID block is completely independent and I plan a PR for it soon.

I think AI did a decent job at maintaining "certificate consistency", but I'm not sure about the versioning.

Closes #105 and https://amd.atlassian.net/browse/DEAEOCC-304 (Jira).

Copilot AI lite review requested due to automatic review settings May 13, 2026 03:02

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.

Pull request overview

Adds an SNP guest “derived keys” test module to the guest image and wires its results into the host-side reporting so derived-key behavior is exercised and summarized alongside existing attestation tests.

Changes:

  • Introduce a new guest test module (key-derivation) implemented as a Python script invoked by a systemd oneshot service.
  • Integrate the new service into the guest test stage ordering and into host-side log display / certificate summary generation.
  • Ensure the guest image includes Python 3 to run the new test.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
modules/test/guest/test-done/mkosi.extra/usr/local/lib/systemd/system/test-done.service Adds key-derivation.service as a dependency of the test barrier.
modules/test/guest/mkosi.conf Includes the new key-derivation guest test module in the guest test build.
modules/test/guest/key-derivation/README.md Documents the new derived-keys test module and how to run/interpret it.
modules/test/guest/key-derivation/mkosi.extra/usr/local/lib/systemd/system/key-derivation.service Adds a systemd oneshot service to run the derived-keys test script.
modules/test/guest/key-derivation/mkosi.extra/usr/local/lib/scripts/snpguest_key_derivation.py Implements key derivation tests (determinism, VMPL isolation, root key selection, parameter sensitivity).
modules/report/host/sev-certificate-generator/.../sev_certificate_version_3_0_0_0.py Extends guest summary generation to include step-by-step key-derivation results and inferred status fallback.
modules/report/host/display-guest-logs/.../display-guest-logs.sh Includes key-derivation.service in the host’s guest-log display output.
modules/build/guest/mkosi.conf Adds python3 to the guest image package set to support the new test script.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread modules/test/guest/key-derivation/README.md Outdated
Comment thread modules/test/guest/key-derivation/README.md Outdated
Comment on lines +334 to +338
if not derive_key(key_vmpl1_file, root_key="vcek", vmpl=1):
print(" Note: VMPL1 derivation failed (expected if not running at VMPL0)")
print("✓ PASS: N/A")
return True

Comment on lines +356 to +358
def test_root_key_difference() -> bool:
"""Test that different root keys (VCEK vs VMRK) produce different keys."""
key_vck_file = KEY_DERIVATION_DIR / "vck_key.bin"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@markg-github I know that you meant vcek ignore this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe I saw "VCK" somewhere and it may me wonder about something that I was already curious about: in the context of derived keys, VCEK, as an asymmetric key (pair), shouldn't be relevant. What's relevant is the derivation chain from fuses (PUFs?) to the derived keys and I thought VCK might be the name of something in this chain.

Comment on lines +138 to +140
for step, status_code in key_derivation_data.items():
key_derivation_data[step] = "passed" if int(status_code) == 0 else "failed"

@amd-aliem

Copy link
Copy Markdown
Contributor

@markg-github I had added some certificate versioning logic in #241 (it still needs to get reviewed), but I think it might be good to discuss what we want to keep and how we'll merge both. For that one I was intending the next few tests to be under v3.0.0-1.

Do you have some passing certificate examples you can link here? It should have a milestone assigned if it reaches some certificate level. I looked at your recent ones in your repository and they appear to have issues. One of the issues looked familiar, I had a fix for snpguest ok running too early sometimes. But not sure if the others were expected. Note that you'll need to create the milestone in your fork for it to be assigned if you haven't yet (c3.0.0-0, and later when we enable it, c3.0.0-1)

Copilot AI review requested due to automatic review settings June 5, 2026 16:42

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (2)

modules/report/host/sev-certificate-generator/mkosi.extra/usr/local/lib/scripts/generate_sev_certificate/sev_certificate/sev_certificate_version_3_0_0_0.py:171

  • key-derivation.service is appended to snpguest_services_list even when it has no journal entries. The subsequent extract_service_status() call uses subprocess.run(..., check=True) (in Service.get_service_message) and will raise CalledProcessError if the unit is absent, which can crash certificate generation.
        # key-derivation.service may finish after other services and miss the journal
        # upload window, so ensure it is always included even if discovery missed it.
        if "key-derivation.service" not in snpguest_services_list:
            snpguest_services_list.append("key-derivation.service")

        # Map SNP Guest test service name with its status
        snpguest_services_status ={}

        for service in snpguest_services_list:
            service_status = self.sev_service.extract_service_status(service, "guest")
            snpguest_services_status[service] = service_status

modules/build/guest/mkosi.conf:14

  • The new key-derivation test module executes /usr/bin/python3, but most guest image templates define their own Packages= list and currently do not include python3. If mkosi does not merge Packages across includes, the key-derivation service will fail at runtime. Please ensure python3 is included in the final guest images (e.g., add it to each guest image Packages= list, or confirm/document that modules/build/guest/mkosi.conf reliably augments packages).
[Content]
KernelCommandLine=console=ttyS0
Packages=
	python3

Comment on lines +393 to +397
if not derive_key(key_vmpl1_file, root_key="vcek", vmpl=1):
print(" Note: VMPL1 derivation failed (expected if not running at VMPL0)")
print("✓ PASS: N/A")
return True

Comment on lines +468 to +472
# the firmware; treat all loop failures as expected so they don't flood
# the log with ERROR output.
if not derive_key(key_file, root_key="vcek", vmpl=0, guest_svn=svn,
guest_field_select=1 << 4, expected_failure=True):
failed_svns.append(svn)
Comment on lines +535 to +538
key_file = KEY_DERIVATION_DIR / f"tcb_{tcb_u64:016x}_key.bin"
if not derive_key(key_file, root_key="vcek", vmpl=0, tcb_version=tcb_u64,
guest_field_select=1 << 5, expected_failure=True):
failed_tcbs.append(tcb_u64)
After=system.target

# Putting the key derivation test, which should never fail, first
# avoids the effect of other guest test services that fail. Doing
Comment on lines +5 to +9
This script tests the snpguest key derivation functionality, verifying:
1. Deterministic key generation (same params -> same key)
2. VMPL-based key isolation (different VMPL -> different keys)
3. Root key differences (VCK vs VMRK -> different keys)
4. Parameter sensitivity (different params -> different keys)
python3-pip
python3-emoji
jq
apt
Comment on lines +23 to +27
openssl
ca-certificates
jq
xxd

@DGonzalezVillal DGonzalezVillal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You went above and beyond! I think this really covers all of the derived key functionality testing, covered pretty much most test cases.

I added some comment on you tcb derivation logic, tcb is derived from chip and not from report version.

Also just an idea on how to maybe parse the contents of the report. I know historically we've done it by printing the report and the parsing the contents, but I wonder if for us trying to handle bytes could be cleaner.

I also think that the way you have it set up could lend itself well to migrate to the new test harness.

For some of the tests you're generating a large amount of keys, that's the one thing that worries me since the new harness doesn't really have a way of running functions on guest, so we might need to brainstorm how to migrate the size of your test into the harness.

Comment on lines +42 to +53
"""
AMD SEV-SNP TCB_VERSION packed as a u64:
bits 7:0 - Boot Loader SVN
bits 15:8 - TEE SVN
bits 47:16 - Reserved (zero)
bits 55:48 - SNP firmware SVN
bits 63:56 - Microcode SVN
"""
boot_loader: int = 0
tee: int = 0
snp: int = 0
microcode: int = 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will be different according to generation:

This matches the Milan and Genoa TCB spec. We need to extend to the Turin definition. Venice will be a completely different look as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ACK

f"snp=0x{self.snp:02x} mc=0x{self.microcode:02x}")


EXPECTED_REPORT_VERSION = 2 # ATTESTATION_REPORT schema version this test was written for

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a particular reason you need to be aware of the report version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You mean since the code doesn't parse the report directly, per your earlier comment?

In general, I would say any type of parsing should condition on a (schema/struct) version whenever possible.

If you're asking why I need to read the report in the 1st place, it's to learn what the derived keys valid (and invalid) inputs are.

Comment on lines +205 to +240
info = ReportInfo()

m = re.search(r'^\s*Version\s*[:\s]+(0x[0-9a-fA-F]+|[0-9]+)',
display_output, re.IGNORECASE | re.MULTILINE)
if m:
info.version = int(m.group(1), 0)

m = re.search(r'Guest\s+SVN\s*[:\s]+(0x[0-9a-fA-F]+|[0-9]+)',
display_output, re.IGNORECASE)
if m:
info.guest_svn = int(m.group(1), 0)

m = re.search(r'Family\s+ID\s*[:\s]+([0-9a-fA-F]+)', display_output, re.IGNORECASE)
if m:
info.family_id = m.group(1).lower()

m = re.search(r'Image\s+ID\s*[:\s]+([0-9a-fA-F]+)', display_output, re.IGNORECASE)
if m:
info.image_id = m.group(1).lower()

boundary = r'(?:Current|Committed|Reported|Launch)\s+TCB'
for section_name, attr in [
('Current TCB', 'current_tcb'),
('Committed TCB', 'committed_tcb'),
('Reported TCB', 'reported_tcb'),
('Launch TCB', 'launch_tcb'),
]:
pattern = rf'{re.escape(section_name)}\s*:?(.*?)(?={boundary}|\Z)'
m = re.search(pattern, display_output, re.DOTALL | re.IGNORECASE)
if m:
setattr(info, attr, parse_tcb_section(m.group(1)))

return info
except Exception as e:
print(f"WARNING: Failed to parse report info: {e}", file=sys.stderr)
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm wondering, I have not implemented a parser in python, but maybe if it would be easier to find the info you're looking for by looking at the bit ranges rather than parsing the display format of snpguest?

like read bytes in and parse the specific locations.

Quick AI gave me this:

import struct

def extract_tcb_values(report_filepath):
    # Dictionary mapping the TCB names to their byte offset in the specification
    # Each TCB version field is 64 bits (8 bytes) long
    tcb_offsets = {
        "CURRENT_TCB": 0x38,
        "REPORTED_TCB": 0x180,
        "COMMITTED_TCB": 0x1E0,
        "LAUNCH_TCB": 0x1F0
    }
    
    tcb_results = {}
    
    with open(report_filepath, 'rb') as f:
        for name, offset in tcb_offsets.items():
            # Seek directly to the byte location
            f.seek(offset)
            
            # Read exactly 8 bytes (64 bits)
            byte_data = f.read(8)
            
            if len(byte_data) < 8:
                tcb_results[name] = "Error: Out of bounds / File too short"
                continue
            
            # Unpack as an unsigned 64-bit integer (<Q for Little-Endian, >Q for Big-Endian)
            # x86 platform hardware structures typically use Little-Endian format
            tcb_value = struct.unpack('<Q', byte_data)[0]
            tcb_results[name] = tcb_value
            
    return tcb_results

def decode_tcb_components(tcb_int):
    # Convert the 64-bit integer back to 8 raw bytes
    tcb_bytes = struct.pack('<Q', tcb_int)
    
    # AMD SEV-SNP maps these to individual security version numbers (SVNs)
    return {
        "Microcode SVN": tcb_bytes[0],
        "SNP SVN":       tcb_bytes[1],
        "TEE SVN":       tcb_bytes[2],
        "Bootloader SVN":tcb_bytes[3],
        # Bytes 4-7 are typically reserved or specific platform SVNs
        "Raw Bytes":     list(tcb_bytes)
    }

Not mandatory but I feel this type of data works easier with bytes, but also python is not created to work with bytes. So both approaches are fine with me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I remembered this comment, but thought it was on my ID block PR. No problem, they should both use the same approach. I'll either split out the binary parser (from ID block PR) into a new PR or if ID block merges, rebase and use the binary parser. I do NOT want to "stack" any PRs. :)

Comment on lines +281 to +285
if report_info.version is not None:
version_note = (
"" if report_info.version == EXPECTED_REPORT_VERSION
else f" *** UNEXPECTED (expected {EXPECTED_REPORT_VERSION}) —"
f" TCB layout assumptions may not apply ***"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh I see your logic is that the TCB changes according to your report version.

The TCB really changes according to your CPU as I noted before. The only way of being able to tell what CPU you're running on (from a guest perspective) is through the attestation report family/model/stepping fields, and those are only present on reports v3+. I think we can fix up this TCB logic up a bit on the new harness, it should be easier to extract some of this information.



def test_root_key_difference() -> bool:
"""Test that different root keys (VCEK vs VMRK) produce different keys."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is just out of my own curiosity, but did you find out if there is a way of generating, or finding the vmrk? I know we can request vcek from kds, but I have no idea where the vmrk lives, or if there is ever a situation where you want to generate it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure. It's migration-related, right? If I had to guess, I'd say a more or less dedicated FW API for it...?

Comment on lines +356 to +358
def test_root_key_difference() -> bool:
"""Test that different root keys (VCEK vs VMRK) produce different keys."""
key_vck_file = KEY_DERIVATION_DIR / "vck_key.bin"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@markg-github I know that you meant vcek ignore this comment.

Comment on lines +456 to +460
if len(svn_values) < 2:
print(" Only one valid SVN value (0); sensitivity cannot be tested.")
print(" (Expected when guest was launched without an ID block.)")
print("✓ PASS: N/A (single valid value)")
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If guest svn is 0, could you at least test that 1 fails? I don't know if that's how it works. But maybe if svn is 0, then try to derive with 1 and expect a failure. And then only test ranges if svn is > 0.

@markg-github markg-github Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried to generalize your idea here, @DGonzalezVillal , in updated version(s) of the PR.

Copilot AI review requested due to automatic review settings July 13, 2026 18:27

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Comment on lines +23 to +27
# ── Level 3.0.0-2 ───────────────────────────────────────────

[[tests]]
name = "key-derivation"
description = "Launch SEV-SNP guest and run key derivation tests"
Comment on lines +483 to +487
for svn in svn_values:
key_file = KEY_DERIVATION_DIR / f"svn{svn}_key.bin"
if not derive_key(key_file, root_key="vcek", vmpl=0, guest_svn=svn,
guest_field_select=1 << 4, expected_failure=True):
failed_svns.append(svn)
Comment on lines +569 to +573
for tcb_u64 in candidates:
key_file = KEY_DERIVATION_DIR / f"tcb_{tcb_u64:016x}_key.bin"
if not derive_key(key_file, root_key="vcek", vmpl=0, tcb_version=tcb_u64,
guest_field_select=1 << 5, expected_failure=True):
failed_tcbs.append(tcb_u64)
Comment on lines +120 to +123
key_derivation_service = "key-derivation.service"
key_derivation_cmd = f"journalctl -D {self.guest_logs_path} -u {key_derivation_service} -o cat"
result = subprocess.run(key_derivation_cmd, shell=True, text=True, capture_output=True)

Copilot AI review requested due to automatic review settings July 13, 2026 18:34

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.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.

Comment on lines 48 to 52
Step.for_host(
name="snphost ok",
type="required",
type="info",
command="snphost ok",
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure I agree with changing this to info... or at least, a fail here shouldn't grant a milestone/pass even if everything else passes.

You added this b/c your machine was failing the snphost alias check right? I briefly asked @DGonzalezVillal and @amd-kkin if we should move the alias check into a --mitigations flag or something (along with other cve checks in the future). Maybe that would help here and more reason to keep it required.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I asked mark to move it back to required, but do you think info is fine for snphost ok check? I think we need to introduce the fix to snphost ok, but keep this particular step as required.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah I would prefer to keep it as required for now

@markg-github markg-github Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm fine with changing it back to required and I did so. (I also updated my system's TCB enough to avoid the failure.)

fwiw, it seems that the alias check issue/mitigation at play here is different, if not entirely unique, in that the mitigation isn't effective if loaded at runtime. That said, it's not intuitive to me that missing "reboot required" mitigations like this should be considered failures while missing "runtime okay" mitigations aren't.

Comment thread modules/build/guest/mkosi.conf Outdated
Comment on lines +11 to +14
[Content]
KernelCommandLine=console=ttyS0
Packages=
python3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't think it overwrites like copilot says, but at this point not needed anymore since we added python3 for the sev_verify harness already (@DGonzalezVillal's vsock change), so guests already have python3 (unless you're trying to deduplicate, but then I think that can be its own PR?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think if it's named differently it might cause issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll remove and update the PR again after testing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +485 to +487
if not derive_key(key_file, root_key="vcek", vmpl=0, guest_svn=svn,
guest_field_select=1 << 4, expected_failure=True):
failed_svns.append(svn)
Comment on lines +571 to +573
if not derive_key(key_file, root_key="vcek", vmpl=0, tcb_version=tcb_u64,
guest_field_select=1 << 5, expected_failure=True):
failed_tcbs.append(tcb_u64)
Comment on lines +161 to +165
# key-derivation.service may finish after other services and miss the journal
# upload window, so ensure it is always included even if discovery missed it.
if "key-derivation.service" not in snpguest_services_list:
snpguest_services_list.append("key-derivation.service")

@markg-github markg-github mentioned this pull request Jul 16, 2026

@amd-aliem amd-aliem left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed only for structure, it currently has a lot of extra files from the legacy systemd modules - if you could please rebase against main and clean that up would make it a lot shorter.

Will be doing another review on the test script and content tomorrow!

Comment thread modules/build/guest/mkosi.conf Outdated
Comment on lines +11 to +14
[Content]
KernelCommandLine=console=ttyS0
Packages=
python3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't think it overwrites like copilot says, but at this point not needed anymore since we added python3 for the sev_verify harness already (@DGonzalezVillal's vsock change), so guests already have python3 (unless you're trying to deduplicate, but then I think that can be its own PR?)

@@ -0,0 +1,871 @@
#!/usr/bin/env python3
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

imo this script should be co-located with the module in sev_verify

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thinking about this more, it may be harder than I thought since we don't have a 'file push' for vsock yet. I'm ok with keeping it here for now - but really would like to discuss the convention for future tests and potentially move this script to sev_verify in the future.

Reasons I would prefer to NOT add it to the guest image:

  • harder to test changes (whole image build for any change to this script)
  • image will get larger unnecessarily, esp if we allow multiple VM launches. Bring up a VM to test 4.0 will have extra data from all previous tests.

@DGonzalezVillal maybe a new step type for "run this script on the VM" that bundles together pushing a script and then executing it?

@markg-github markg-github Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I addressed by adding the following to the main test file, key_derivation_test.py:

from sev_verify.guest_vsock import GuestCommandError, fetch_guest_file_bytes, run_guest_command

and using GuestCommandError, fetch_guest_file_bytes and run_guest_command directly.

To the point raised by @amd-aliem and @DGonzalezVillal , I agree that it would be better if sev_verify itself supported "dynamic tests" like this better.

@@ -0,0 +1,18 @@
[Unit]
Description=Run SNP Key Derivation Tests after boot

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is this a service? I see you're already using the .for_guest() step type, which is what I think would launch it. I think this file should be removed unless I'm missing something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread modules/test/guest/mkosi.conf
description = "Launch SEV-SNP guest and run key derivation tests"
module = "cert_tests.c3_0.c3_0_0_2.key_derivation_test"
scope = "mixed"
level = "3.0.0-2"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe we were having this under 3.0.0-1 (released together with snp config commit?), not one test per -X level

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No problem. I'll change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 48 to 52
Step.for_host(
name="snphost ok",
type="required",
type="info",
command="snphost ok",
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure I agree with changing this to info... or at least, a fail here shouldn't grant a milestone/pass even if everything else passes.

You added this b/c your machine was failing the snphost alias check right? I briefly asked @DGonzalezVillal and @amd-kkin if we should move the alias check into a --mitigations flag or something (along with other cve checks in the future). Maybe that would help here and more reason to keep it required.

Copilot AI review requested due to automatic review settings July 31, 2026 14:49

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.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (5)

sev_verify/cert_tests/common/snp_ok.py:52

  • snp_ok is invoked from cert_tests/common/prereqs.toml (prereqs are intended to gate the certification run), but changing snphost ok to type="info" means the CLI will not mark the overall test/certification as failed when this step fails (only setup/required affect overall; see sev_verify/cli.py:467-476). This can allow later tests to run on a host where SNP isn't functional.
        Step.for_host(
            name="snphost ok",
            type="info",
            command="snphost ok",
        ),

sev_verify/cert_tests/c3_0/manifest.toml:27

  • This manifest adds a new level 3.0.0-2, but max_certification_level is still set to 3.0.0-0. The runner clamps the reported highest certified level to that cap even if higher levels pass (sev_verify/cli.py:_highest_certified_level, lines 557-584), so passing key-derivation will never result in a certified level above 3.0.0-0.
# ── Level 3.0.0-2 ───────────────────────────────────────────

[[tests]]
name = "key-derivation"
description = "Launch SEV-SNP guest and run key derivation tests"

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:80

  • ReportInfo.committed_tcb is annotated as TcbVersion but initialized with None and then patched in __post_init__. This defeats type checking and makes it easy to introduce None bugs when refactoring. Make the annotation explicitly optional (or use a default factory).
@dataclass
class ReportInfo:
    version: Optional[int] = None
    guest_svn: int = 0
    committed_tcb: TcbVersion = None

    def __post_init__(self):
        if self.committed_tcb is None:
            self.committed_tcb = TcbVersion()

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:206

  • test_determinism can incorrectly pass if both key reads fail: _read_key returns None on error, and None == None evaluates true. Explicitly fail when either derived key file can't be read.
    k1 = _read_key(ctx.artifact_dir / "det_key1.bin")
    k2 = _read_key(ctx.artifact_dir / "det_key2.bin")
    if k1 == k2:
        return StepHandlerResult(exit_code=0, stdout="Keys match (deterministic)")
    return StepHandlerResult(exit_code=1, stderr="Keys differ — derivation is not deterministic")

modules/build/guest/mkosi.conf:14

  • Adding python3 to the shared modules/build/guest module is redundant with the per-image guest configs already listing python3 (e.g. images/guest-ubuntu-26.04/mkosi.conf:11-25). Keeping it here makes it easy to accidentally diverge package sets across images (or later forget where python3 is coming from).
[Content]
KernelCommandLine=console=ttyS0
Packages=
	python3

@markg-github

Copy link
Copy Markdown
Contributor Author

I changed to use the generic guest image. The message from the one new commit:

feat: move key derivation test logic to host, use vsock for guest commands

Replace the guest-side Python script approach with individual snpguest
key commands over vsock, pulling derived keys to the host for comparison.
All test logic (determinism, VMPL isolation, root key difference, SVN/TCB
bound enforcement, GFS sensitivity and field mixing) now lives in the
sev_verify test module as callable steps.

Use callable steps that loop over snpguest key via
run_guest_command/fetch_guest_file_bytes while the VM is running. SVN
and TCB sweeps are fully dynamic based on the runtime attestation report.

TCB above-bound tests use committed+1..committed+3 per component from
the runtime report, capped at 0xFF to avoid 8-bit overflow.

Remove key-derivation guest module. The guest-side Python script and systemd
service are no longer needed and snpguest is available in the generic guest
image.

@DGonzalezVillal DGonzalezVillal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Need to extend the TCB definition for Turin vs Genoa and consider Venice one will look different as well. If need be we can chat on ways to do this.

Comment thread modules/build/guest/mkosi.conf Outdated
Comment on lines +11 to +14
[Content]
KernelCommandLine=console=ttyS0
Packages=
python3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think if it's named differently it might cause issues.

Comment on lines +56 to +68
class TcbVersion:
boot_loader: int = 0
tee: int = 0
snp: int = 0
microcode: int = 0

def to_u64(self) -> int:
return (
(self.boot_loader & 0xFF) |
((self.tee & 0xFF) << 8) |
((self.snp & 0xFF) << 48) |
((self.microcode & 0xFF) << 56)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your TCB generation still needs updating for Turin version (I'll allow no Venice for now)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ACK

Comment thread sev_verify/cert_tests/common/snp_ok.py Outdated
Step.for_host(
name="snphost ok",
type="required",
type="info",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
type="info",
type="required",

Comment on lines 48 to 52
Step.for_host(
name="snphost ok",
type="required",
type="info",
command="snphost ok",
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I asked mark to move it back to required, but do you think info is fine for snphost ok check? I think we need to introduce the fix to snphost ok, but keep this particular step as required.



def test_vmpl_isolation(ctx: StepContext) -> StepHandlerResult:
ok0, err0 = _derive_key(ctx, "vmpl0_key.bin", vmpl=0)

@amd-aliem amd-aliem Aug 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For forwards compatibility should we NOT hard-code vmpl=0? In the fuure possibly distros may have svsm enabled by default so VMs would be on vmpl 1 right? Could check current level first and then increment by 1 perhaps. Also ok with leaving this for whenever we run into it.

@markg-github markg-github Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point.

cf9fe4b

@markg-github
markg-github marked this pull request as draft August 20, 2026 19:03
Always verify that values above the active bound are rejected, even
when the bound is 0 (no ID block). Tests 3 values above the bound
for both guest SVN and each TCB component.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 20, 2026 19:09

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.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (9)

sev_verify/cert_tests/c3_0/manifest.toml:26

  • This manifest still caps the certification at max_certification_level="3.0.0-0" (line 3), so even if level 3.0.0-2 runs and passes, the computed highest certified level will be clamped to 3.0.0-0. If this test is intended to be part of the 3.0 track, the cap should be bumped to include 3.0.0-2.

# ── Level 3.0.0-2 ───────────────────────────────────────────

[[tests]]
name = "key-derivation"

sev_verify/cert_tests/common/snp_ok.py:50

  • Changing this step to type="info" means a failing snphost ok no longer fails or aborts the test run (only "setup"/"required" failures affect the overall result in sev_verify/cli.py). That can allow certification to proceed on a host that cannot run SNP guests.
            type="info",

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:220

  • A read failure returning None can cause a false pass here (e.g., None != evaluates true). Treat unreadable key files as a test failure before comparing values.
    k0 = _read_key(ctx.artifact_dir / "vmpl0_key.bin")
    k1 = _read_key(ctx.artifact_dir / "vmpl1_key.bin")
    if k0 != k1:

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:232

  • A read failure returning None can cause a false pass here (e.g., None != evaluates true). Fail the step if either key file can't be read.
    kv = _read_key(ctx.artifact_dir / "vcek_key.bin")
    km = _read_key(ctx.artifact_dir / "vmrk_key.bin")
    if kv != km:

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:353

  • A read failure returning None can cause a false pass here (e.g., None != evaluates true). Fail the step if either derived key can't be read before comparing.
    k1 = _read_key(ctx.artifact_dir / "gfs1_key.bin")
    k2 = _read_key(ctx.artifact_dir / "gfs2_key.bin")
    if k1 != k2:

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:363

  • If reading the baseline key fails (baseline=None), comparisons can produce misleading passes/failures. Treat an unreadable baseline derived key as a hard failure.
    ok, err = _derive_key(ctx, "gfs_baseline.bin", gfs=0)
    if not ok:
        return StepHandlerResult(exit_code=1, stderr=f"Baseline derivation failed: {err}")
    baseline = _read_key(ctx.artifact_dir / "gfs_baseline.bin")

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:384

  • If a per-bit derived key can't be read (k=None), the current k != baseline check will treat it as “differs from baseline” and incorrectly pass that bit. Handle unreadable key files explicitly as failures.
        k = _read_key(ctx.artifact_dir / f"gfs_bit{bit}.bin")
        if k != baseline:
            lines.append(f"GFS bit {bit} ({label}): differs from baseline")
        else:
            failed.append(f"GFS bit {bit} ({label}): same as baseline")

modules/build/guest/mkosi.conf:14

  • All existing guest image templates already include python3 in their own images/guest-*/mkosi.conf package lists. Adding it again here in the shared module is redundant and can be confusing about where guest package requirements are defined (docs/how-to-add-new-os-images.md shows packages living in the per-image templates).
[Content]
KernelCommandLine=console=ttyS0
Packages=
	python3

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:203

  • If _read_key() fails and returns None for both reads, this comparison will incorrectly pass as “deterministic”. Make the test fail when either derived key cannot be read.

This issue also appears in the following locations of the same file:

  • line 218
  • line 230
  • line 351
  • line 360
  • line 380
    k1 = _read_key(ctx.artifact_dir / "det_key1.bin")
    k2 = _read_key(ctx.artifact_dir / "det_key2.bin")
    if k1 == k2:

@markg-github
markg-github marked this pull request as ready for review August 24, 2026 14:16
@markg-github

markg-github commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Add so-called cross-CVM (sub) test: launch a CVM - save a derived key - stop the CVM - launch a CVM - verify ability to generate the same key.

This version of the test was in a 2nd, draft PR (#285) --> decided no need to split up the test across two PRs and to close the 2nd PR.

markg-github and others added 3 commits August 24, 2026 10:14
Import calculate_measurement from attestation_test instead of
duplicating it. Conditionally add generate_id_block step when
sev_verify.id_block is available.

Co-Authored-By: Claude <noreply@anthropic.com>
…mands

Replace the guest-side Python script approach with individual snpguest
key commands over vsock, pulling derived keys to the host for comparison.
All test logic (determinism, VMPL isolation, root key difference, SVN/TCB
bound enforcement, GFS sensitivity and field mixing) now lives in the
sev_verify test module as callable steps.

Use callable steps that loop over snpguest key via
run_guest_command/fetch_guest_file_bytes while the VM is running. SVN
and TCB sweeps are fully dynamic based on the runtime attestation report.

TCB above-bound tests use committed+1..committed+3 per component from
the runtime report, capped at 0xFF to avoid 8-bit overflow.

Remove key-derivation guest module. The guest-side Python script and systemd
service are no longer needed and snpguest is available in the generic guest
image.

Co-Authored-By: Claude <noreply@anthropic.com>
Derives a key at the end of CVM 1, stops it, launches CVM 2, derives
the same key with identical parameters, and compares. Proves the key
is bound to platform identity rather than transient VM state.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 24, 2026 15:16

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.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:303

  • The TCB above-bound test builds tcb_u64 with only the tested component set and all other components zero. If the firmware’s committed-bound check is lexicographic / holistic, this can create a TCB value that is not actually above the committed bound (because other components are lowered), producing false results. Construct the test TCB from the committed TCB and bump only the component under test.
        for val in above_vals:
            tcb_u64 = TcbVersion(**{comp: val}).to_u64()
            ok, _ = _derive_key(ctx, f"tcb_above_{comp}_{val}.bin",
                                tcb=tcb_u64, gfs=1 << 5)
            if ok:

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:327

  • The TCB sensitivity loop currently computes tcb_u64 with only the varied component set and all other components zero. This doesn’t isolate per-component sensitivity relative to the committed TCB and can also change the overall TCB value in unintended ways. Use the committed TCB as the baseline and vary one component at a time.
            tcb_u64 = TcbVersion(**{comp: val}).to_u64()
            if tcb_u64 in keys:
                continue  # already derived this exact TCB value
            fname = f"tcb_{comp}_{val}.bin"
            ok, err = _derive_key(ctx, fname, tcb=tcb_u64, gfs=1 << 5)

Comment thread modules/build/guest/mkosi.conf Outdated
Comment on lines +11 to +14
[Content]
KernelCommandLine=console=ttyS0
Packages=
python3
Comment on lines +201 to +205
k1 = _read_key(ctx.artifact_dir / "det_key1.bin")
k2 = _read_key(ctx.artifact_dir / "det_key2.bin")
if k1 == k2:
return StepHandlerResult(exit_code=0, stdout="Keys match (deterministic)")
return StepHandlerResult(exit_code=1, stderr="Keys differ — derivation is not deterministic")
Signed-off-by: Mark Gentry <markg.inspire@gmail.com>
… level 3.0.0-1

Signed-off-by: Mark Gentry <markg.inspire@gmail.com>
Copilot AI review requested due to automatic review settings August 24, 2026 21:23

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.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

Comment on lines +23 to +28
[[tests]]
name = "key-derivation"
description = "Launch SEV-SNP guest and run key derivation tests"
module = "cert_tests.c3_0.c3_0_0_2.key_derivation_test"
scope = "mixed"
level = "3.0.0-1"
Comment on lines +142 to +146
def _read_key(path: Path) -> Optional[bytes]:
try:
return path.read_bytes()
except Exception:
return None
Copilot AI review requested due to automatic review settings August 27, 2026 20:55

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.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (6)

Previously missed (4) — in code that hasn't changed since the last review.

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:80

  • ReportInfo.committed_tcb is annotated as TcbVersion but defaulted to None. Even though __post_init__ fills it, this is inconsistent and can trip type checkers / readers into thinking None is a valid runtime value. Annotate it as optional (or use a default factory) to match the actual initialization pattern.
class ReportInfo:
    version: Optional[int] = None
    guest_svn: int = 0
    vmpl: int = 0
    committed_tcb: TcbVersion = None

    def __post_init__(self):
        if self.committed_tcb is None:
            self.committed_tcb = TcbVersion()

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:157

  • subprocess.run() in parse_report() has no timeout. If snpguest display report hangs (e.g., corrupted report file or tool bug), the whole harness can stall indefinitely. Add a reasonable timeout consistent with other host-side subprocess calls.

This issue also appears on line 520 of the same file.

    result = subprocess.run(
        ["snpguest", "display", "report", str(report_file)],
        capture_output=True, text=True, check=False,
    )

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:213

  • test_determinism() treats None keys as comparable values. If both reads fail and return None, the test will incorrectly pass (None == None). Fail the step when either derived key cannot be read.

This issue also appears on line 376 of the same file.

    k1 = _read_key(ctx.artifact_dir / "det_key1.bin")
    k2 = _read_key(ctx.artifact_dir / "det_key2.bin")
    if k1 == k2:
        return StepHandlerResult(exit_code=0, stdout="Keys match (deterministic)")
    return StepHandlerResult(exit_code=1, stderr="Keys differ — derivation is not deterministic")

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:353

  • test_tcb() returns success when fewer than 2 keys were derived, even if that happened due to unexpected rejections or other failures. Only treat this as N/A when the committed TCB is actually all zeros; otherwise this should fail the test to surface a real regression.
    if len(keys) < 2:
        lines.append("TCB sensitivity N/A — all committed components are zero")
        return StepHandlerResult(exit_code=0, stdout="\n".join(lines))

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:380

  • test_gfs_field_mixing() doesn't validate that the baseline key was actually read. If _read_key() returns None, every per-bit key will appear to "differ" and the test can pass incorrectly. Fail early if the baseline key cannot be read.
    ok, err = _derive_key(ctx, "gfs_baseline.bin", gfs=0)
    if not ok:
        return StepHandlerResult(exit_code=1, stderr=f"Baseline derivation failed: {err}")
    baseline = _read_key(ctx.artifact_dir / "gfs_baseline.bin")

sev_verify/cert_tests/c3_0/c3_0_0_2/key_derivation_test.py:524

  • test_tcb can perform up to 1021 snpguest key invocations in the worst case (4 components * 256 values minus dedup). With a step timeout of 120s, it’s easy for this to time out on slower hosts. Consider increasing the step timeout to match the possible workload (or sampling the value range).
        Step.for_callable(
            name="Test TCB bound enforcement and sensitivity",
            type="required",
            handler="test_tcb",
            timeout=120,

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.

key derivation test

4 participants