-
Notifications
You must be signed in to change notification settings - Fork 259
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add guest package for fetching attestation report via syscall
Add ioctl wrapper and structs required to fetch attestation report. Add a command line tool to fetch real and fake attestation reports. Fake attestation report can be used when testing integrations. Validate that LaunchData provided to HCS and HostData returned as part of attestation report match. Signed-off-by: Maksim An <[email protected]>
- Loading branch information
Showing
8 changed files
with
475 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//go:build linux | ||
// +build linux | ||
|
||
package snp | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"encoding/hex" | ||
) | ||
|
||
const fakeSNPReport = "01000000010000001f00030000000000010000000000000000000000000000000200000000000000000000000000000000000000010000000000000000000028010000000000000000000000000000007ab000a323b3c873f5b81bbe584e7c1a26bcf40dc27e00f8e0d144b1ed2d14f10000000000000000000000000000000000000000000000000000000000000000e29af700e85b39996fa38226d2804b78cad746ffef4477360a61b47874bdecd640f9d32f5ff64a55baad3c545484d9ed28603a3ea835a83bd688b0ec1dcb36b6b8c22412e5b63115b75db8628b989bc598c475ca5f7683e8d351e7e789a1baff19041750567161ad52bf0d152bd76d7c6f313d0a0fd72d0089692c18f521155800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040aea62690b08eb6d680392c9a9b3db56a9b3cc44083b9da31fb88bcfc493407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000028000000000000000000000000000000000000000000000000e6c86796cd44b0bc6b7c0d4fdab33e2807e14b5fc4538b3750921169d97bcf4447c7d3ab2a7c25f74c1641e2885c1011d025cc536f5c9a2504713136c7877f480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003131c0f3e7be5c6e400f22404596e1874381e99d03de45ef8b97eee0a0fa93a4911550330343f14dddbbd6c0db83744f000000000000000000000000000000000000000000000000db07c83c5e6162c2387f3b76cd547672657f6a5df99df98efee7c15349320d83e086c5003ec43050a9b18d1c39dedc340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" | ||
|
||
func fakeGetSNPReport() (snpReport, error) { | ||
reportBytes, err := hex.DecodeString(fakeSNPReport) | ||
if err != nil { | ||
return snpReport{}, err | ||
} | ||
|
||
report := snpReport{} | ||
if err := binary.Read(bytes.NewBuffer(reportBytes), binary.LittleEndian, &report); err != nil { | ||
return snpReport{}, err | ||
} | ||
return report, nil | ||
} | ||
|
||
func FetchFakeSNPReport(hostData string) (Report, error) { | ||
fakeRaw, err := fakeGetSNPReport() | ||
if err != nil { | ||
return Report{}, err | ||
} | ||
if hostData != "" { | ||
copy(fakeRaw.HostData[:], hostData) | ||
} | ||
return convertRawReport(&fakeRaw), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
//go:build linux | ||
// +build linux | ||
|
||
// package snp contains minimal functionality required to fetch | ||
// attestation reports inside an enlightened guest. | ||
package snp | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"encoding/hex" | ||
"fmt" | ||
"os" | ||
"unsafe" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
const ( | ||
MsgTypeInvalid = iota | ||
MsgCPUIDRequest | ||
MsgCPUIDResponse | ||
MsgKeyRequest | ||
MsgKeyResponse | ||
MsgReportRequest | ||
MsgReportResponse | ||
MsgExportRequest | ||
MsgExportResponse | ||
MsgImportRequest | ||
MsgImportResponse | ||
MsgAbsorbRequest | ||
MsgAbsorbResponse | ||
MsgVMRKRequest | ||
MsgVMRKResponse | ||
MsgTypeMax | ||
) | ||
|
||
type SevSNPGuestRequest struct { | ||
RequestMsgType byte | ||
ResponseMsgType byte | ||
MsgVersion byte | ||
RequestLength uint16 | ||
RequestUAddr uint64 | ||
ResponseLength uint16 | ||
ResponseUAddr uint64 | ||
Error uint32 | ||
} | ||
|
||
// Below is ported from Linux kernel code: | ||
// https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/include/uapi/asm-generic/ioctl.h | ||
const ( | ||
iocNone = 0 | ||
iocWrite = 1 | ||
iocRead = 2 | ||
iocNRBits = 8 | ||
iocTypeBits = 8 | ||
iocSizeBits = 14 | ||
iocDirBits = 2 | ||
|
||
// bit shifts | ||
iocNRShift = 0 | ||
iocTypeShift = iocNRBits | ||
iocSizeShift = iocTypeShift + iocTypeBits | ||
iocDirShift = iocSizeShift + iocSizeBits | ||
|
||
// SEV-SNP IOCTL type | ||
sevGuestType = 'S' | ||
// SEV-SNP IOCTL size, same as unsafe.Sizeof(SevSNPGuestRequest{}) | ||
sevGuestSize = 40 | ||
|
||
// SEV-SNP requests | ||
sevSNPReportCode = 0x1 | ||
) | ||
|
||
// _IOC macros equivalent | ||
func ioc(dir, t, nr, size int) int { | ||
return dir<<iocDirShift | t<<iocTypeShift | nr<<iocNRShift | size<<iocSizeShift | ||
} | ||
|
||
// _IOCWR macros equivalent | ||
func iocWR(t, nr, size int) int { | ||
return ioc(iocRead|iocWrite, t, nr, size) | ||
} | ||
|
||
// msgReportRequest used to issue SEV-SNP request | ||
// https://www.amd.com/system/files/TechDocs/56860.pdf | ||
// MSG_REPORT_REQ Table 20. | ||
type msgReportRequest struct { | ||
ReportData [64]byte | ||
VMPL uint32 | ||
_ [28]byte | ||
} | ||
|
||
// msgReportRequest used to issue SEV-SNP request | ||
// https://www.amd.com/system/files/TechDocs/56860.pdf | ||
// ATTESTATION_REPORT Table 21. | ||
type snpReport struct { | ||
Version uint32 | ||
GuestSVN uint32 | ||
Policy uint64 | ||
FamilyID [16]byte | ||
ImageID [16]byte | ||
VMPL uint32 | ||
SignatureAlgo uint32 | ||
PlatformVersion uint64 | ||
PlatformInfo uint64 | ||
AuthorKeyEn uint32 | ||
Reserved1 uint32 | ||
ReportData [64]byte | ||
Measurement [48]byte | ||
HostData [32]byte | ||
IDKeyDigest [48]byte | ||
AuthorKeyDigest [48]byte | ||
ReportID [32]byte | ||
ReportIDMA [32]byte | ||
ReportTCB uint64 | ||
Reserved2 [24]byte | ||
ChipID [64]byte | ||
CommittedSVN [8]byte | ||
CommittedVersion [8]byte | ||
LaunchSVN [8]byte | ||
Reserved3 [168]byte | ||
Signature [512]byte | ||
} | ||
|
||
// msgReportResponse is the attestation response struct | ||
// https://www.amd.com/system/files/TechDocs/56860.pdf | ||
// MSG_REPORT_RSP Table 23. | ||
// NOTE: msgReportResponse.Report is a byte slice, to have the original | ||
// response in bytes. The conversion to internal struct happens inside | ||
// convertRawReport. | ||
// NOTE: the additional 64 bytes are reserved, without them, the ioctl fails. | ||
type msgReportResponse struct { | ||
Status uint32 | ||
ReportSize uint32 | ||
reserved1 [24]byte | ||
Report [1184]byte | ||
reserved2 [64]byte // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) | ||
} | ||
|
||
func snpIoctl(f *os.File, code int, payload *SevSNPGuestRequest) error { | ||
sevSNPIOCBase := iocWR(sevGuestType, 0x0, sevGuestSize) | ||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), uintptr(code|sevSNPIOCBase), uintptr(unsafe.Pointer(payload))) | ||
if errno != 0 { | ||
return fmt.Errorf("failed to make snp IOCTL: %s", errno) | ||
} | ||
return nil | ||
} | ||
|
||
// FetchRawSNPReport returns attestation report bytes. | ||
func FetchRawSNPReport(reportData string) ([]byte, error) { | ||
f, err := os.OpenFile("/dev/sev", os.O_RDWR, 0) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
f.Close() | ||
}() | ||
|
||
msgReportIn := msgReportRequest{} | ||
msgReportOut := msgReportResponse{} | ||
|
||
if reportData != "" { | ||
if len(reportData) > len(msgReportIn.ReportData) { | ||
return nil, fmt.Errorf("reportData too large: %s", reportData) | ||
} | ||
rd, err := hex.DecodeString(reportData) | ||
if err != nil { | ||
return nil, err | ||
} | ||
copy(msgReportIn.ReportData[:], rd[:]) | ||
} | ||
|
||
payload := SevSNPGuestRequest{ | ||
RequestMsgType: MsgReportRequest, | ||
ResponseMsgType: MsgReportResponse, | ||
MsgVersion: 1, | ||
RequestLength: uint16(unsafe.Sizeof(msgReportIn)), | ||
RequestUAddr: uint64(uintptr(unsafe.Pointer(&msgReportIn))), | ||
ResponseLength: uint16(unsafe.Sizeof(msgReportOut)), | ||
ResponseUAddr: uint64(uintptr(unsafe.Pointer(&msgReportOut))), | ||
Error: 0, | ||
} | ||
|
||
if err := snpIoctl(f, sevSNPReportCode, &payload); err != nil { | ||
return nil, err | ||
} | ||
return msgReportOut.Report[:], nil | ||
} | ||
|
||
// Report represents parsed attestation report. | ||
type Report struct { | ||
Version uint32 | ||
GuestSVN uint32 | ||
Policy uint64 | ||
FamilyID string | ||
ImageID string | ||
VMPL uint32 | ||
SignatureAlgo uint32 | ||
PlatformVersion uint64 | ||
PlatformInfo uint64 | ||
AuthorKeyEn uint32 | ||
ReportData string | ||
Measurement string | ||
HostData string | ||
IDKeyDigest string | ||
AuthorKeyDigest string | ||
ReportID string | ||
ReportIDMA string | ||
ReportTCB uint64 | ||
ChipID string | ||
CommittedSVN string | ||
CommittedVersion string | ||
LaunchSVN string | ||
Signature string | ||
} | ||
|
||
// PrettyString returns formatted attestation report. | ||
func (r Report) PrettyString() string { | ||
fieldNameFmt := "%-20s" | ||
pretty := "" | ||
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "Version", r.Version) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "GuestSVN", r.GuestSVN) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "Policy", r.Policy) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "FamilyID", r.FamilyID) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ImageID", r.ImageID) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "VMPL", r.VMPL) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "SignatureAlgo", r.SignatureAlgo) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "PlatformVersion", r.PlatformVersion) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "PlatformInfo", r.PlatformInfo) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%08x\n", "AuthorKeyEn", r.AuthorKeyEn) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ReportData", r.ReportData) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "Measurement", r.Measurement) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "HostData", r.HostData) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "IDKeyDigest", r.IDKeyDigest) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "AuthorKeyDigest", r.AuthorKeyDigest) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ReportID", r.ReportID) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ReportIDMA", r.ReportIDMA) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%016x\n", "ReportTCB", r.ReportTCB) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "ChipID", r.ChipID) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "CommittedSVN", r.CommittedSVN) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "CommittedVersion", r.CommittedVersion) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "LaunchSVN", r.LaunchSVN) | ||
pretty += fmt.Sprintf(fieldNameFmt+"%s\n", "Signature", r.Signature) | ||
return pretty | ||
} | ||
|
||
// mirrorBytes mirrors the byte ordering so that hex-encoding little endian | ||
// ordered bytes come out in the readable order. | ||
func mirrorBytes(b []byte) []byte { | ||
for i := 0; i < len(b)/2; i++ { | ||
mirrorIndex := len(b) - i - 1 | ||
b[i], b[mirrorIndex] = b[mirrorIndex], b[i] | ||
} | ||
return b | ||
} | ||
|
||
func convertRawReport(rawReport *snpReport) Report { | ||
return Report{ | ||
Version: rawReport.Version, | ||
GuestSVN: rawReport.GuestSVN, | ||
Policy: rawReport.Policy, | ||
FamilyID: hex.EncodeToString(mirrorBytes(rawReport.FamilyID[:])[:]), | ||
ImageID: hex.EncodeToString(mirrorBytes(rawReport.ImageID[:])[:]), | ||
VMPL: rawReport.VMPL, | ||
SignatureAlgo: rawReport.SignatureAlgo, | ||
PlatformVersion: rawReport.PlatformVersion, | ||
PlatformInfo: rawReport.PlatformInfo, | ||
AuthorKeyEn: rawReport.AuthorKeyEn, | ||
ReportData: hex.EncodeToString(rawReport.ReportData[:]), | ||
Measurement: hex.EncodeToString(rawReport.Measurement[:]), | ||
HostData: hex.EncodeToString(rawReport.HostData[:]), | ||
IDKeyDigest: hex.EncodeToString(rawReport.IDKeyDigest[:]), | ||
AuthorKeyDigest: hex.EncodeToString(rawReport.AuthorKeyDigest[:]), | ||
ReportID: hex.EncodeToString(rawReport.ReportID[:]), | ||
ReportIDMA: hex.EncodeToString(rawReport.ReportIDMA[:]), | ||
ReportTCB: rawReport.ReportTCB, | ||
ChipID: hex.EncodeToString(rawReport.ChipID[:]), | ||
CommittedSVN: hex.EncodeToString(rawReport.CommittedSVN[:]), | ||
CommittedVersion: hex.EncodeToString(rawReport.CommittedVersion[:]), | ||
LaunchSVN: hex.EncodeToString(rawReport.LaunchSVN[:]), | ||
Signature: hex.EncodeToString(rawReport.Signature[:]), | ||
} | ||
} | ||
|
||
// FetchParsedSNPReport parses raw attestation response into proper structs. | ||
func FetchParsedSNPReport(reportData string) (Report, error) { | ||
rawBytes, err := FetchRawSNPReport(reportData) | ||
if err != nil { | ||
return Report{}, err | ||
} | ||
|
||
var report snpReport | ||
buf := bytes.NewBuffer(rawBytes) | ||
if err := binary.Read(buf, binary.LittleEndian, &report); err != nil { | ||
return Report{}, err | ||
} | ||
return convertRawReport(&report), nil | ||
} | ||
|
||
// ValidateHostData fetches SNP report (if applicable) and validates `hostData` against | ||
// HostData set at UVM launch. | ||
func ValidateHostData(hostData, reportData string) error { | ||
report, err := FetchParsedSNPReport(reportData) | ||
if err != nil { | ||
// For non-SNP hardware /dev/sev will not exist | ||
if os.IsNotExist(err) { | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
if report.HostData != hostData { | ||
return fmt.Errorf( | ||
"security policy digest %q doesn't match HostData provided at launch %q", | ||
hostData, | ||
report.HostData, | ||
) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.