From 2b3092fff53178912afe9662ef3fe42b429c4503 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Thu, 17 Mar 2022 11:56:38 -0700 Subject: [PATCH 1/2] Add guest package for fetching attestation report via syscall Add internal/guest/linux package, which contains linux ioctl definitions. Devicemapper code is refactored to use the new package. Introduce ioctl wrapper and structs required to fetch attestation report. Validate that LaunchData provided to HCS and HostData returned as part of attestation report match. Add utility binary to fetch SNP report and update Makefile to support DEV_BUILD parameter, which includes test utilities inside LCOW image. Fake attestation report can be used when testing integrations. Signed-off-by: Maksim An --- Makefile | 21 +- internal/guest/amdsev/report.go | 268 ++++++++++++++++++ internal/guest/amdsev/report_test.go | 53 ++++ internal/guest/linux/ioctl.go | 49 ++++ internal/guest/runtime/hcsv2/hostdata.go | 34 +++ internal/guest/runtime/hcsv2/uvm.go | 9 + .../storage/devicemapper/devicemapper.go | 38 +-- internal/tools/snp-report/fake/report.go | 57 ++++ internal/tools/snp-report/main.go | 77 +++++ internal/uvm/create_lcow.go | 23 +- pkg/securitypolicy/securitypolicy.go | 15 + .../github.com/Microsoft/hcsshim/Makefile | 21 +- .../hcsshim/internal/uvm/create_lcow.go | 23 +- .../pkg/securitypolicy/securitypolicy.go | 15 + 14 files changed, 645 insertions(+), 58 deletions(-) create mode 100644 internal/guest/amdsev/report.go create mode 100644 internal/guest/amdsev/report_test.go create mode 100644 internal/guest/linux/ioctl.go create mode 100644 internal/guest/runtime/hcsv2/hostdata.go create mode 100644 internal/tools/snp-report/fake/report.go create mode 100644 internal/tools/snp-report/main.go diff --git a/Makefile b/Makefile index e1a0f51129..c11786ce3a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ BASE:=base.tar.gz +DEV_BUILD:=0 GO:=go GO_FLAGS:=-ldflags "-s -w" # strip Go binaries @@ -16,6 +17,12 @@ GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA) SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +DELTA_TARGET=out/delta.tar.gz + +ifeq "$(DEV_BUILD)" "1" +DELTA_TARGET=out/delta-dev.tar.gz +endif + # The link aliases for gcstools GCS_TOOLS=\ generichook \ @@ -55,6 +62,15 @@ out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools bin/cmd/ho tar -zcf $@ -C rootfs . rm -rf rootfs +# This target includes utilities which may be useful for testing purposes. +out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report + rm -rf rootfs-dev + mkdir rootfs-dev + tar -xzf out/delta.tar.gz -C rootfs-dev + cp bin/internal/tools/snp-report rootfs-dev/bin/ + tar -zcf $@ -C rootfs-dev . + rm -rf rootfs-dev + out/rootfs.tar.gz: out/initrd.img rm -rf rootfs-conv mkdir rootfs-conv @@ -62,8 +78,8 @@ out/rootfs.tar.gz: out/initrd.img tar -zcf $@ -C rootfs-conv . rm -rf rootfs-conv -out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh - $(SRCROOT)/hack/catcpio.sh "$(BASE)" out/delta.tar.gz > out/initrd.img.uncompressed +out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh + $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed gzip -c out/initrd.img.uncompressed > $@ rm out/initrd.img.uncompressed @@ -71,6 +87,7 @@ out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh -include deps/cmd/gcstools.gomake -include deps/cmd/hooks/wait-paths.gomake -include deps/cmd/tar2ext4.gomake +-include deps/internal/tools/snp-report.gomake # Implicit rule for includes that define Go targets. %.gomake: $(SRCROOT)/Makefile diff --git a/internal/guest/amdsev/report.go b/internal/guest/amdsev/report.go new file mode 100644 index 0000000000..18b9a87864 --- /dev/null +++ b/internal/guest/amdsev/report.go @@ -0,0 +1,268 @@ +//go:build linux +// +build linux + +// Package amdsev contains minimal functionality required to fetch +// attestation reports inside an enlightened guest. +package amdsev + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "os" + "unsafe" + + "github.com/Microsoft/hcsshim/internal/guest/linux" +) + +const ( + msgTypeInvalid = iota + msgCPUIDRequest + msgCPUIDResponse + msgKeyRequest + msgKeyResponse + msgReportRequest + msgReportResponse + msgExportRequest + msgExportResponse + msgImportRequest + msgImportResponse + msgAbsorbRequest + msgAbsorbResponse + msgVMRKRequest + msgVMRKResponse + msgTypeMax +) + +type guestRequest struct { + RequestMsgType byte + ResponseMsgType byte + MsgVersion byte + RequestLength uint16 + RequestUAddr unsafe.Pointer + ResponseLength uint16 + ResponseUAddr unsafe.Pointer + Error uint32 +} + +// AMD SEV ioctl definitions +const ( + // SEV-SNP IOCTL type + guestType = 'S' + // SEV-SNP IOCTL size, same as unsafe.Sizeof(SevSNPGuestRequest{}) + guestSize = 40 + ioctlBase = linux.IocWRBase | guestType< 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 := &guestRequest{ + RequestMsgType: msgReportRequest, + ResponseMsgType: msgReportResponse, + MsgVersion: 1, + RequestLength: uint16(unsafe.Sizeof(msgReportIn)), + RequestUAddr: unsafe.Pointer(&msgReportIn), + ResponseLength: uint16(unsafe.Sizeof(msgReportOut)), + ResponseUAddr: unsafe.Pointer(&msgReportOut), + Error: 0, + } + + if err := linux.Ioctl(f, reportCode|ioctlBase, unsafe.Pointer(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 []byte + 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+"%x\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 +} + +// 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 r report + buf := bytes.NewBuffer(rawBytes) + if err := binary.Read(buf, binary.LittleEndian, &r); err != nil { + return Report{}, err + } + return r.report(), nil +} diff --git a/internal/guest/amdsev/report_test.go b/internal/guest/amdsev/report_test.go new file mode 100644 index 0000000000..2f233031c1 --- /dev/null +++ b/internal/guest/amdsev/report_test.go @@ -0,0 +1,53 @@ +//go:build linux +// +build linux + +package amdsev + +import ( + "testing" +) + +func Test_Mirror_NonEmpty_Byte_Slices(t *testing.T) { + type config struct { + name string + input []byte + expected []byte + } + + for _, conf := range []config{ + { + name: "Length0", + input: []byte{}, + expected: []byte{}, + }, + { + name: "Length1", + input: []byte{100}, + expected: []byte{100}, + }, + { + name: "LengthOdd", + input: []byte{100, 101, 102, 103, 104}, + expected: []byte{104, 103, 102, 101, 100}, + }, + { + name: "LengthEven", + input: []byte{100, 101, 102, 103, 104, 105}, + expected: []byte{105, 104, 103, 102, 101, 100}, + }, + } { + t.Run(conf.name, func(t *testing.T) { + result := mirrorBytes(conf.input) + if string(result[:]) != string(conf.expected[:]) { + t.Fatalf("the ipnut byte array %+v was not mirrored; %+v", conf.input, result) + } + }) + } +} + +func Test_Mirror_Nil_Slice(t *testing.T) { + result := mirrorBytes(nil) + if result != nil { + t.Fatalf("expected nil slice, got: %+v", result) + } +} diff --git a/internal/guest/linux/ioctl.go b/internal/guest/linux/ioctl.go new file mode 100644 index 0000000000..c87a3da9f5 --- /dev/null +++ b/internal/guest/linux/ioctl.go @@ -0,0 +1,49 @@ +//go:build linux +// +build linux + +// Package linux contains definitions required for making a linux ioctl. +package linux + +import ( + "os" + "unsafe" + + "golang.org/x/sys/unix" +) + +// 32 bits to describe an ioctl: +// 0-7: NR (command for a given ioctl type) +// 8-15: TYPE (ioctl type) +// 16-29: SIZE (payload size) +// 30-31: DIR (direction of ioctl, can be: none/write/read/write-read) +const ( + IocWrite = 1 + IocRead = 2 + IocNRBits = 8 + IocTypeBits = 8 + IocSizeBits = 14 + IocDirBits = 2 + + IocNRMask = (1 << IocNRBits) - 1 + IocTypeMask = (1 << IocTypeBits) - 1 + IocSizeMask = (1 << IocSizeBits) - 1 + IocDirMask = (1 << IocDirBits) - 1 + IocTypeShift = IocNRBits + IocSizeShift = IocTypeShift + IocTypeBits + IocDirShift = IocSizeShift + IocSizeBits + IocWRBase = (IocRead | IocWrite) << IocDirShift +) + +// Ioctl makes a syscall described by `command` with data `dataPtr` to device +// driver file `f`. +func Ioctl(f *os.File, command int, dataPtr unsafe.Pointer) error { + if _, _, err := unix.Syscall( + unix.SYS_IOCTL, + f.Fd(), + uintptr(command), + uintptr(dataPtr), + ); err != 0 { + return err + } + return nil +} diff --git a/internal/guest/runtime/hcsv2/hostdata.go b/internal/guest/runtime/hcsv2/hostdata.go new file mode 100644 index 0000000000..7b18704968 --- /dev/null +++ b/internal/guest/runtime/hcsv2/hostdata.go @@ -0,0 +1,34 @@ +//go:build linux +// +build linux + +package hcsv2 + +import ( + "bytes" + "fmt" + "os" + + "github.com/Microsoft/hcsshim/internal/guest/amdsev" +) + +// validateHostData fetches SNP report (if applicable) and validates `hostData` against +// HostData set at UVM launch. +func validateHostData(hostData []byte) error { + report, err := amdsev.FetchParsedSNPReport("") + if err != nil { + // For non-SNP hardware /dev/sev will not exist + if os.IsNotExist(err) { + return nil + } + return err + } + + if bytes.Compare(hostData, report.HostData) != 0 { + return fmt.Errorf( + "security policy digest %q doesn't match HostData provided at launch %q", + hostData, + report.HostData, + ) + } + return nil +} diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index d5e004fc6a..b1abecc811 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -95,6 +95,15 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error { return err } + hostData, err := securitypolicy.NewSecurityPolicyDigest(base64Policy) + if err != nil { + return err + } + + if err := validateHostData(hostData[:]); err != nil { + return err + } + h.securityPolicyEnforcer = p h.securityPolicyEnforcerSet = true diff --git a/internal/guest/storage/devicemapper/devicemapper.go b/internal/guest/storage/devicemapper/devicemapper.go index 3b313284c1..3f9b6797e7 100644 --- a/internal/guest/storage/devicemapper/devicemapper.go +++ b/internal/guest/storage/devicemapper/devicemapper.go @@ -12,6 +12,8 @@ import ( "unsafe" "golang.org/x/sys/unix" + + "github.com/Microsoft/hcsshim/internal/guest/linux" ) // CreateFlags modify the operation of CreateDevice @@ -28,24 +30,9 @@ var ( ) const ( - _IOC_WRITE = 1 - _IOC_READ = 2 - _IOC_NRBITS = 8 - _IOC_TYPEBITS = 8 - _IOC_SIZEBITS = 14 - _IOC_DIRBITS = 2 - - _IOC_NRMASK = ((1 << _IOC_NRBITS) - 1) - _IOC_TYPEMASK = ((1 << _IOC_TYPEBITS) - 1) - _IOC_SIZEMASK = ((1 << _IOC_SIZEBITS) - 1) - _IOC_DIRMASK = ((1 << _IOC_DIRBITS) - 1) - _IOC_TYPESHIFT = (_IOC_NRBITS) - _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS) - _IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS) - _DM_IOCTL = 0xfd _DM_IOCTL_SIZE = 312 - _DM_IOCTL_BASE = (_IOC_READ|_IOC_WRITE)<<_IOC_DIRSHIFT | _DM_IOCTL<<_IOC_TYPESHIFT | _DM_IOCTL_SIZE<<_IOC_SIZESHIFT + _DM_IOCTL_BASE = linux.IocWRBase | _DM_IOCTL< out/initrd.img.uncompressed +out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh + $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed gzip -c out/initrd.img.uncompressed > $@ rm out/initrd.img.uncompressed @@ -71,6 +87,7 @@ out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh -include deps/cmd/gcstools.gomake -include deps/cmd/hooks/wait-paths.gomake -include deps/cmd/tar2ext4.gomake +-include deps/internal/tools/snp-report.gomake # Implicit rule for includes that define Go targets. %.gomake: $(SRCROOT)/Makefile diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go index 3acfa875c3..79a065454e 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go @@ -4,7 +4,6 @@ package uvm import ( "context" - "crypto/sha256" "encoding/base64" "fmt" "io" @@ -15,6 +14,7 @@ import ( "github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio/pkg/guid" + "github.com/Microsoft/hcsshim/pkg/securitypolicy" "github.com/pkg/errors" "github.com/sirupsen/logrus" "go.opencensus.io/trace" @@ -402,28 +402,23 @@ func makeLCOWSecurityDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) // and can be used to check that the policy used by opengcs is the required one as // a condition of releasing secrets to the container. - // First, decode the base64 string into a human readable (json) string . - jsonPolicy, err := base64.StdEncoding.DecodeString(opts.SecurityPolicy) + policyDigest, err := securitypolicy.NewSecurityPolicyDigest(opts.SecurityPolicy) if err != nil { - return nil, fmt.Errorf("failed to decode base64 SecurityPolicy") + return nil, err } - - // make a sha256 hashing object - hostData := sha256.New() - // give it the jsaon string to measure - hostData.Write(jsonPolicy) - // get the measurement out - securityPolicyHash := base64.StdEncoding.EncodeToString(hostData.Sum(nil)) + // HCS API expect a base64 encoded string as LaunchData. Internally it + // decodes it to bytes. SEV later returns the decoded byte blob as HostData + // field of the report. + hostData := base64.StdEncoding.EncodeToString(policyDigest) // Put the measurement into the LaunchData field of the HCS creation command. - // This will endup in HOST_DATA of SNP_LAUNCH_FINISH command the and ATTESTATION_REPORT + // This will end-up in HOST_DATA of SNP_LAUNCH_FINISH command the and ATTESTATION_REPORT // retrieved by the guest later. - doc.VirtualMachine.SecuritySettings = &hcsschema.SecuritySettings{ EnableTpm: false, Isolation: &hcsschema.IsolationSettings{ IsolationType: "SecureNestedPaging", - LaunchData: securityPolicyHash, + LaunchData: hostData, // HclEnabled: true, /* Not available in schema 2.5 - REQUIRED when using BlockStorage in 2.6 */ }, } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go index 2a539ed9db..a75ada7030 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -1,8 +1,10 @@ package securitypolicy import ( + "crypto/sha256" "encoding/base64" "encoding/json" + "fmt" "regexp" "strconv" @@ -86,6 +88,19 @@ func NewOpenDoorPolicy() *SecurityPolicy { } } +// NewSecurityPolicyDigest decodes base64 encoded policy string, computes +// and returns sha256 digest +func NewSecurityPolicyDigest(base64policy string) ([]byte, error) { + jsonPolicy, err := base64.StdEncoding.DecodeString(base64policy) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 security policy: %w", err) + } + digest := sha256.New() + digest.Write(jsonPolicy) + digestBytes := digest.Sum(nil) + return digestBytes, nil +} + // Internal version of SecurityPolicyContainer type securityPolicyContainer struct { // The command that we will allow the container to execute From ddb7af2fbc458992cdc8dac56cbaa6a8c039764f Mon Sep 17 00:00:00 2001 From: Maksim An Date: Tue, 12 Apr 2022 10:50:18 -0700 Subject: [PATCH 2/2] make amdsevsnp a public package. Signed-off-by: Maksim An --- internal/guest/runtime/hcsv2/hostdata.go | 4 +- internal/tools/snp-report/fake/report.go | 8 +-- internal/tools/snp-report/main.go | 50 +++++++++++++++++-- .../guest/amdsev => pkg/amdsevsnp}/report.go | 46 +++-------------- .../amdsev => pkg/amdsevsnp}/report_test.go | 2 +- 5 files changed, 58 insertions(+), 52 deletions(-) rename {internal/guest/amdsev => pkg/amdsevsnp}/report.go (73%) rename {internal/guest/amdsev => pkg/amdsevsnp}/report_test.go (98%) diff --git a/internal/guest/runtime/hcsv2/hostdata.go b/internal/guest/runtime/hcsv2/hostdata.go index 7b18704968..562ed006e1 100644 --- a/internal/guest/runtime/hcsv2/hostdata.go +++ b/internal/guest/runtime/hcsv2/hostdata.go @@ -8,13 +8,13 @@ import ( "fmt" "os" - "github.com/Microsoft/hcsshim/internal/guest/amdsev" + "github.com/Microsoft/hcsshim/pkg/amdsevsnp" ) // validateHostData fetches SNP report (if applicable) and validates `hostData` against // HostData set at UVM launch. func validateHostData(hostData []byte) error { - report, err := amdsev.FetchParsedSNPReport("") + report, err := amdsevsnp.FetchParsedSNPReport(nil) if err != nil { // For non-SNP hardware /dev/sev will not exist if os.IsNotExist(err) { diff --git a/internal/tools/snp-report/fake/report.go b/internal/tools/snp-report/fake/report.go index 709db23d85..9ceae35ff9 100644 --- a/internal/tools/snp-report/fake/report.go +++ b/internal/tools/snp-report/fake/report.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "fmt" - "github.com/Microsoft/hcsshim/internal/guest/amdsev" + "github.com/Microsoft/hcsshim/pkg/amdsevsnp" ) const fakeSNPReport = "01000000010000001f00030000000000010000000000000000000000000000000200000000000000000000000000000000000000010000000000000000000028010000000000000000000000000000007ab000a323b3c873f5b81bbe584e7c1a26bcf40dc27e00f8e0d144b1ed2d14f10000000000000000000000000000000000000000000000000000000000000000e29af700e85b39996fa38226d2804b78cad746ffef4477360a61b47874bdecd640f9d32f5ff64a55baad3c545484d9ed28603a3ea835a83bd688b0ec1dcb36b6b8c22412e5b63115b75db8628b989bc598c475ca5f7683e8d351e7e789a1baff19041750567161ad52bf0d152bd76d7c6f313d0a0fd72d0089692c18f521155800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040aea62690b08eb6d680392c9a9b3db56a9b3cc44083b9da31fb88bcfc493407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000028000000000000000000000000000000000000000000000000e6c86796cd44b0bc6b7c0d4fdab33e2807e14b5fc4538b3750921169d97bcf4447c7d3ab2a7c25f74c1641e2885c1011d025cc536f5c9a2504713136c7877f480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003131c0f3e7be5c6e400f22404596e1874381e99d03de45ef8b97eee0a0fa93a4911550330343f14dddbbd6c0db83744f000000000000000000000000000000000000000000000000db07c83c5e6162c2387f3b76cd547672657f6a5df99df98efee7c15349320d83e086c5003ec43050a9b18d1c39dedc340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" @@ -20,15 +20,15 @@ func FetchRawSNPReport() ([]byte, error) { // FetchSNPReport returns amdsev.Report object that corresponds to the decoded // version of fakeSNPReport. Overrides the resulting report's HostData field // with provided `hostData`. -func FetchSNPReport(hostData string) (amdsev.Report, error) { +func FetchSNPReport(hostData string) (amdsevsnp.Report, error) { if hostData == "" { hostData = "28603a3ea835a83bd688b0ec1dcb36b6b8c22412e5b63115b75db8628b989bc5" } hdBytes, err := hex.DecodeString(hostData) if err != nil { - return amdsev.Report{}, fmt.Errorf("failed to decode host data: %w", err) + return amdsevsnp.Report{}, fmt.Errorf("failed to decode host data: %w", err) } - r := amdsev.Report{ + r := amdsevsnp.Report{ Version: 1, GuestSVN: 1, Policy: 0x03001f, diff --git a/internal/tools/snp-report/main.go b/internal/tools/snp-report/main.go index 90c9795b79..dcdc31e8a7 100644 --- a/internal/tools/snp-report/main.go +++ b/internal/tools/snp-report/main.go @@ -4,14 +4,45 @@ package main import ( + "encoding/hex" "flag" "fmt" "os" - "github.com/Microsoft/hcsshim/internal/guest/amdsev" "github.com/Microsoft/hcsshim/internal/tools/snp-report/fake" + "github.com/Microsoft/hcsshim/pkg/amdsevsnp" ) +// verboseReport returns formatted attestation report. +func verboseReport(r amdsevsnp.Report) 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+"%x\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 +} + func main() { fakeReportFlag := flag.Bool( "fake-report", @@ -41,13 +72,22 @@ func main() { flag.Parse() + var reportBytes []byte + if *reportDataFlag != "" { + var err error + reportBytes, err = hex.DecodeString(*reportDataFlag) + if err != nil { + fmt.Printf("failed to decode report data:%s\n", err) + os.Exit(1) + } + } if *binaryFmtFlag { var binaryReport []byte var err error if !*fakeReportFlag { binaryReport, err = fake.FetchRawSNPReport() } else { - binaryReport, err = amdsev.FetchRawSNPReport(*reportDataFlag) + binaryReport, err = amdsevsnp.FetchRawSNPReport(reportBytes) } if err != nil { fmt.Println(err) @@ -57,12 +97,12 @@ func main() { os.Exit(0) } - var report amdsev.Report + var report amdsevsnp.Report var err error if *fakeReportFlag { report, err = fake.FetchSNPReport(*hostDataFlag) } else { - report, err = amdsev.FetchParsedSNPReport(*reportDataFlag) + report, err = amdsevsnp.FetchParsedSNPReport(reportBytes) } if err != nil { fmt.Printf("failed to fetch SNP report: %s", err) @@ -72,6 +112,6 @@ func main() { if !*verbosePrintFlag { fmt.Printf("%+v\n", report) } else { - fmt.Println(report.PrettyString()) + fmt.Println(verboseReport(report)) } } diff --git a/internal/guest/amdsev/report.go b/pkg/amdsevsnp/report.go similarity index 73% rename from internal/guest/amdsev/report.go rename to pkg/amdsevsnp/report.go index 18b9a87864..0a0e0abbfa 100644 --- a/internal/guest/amdsev/report.go +++ b/pkg/amdsevsnp/report.go @@ -1,9 +1,9 @@ //go:build linux // +build linux -// Package amdsev contains minimal functionality required to fetch +// Package amdsevsnp contains minimal functionality required to fetch // attestation reports inside an enlightened guest. -package amdsev +package amdsevsnp import ( "bytes" @@ -143,7 +143,7 @@ type reportResponse struct { } // FetchRawSNPReport returns attestation report bytes. -func FetchRawSNPReport(reportData string) ([]byte, error) { +func FetchRawSNPReport(reportData []byte) ([]byte, error) { f, err := os.OpenFile("/dev/sev", os.O_RDWR, 0) if err != nil { return nil, err @@ -157,15 +157,11 @@ func FetchRawSNPReport(reportData string) ([]byte, error) { msgReportOut reportResponse ) - if reportData != "" { + if reportData != nil { 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[:]) + copy(msgReportIn.ReportData[:], reportData) } payload := &guestRequest{ @@ -212,36 +208,6 @@ type Report struct { 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+"%x\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 { @@ -253,7 +219,7 @@ func mirrorBytes(b []byte) []byte { } // FetchParsedSNPReport parses raw attestation response into proper structs. -func FetchParsedSNPReport(reportData string) (Report, error) { +func FetchParsedSNPReport(reportData []byte) (Report, error) { rawBytes, err := FetchRawSNPReport(reportData) if err != nil { return Report{}, err diff --git a/internal/guest/amdsev/report_test.go b/pkg/amdsevsnp/report_test.go similarity index 98% rename from internal/guest/amdsev/report_test.go rename to pkg/amdsevsnp/report_test.go index 2f233031c1..e42af6de1f 100644 --- a/internal/guest/amdsev/report_test.go +++ b/pkg/amdsevsnp/report_test.go @@ -1,7 +1,7 @@ //go:build linux // +build linux -package amdsev +package amdsevsnp import ( "testing"