cvm-attestation/snp.py (347 lines of code) (raw):
import struct
from typing import List
class Signature:
def __init__(self, r_component=None, s_component=None, reserved=None):
self.r_component = r_component or [0] * 72 # RComponent[72]
self.s_component = s_component or [0] * 72 # SComponent[72]
self.reserved = reserved or [0] * 368 # RSVD[368]
def serialize(self):
return struct.pack(
'<72s72s368s',
bytes(self.r_component),
bytes(self.s_component),
bytes(self.reserved)
)
@classmethod
def deserialize(cls, data):
unpacked_data = struct.unpack('<72s72s368s', data)
return cls(
list(unpacked_data[0]),
list(unpacked_data[1]),
list(unpacked_data[2])
)
class TcbVersion:
def __init__(self, bootloader=0, tee=0, reserved=0, snp=0, microcode=0):
self.bootloader = bootloader
self.tee = tee
self.reserved = reserved
self.snp = snp
self.microcode = microcode
def serialize(self):
return struct.pack(
'<Q', # Little-endian unsigned 64-bit integer
(self.bootloader)
| (self.tee << 8)
| (self.reserved << 16)
| (self.snp << 48)
| (self.microcode << 56)
)
@classmethod
def deserialize(cls, data):
unpacked_data = struct.unpack('<Q', data)[0] # Little-endian unsigned 64-bit integer
return cls(
bootloader=unpacked_data & 0xFF,
tee=(unpacked_data >> 8) & 0xFF,
reserved=(unpacked_data >> 16) & 0xFFFFFFFF,
snp=(unpacked_data >> 48) & 0xFF,
microcode=(unpacked_data >> 56) & 0xFF
)
class PlatformInfo:
def __init__(self, smt_enabled=0, tsme_enabled=0, ecc_enabled=0, rapl_disabled=0, ciphertext_hiding_enabled=0, reserved=0):
self.smt_enabled = smt_enabled
self.tsme_enabled = tsme_enabled
self.ecc_enabled = ecc_enabled
self.rapl_disabled = rapl_disabled
self.ciphertext_hiding_enabled = ciphertext_hiding_enabled
self.reserved = reserved
def serialize(self):
value = (
(self.reserved << 5)
| (self.ciphertext_hiding_enabled << 4)
| (self.rapl_disabled << 3)
| (self.ecc_enabled << 2)
| (self.tsme_enabled << 1)
| self.smt_enabled
)
return struct.pack('<Q', value) # Little-endian unsigned 64-bit integer
@classmethod
def deserialize(cls, data):
unpacked_data = struct.unpack('<Q', data)[0] # Little-endian unsigned 64-bit integer
return cls(
smt_enabled=unpacked_data & 0x1,
tsme_enabled=(unpacked_data >> 1) & 0x1,
ecc_enabled=(unpacked_data >> 2) & 0x1,
rapl_disabled=(unpacked_data >> 3) & 0x1,
ciphertext_hiding_enabled=(unpacked_data >> 4) & 0x1,
reserved=(unpacked_data >> 5) & ((1 << 59) - 1) # Mask bits 5 to 63
)
class KeyInfo:
def __init__(self, author_key_en=0, mask_chip_key=0, signing_key=0, reserved=0):
"""
Initialize the KeyInfo structure.
"""
self.author_key_en = author_key_en # Bit 0
self.mask_chip_key = mask_chip_key # Bit 1
self.signing_key = signing_key # Bits 2–4
self.reserved = reserved # Bits 5–31
def serialize(self):
"""
Serialize the KeyInfo structure into a 4-byte little-endian integer.
"""
return struct.pack(
'<I',
(self.reserved << 5) |
(self.signing_key << 2) |
(self.mask_chip_key << 1) |
self.author_key_en
)
@classmethod
def deserialize(cls, data):
"""
Deserialize a 4-byte little-endian integer into a KeyInfo instance.
"""
value = struct.unpack('<I', data)[0]
return cls(
author_key_en=value & 0x1,
mask_chip_key=(value >> 1) & 0x1,
signing_key=(value >> 2) & 0x7,
reserved=(value >> 5) & 0x7FFFFFF
)
class AttestationReport:
"""
Defines an AttestationReport class from the SEV-SNP attestation report.
Source: https://github.com/virtee/sev/blob/f9c6abe7b021cdf9e5c03c56169d4d479fc64464/src/firmware/guest/types/snp.rs#L127C1-L127C31.
"""
# Define the structure's format string as a class-level constant
FORMAT_STRING = (
'<IIQ16s16sII' # version, guest_svn, policy, family_id, image_id, vmpl, sig_algo
'Q' # current_tcb (8 bytes)
'Q' # plat_info (8 bytes)
'I' # key_info (4 bytes)
'I' # _reserved_0 (4 bytes)
'64s' # report_data (64 bytes)
'48s' # measurement (48 bytes)
'32s' # host_data (32 bytes)
'48s' # id_key_digest (48 bytes)
'48s' # author_key_digest (48 bytes)
'32s' # report_id (32 bytes)
'32s' # report_id_ma (32 bytes)
'Q' # reported_tcb (8 bytes)
'24s' # _reserved_1 (24 bytes)
'64s' # chip_id (64 bytes)
'Q' # committed_tcb (8 bytes)
'BBBB' # current_build, current_minor, current_major, _reserved_2
'BBBB' # committed_build, committed_minor, committed_major, _reserved_3
'Q' # launch_tcb (8 bytes)
'168s' # _reserved_4 (168 bytes)
'512s' # signature (512 bytes)
)
@classmethod
def calculate_size(cls):
"""
Calculate the total size of the class based on its format string.
"""
return struct.calcsize(cls.FORMAT_STRING)
def __init__(self):
self.version = 0
self.guest_svn = 0
self.policy = 0
self.family_id = [0] * 16
self.image_id = [0] * 16
self.vmpl = 0
self.sig_algo = 0
self.current_tcb = TcbVersion()
self.plat_info = PlatformInfo()
self.key_info = KeyInfo()
self._reserved_0 = 0
self.report_data = [0] * 64
self.measurement = [0] * 48
self.host_data = [0] * 32
self.id_key_digest = [0] * 48
self.author_key_digest = [0] * 48
self.report_id = [0] * 32
self.report_id_ma = [0] * 32
self.reported_tcb = TcbVersion()
self._reserved_1 = [0] * 24
self.chip_id = [0] * 64
self.committed_tcb = TcbVersion()
self.current_build = 0
self.current_minor = 0
self.current_major = 0
self._reserved_2 = 0
self.committed_build = 0
self.committed_minor = 0
self.committed_major = 0
self._reserved_3 = 0
self.launch_tcb = TcbVersion()
self._reserved_4 = [0] * 168
self.signature = Signature()
def serialize(self):
"""
Serializes an AttestationReport instance into a binary blob.
"""
print('here', type(self.current_tcb.serialize()))
return struct.pack(
self.FORMAT_STRING,
self.version, # int
self.guest_svn, # int
self.policy, # int or uint64_t
bytes(self.family_id), # bytes
bytes(self.image_id), # bytes
self.vmpl, # int
self.sig_algo, # int
int.from_bytes(self.current_tcb.serialize(), byteorder='little'), # Should return bytes or int (Q)
int.from_bytes(self.plat_info.serialize(), byteorder='little'), # Serialize PlatformInfo to int (Q)
int.from_bytes(self.key_info.serialize(), byteorder='little'), # Serialize KeyInfo to int (I)
self._reserved_0, # int
bytes(self.report_data), # bytes
bytes(self.measurement), # bytes
bytes(self.host_data), # bytes
bytes(self.id_key_digest), # bytes
bytes(self.author_key_digest), # bytes
bytes(self.report_id), # bytes
bytes(self.report_id_ma), # bytes
int.from_bytes(self.reported_tcb.serialize(), byteorder='little'), # Should return bytes or int (Q)
bytes(self._reserved_1), # bytes
bytes(self.chip_id), # bytes
int.from_bytes(self.committed_tcb.serialize(), byteorder='little'), # Should return bytes or int (Q)
self.current_build, # int
self.current_minor, # int
self.current_major, # int
self._reserved_2, # int
self.committed_build, # int
self.committed_minor, # int
self.committed_major, # int
self._reserved_3, # int
int.from_bytes(self.launch_tcb.serialize(), byteorder='little'), # Should return bytes or int (Q)
bytes(self._reserved_4), # bytes
self.signature.serialize() # Should return bytes (512 bytes)
)
@classmethod
def deserialize(cls, data):
"""
Deserialize a binary blob into an instance of the AttestationReport class.
"""
unpacked_data = struct.unpack(cls.FORMAT_STRING, data)
instance = cls()
instance.version = unpacked_data[0]
instance.guest_svn = unpacked_data[1]
instance.policy = unpacked_data[2]
instance.family_id = list(unpacked_data[3])
instance.image_id = list(unpacked_data[4])
instance.vmpl = unpacked_data[5]
instance.sig_algo = unpacked_data[6]
instance.current_tcb = TcbVersion.deserialize(struct.pack('<Q', unpacked_data[7]))
instance.plat_info = PlatformInfo.deserialize(struct.pack('<Q', unpacked_data[8]))
instance.key_info = KeyInfo.deserialize(struct.pack('<I', unpacked_data[9]))
instance._reserved_0 = unpacked_data[10]
instance.report_data = list(unpacked_data[11])
instance.measurement = list(unpacked_data[12])
instance.host_data = list(unpacked_data[13])
instance.id_key_digest = list(unpacked_data[14])
instance.author_key_digest = list(unpacked_data[15])
instance.report_id = list(unpacked_data[16])
instance.report_id_ma = list(unpacked_data[17])
instance.reported_tcb = TcbVersion.deserialize(struct.pack('<Q', unpacked_data[18]))
instance._reserved_1 = list(unpacked_data[19])
instance.chip_id = list(unpacked_data[20])
instance.committed_tcb = TcbVersion.deserialize(struct.pack('<Q', unpacked_data[21]))
instance.current_build = unpacked_data[22]
instance.current_minor = unpacked_data[23]
instance.current_major = unpacked_data[24]
instance._reserved_2 = unpacked_data[25]
instance.committed_build = unpacked_data[26]
instance.committed_minor = unpacked_data[27]
instance.committed_major = unpacked_data[28]
instance._reserved_3 = unpacked_data[29]
instance.launch_tcb = TcbVersion.deserialize(struct.pack('<Q', unpacked_data[30]))
instance._reserved_4 = list(unpacked_data[31])
instance.signature = Signature.deserialize(unpacked_data[32])
return instance
def format_data(self, data):
"""
Format the report data into a multi-line hex output, with 16 bytes per line.
"""
lines = []
for i in range(0, len(data), 16):
chunk = data[i:i+16]
lines.append(" ".join(f"{byte:02x}" for byte in chunk)) # Format bytes as hex
return "\n".join(lines)
def display(self):
"""
Displays the full attestation report
"""
print(f"Attestation Report ({self.calculate_size()} bytes):")
print(f"Version: {self.version}")
print(f"Guest SVN: {self.guest_svn}")
print(f"\nGuest Policy (0x{self.policy:x}):")
print(f" ABI Major: {(self.policy >> 8) & 0xff}")
print(f" ABI Minor: {(self.policy >> 0) & 0xff}")
print(f" SMT Allowed: {(self.policy >> 16) & 0x1}")
print(f" Migrate MA: {(self.policy >> 18) & 0x1}")
print(f" Debug Allowed: {(self.policy >> 19) & 0x1}")
print(f" Single Socket: {(self.policy >> 20) & 0x1}")
print()
print("Family ID: ")
for byte in self.family_id:
print(f"{byte:02x} ", end='')
print()
print()
print("Image ID: ")
for byte in self.image_id:
print(f"{byte:02x} ", end='')
print()
print()
print(f"VMPL: {self.vmpl}")
print(f"Signature Algorithm: {self.sig_algo}")
print()
current_tcb = self.current_tcb.serialize()
formatted_tcb = "".join(f"{byte:02X}" for byte in current_tcb[::-1])
print(f"Current TCB: {formatted_tcb}")
print(f" Microcode: {self.current_tcb.microcode} ({hex(self.current_tcb.microcode)[2:].upper()})")
print(f" SNP: {self.current_tcb.snp} ({hex(self.current_tcb.snp)[2:].upper()})")
print(f" Reserved: {self.current_tcb.reserved} ({hex(self.current_tcb.reserved)[2:].upper()})")
print(f" TEE: {self.current_tcb.tee} ({hex(self.current_tcb.tee)[2:].upper()})")
print(f" Boot Loader: {self.current_tcb.bootloader} ({hex(self.current_tcb.bootloader)[2:].upper()})")
print(f"\nPlatform Info (1):")
print(f" SMT Enabled: {self.plat_info.smt_enabled}")
print(f" TSME Enabled: {self.plat_info.tsme_enabled}")
print(f" ECC Enabled: {self.plat_info.ecc_enabled}")
print(f" RAPL Disabled: {self.plat_info.rapl_disabled}")
print(f" Ciphertext Hiding Enabled: {self.plat_info.ciphertext_hiding_enabled}")
print()
print(f"Author Key Encryption: {bool(self.key_info.author_key_en)}")
print("Report Data: ")
print(self.format_data(self.report_data))
print()
print("Measurement: ")
print(self.format_data(self.measurement))
print()
print("Host Data: ")
print(self.format_data(self.host_data))
print()
print("ID Key Digest: ")
print(self.format_data(self.id_key_digest))
print()
print("Report ID: ")
print(self.format_data(self.report_id))
print()
print("Report ID Migration Agent: ")
print(self.format_data(self.report_id_ma))
print()
reported_tcb = self.reported_tcb.serialize()
formatted_tcb = "".join(f"{byte:02X}" for byte in reported_tcb[::-1])
print(f"Reported TCB:")
print(f"TCB Version: {formatted_tcb}")
print(f" Microcode: {self.reported_tcb.microcode} ({hex(self.reported_tcb.microcode)[2:].upper()})")
print(f" SNP: {self.reported_tcb.snp} ({hex(self.reported_tcb.snp)[2:].upper()})")
print(f" Reserved: {self.reported_tcb.reserved} ({hex(self.reported_tcb.reserved)[2:].upper()})")
print(f" TEE: {self.reported_tcb.tee} ({hex(self.reported_tcb.tee)[2:].upper()})")
print(f" Boot Loader: {self.reported_tcb.bootloader} ({hex(self.reported_tcb.bootloader)[2:].upper()})")
print()
print("Chip ID: ")
print(self.format_data(self.chip_id))
print()
committed_tcb = self.committed_tcb.serialize()
formatted_tcb = "".join(f"{byte:02X}" for byte in committed_tcb[::-1])
print(f"Committed TCB: {formatted_tcb}")
print(f" Microcode: {self.committed_tcb.microcode} ({hex(self.committed_tcb.microcode)[2:].upper()})")
print(f" SNP: {self.committed_tcb.snp} ({hex(self.committed_tcb.snp)[2:].upper()})")
print(f" Reserved: {self.committed_tcb.reserved} ({hex(self.committed_tcb.reserved)[2:].upper()})")
print(f" TEE: {self.committed_tcb.tee} ({hex(self.committed_tcb.tee)[2:].upper()})")
print(f" Boot Loader: {self.committed_tcb.bootloader} ({hex(self.committed_tcb.bootloader)[2:].upper()})")
print()
print(f"Current Build: {self.current_build}")
print(f"Current Minor: {self.current_minor}")
print(f"Current Major: {self.current_major}")
print(f"Committed Build: {self.committed_build}")
print(f"Committed Minor: {self.committed_minor}")
print(f"Committed Major: {self.committed_major}")
print()
launch_tcb = self.launch_tcb.serialize()
formatted_tcb = "".join(f"{byte:02X}" for byte in launch_tcb[::-1])
print(f"Launched TCB: {formatted_tcb}")
print(f" Microcode: {self.launch_tcb.microcode} ({hex(self.launch_tcb.microcode)[2:].upper()})")
print(f" SNP: {self.launch_tcb.snp} ({hex(self.launch_tcb.snp)[2:].upper()})")
print(f" Reserved: {self.launch_tcb.reserved} ({hex(self.launch_tcb.reserved)[2:].upper()})")
print(f" TEE: {self.launch_tcb.tee} ({hex(self.launch_tcb.tee)[2:].upper()})")
print(f" Boot Loader: {self.launch_tcb.bootloader} ({hex(self.launch_tcb.bootloader)[2:].upper()})")
print()
print("Signature: ")
print(f" R Component:")
print(self.format_data(self.signature.r_component))
print()
print(f" S Component:")
print(self.format_data(self.signature.s_component))
print()