Skip to content

Commit

Permalink
Merge pull request #99 from deeglaze/configfs
Browse files Browse the repository at this point in the history
Add configfs-tsm support for attestation reports
  • Loading branch information
deeglaze authored Jan 16, 2024
2 parents 16e7331 + 776294f commit 869dc68
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 1 deletion.
21 changes: 21 additions & 0 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,27 @@ func ReportToProto(data []uint8) (*pb.Report, error) {
return r, nil
}

// ReportCertsToProto creates a pb.Attestation from the report and certificate table represented in
// data. The report is expected to take exactly abi.ReportSize bytes, followed by the certificate
// table.
func ReportCertsToProto(data []uint8) (*pb.Attestation, error) {
var certs []uint8
report := data
if len(data) >= ReportSize {
report = data[:ReportSize]
certs = data[ReportSize:]
}
mreport, err := ReportToProto(report)
if err != nil {
return nil, err
}
table := new(CertTable)
if err := table.Unmarshal(certs); err != nil {
return nil, err
}
return &pb.Attestation{Report: mreport, CertificateChain: table.Proto()}, nil
}

func checkReportSizes(r *pb.Report) error {
if len(r.FamilyId) != FamilyIDSize {
return fmt.Errorf("report family_id length is %d, expect %d", len(r.FamilyId), FamilyIDSize)
Expand Down
13 changes: 13 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ type Device interface {
Product() *pb.SevProduct
}

// LeveledQuoteProvider encapsulates calls to collect an extended attestation report at a given
// privilege level.
type LeveledQuoteProvider interface {
IsSupported() bool
GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error)
}

// QuoteProvider encapsulates calls to collect an extended attestation report.
type QuoteProvider interface {
IsSupported() bool
GetRawQuote(reportData [64]byte) ([]uint8, error)
}

// UseDefaultSevGuest returns true iff -sev_guest_device_path=default.
func UseDefaultSevGuest() bool {
return *sevGuestPath == "default"
Expand Down
101 changes: 101 additions & 0 deletions client/client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"fmt"
"time"

"github.com/google/go-configfs-tsm/configfs/linuxtsm"
"github.com/google/go-configfs-tsm/report"
"github.com/google/go-sev-guest/abi"
labi "github.com/google/go-sev-guest/client/linuxabi"
spb "github.com/google/go-sev-guest/proto/sevsnp"
Expand Down Expand Up @@ -123,3 +125,102 @@ func (d *LinuxDevice) Ioctl(command uintptr, req any) (uintptr, error) {
func (d *LinuxDevice) Product() *spb.SevProduct {
return abi.SevProduct()
}

// LinuxIoctlQuoteProvider implements the QuoteProvider interface to fetch
// attestation quote via the deprecated /dev/sev-guest ioctl.
type LinuxIoctlQuoteProvider struct{}

// IsSupported checks if TSM client can be created to use /dev/sev-guest ioctl.
func (p *LinuxIoctlQuoteProvider) IsSupported() bool {
d, err := OpenDevice()
if err != nil {
return false
}
d.Close()
return true
}

// GetRawQuoteAtLevel returns byte format attestation plus certificate table via /dev/sev-guest ioctl.
func (p *LinuxIoctlQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]uint8, error) {
d, err := OpenDevice()
if err != nil {
return nil, err
}
defer d.Close()
report, certs, err := GetRawExtendedReportAtVmpl(d, reportData, int(level))
if err != nil {
return nil, err
}
return append(report, certs...), nil
}

// GetRawQuote returns byte format attestation plus certificate table via /dev/sev-guest ioctl.
func (p *LinuxIoctlQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
d, err := OpenDevice()
if err != nil {
return nil, err
}
defer d.Close()
report, certs, err := GetRawExtendedReport(d, reportData)
if err != nil {
return nil, err
}
return append(report, certs...), nil
}

// LinuxConfigFsQuoteProvider implements the QuoteProvider interface to fetch
// attestation quote via ConfigFS.
type LinuxConfigFsQuoteProvider struct{}

// IsSupported checks if TSM client can be created to use ConfigFS system.
func (p *LinuxConfigFsQuoteProvider) IsSupported() bool {
_, err := linuxtsm.MakeClient()
return err == nil
}

// GetRawQuoteAtLevel returns byte format attestation plus certificate table via ConfigFS.
func (p *LinuxConfigFsQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]uint8, error) {
req := &report.Request{
InBlob: reportData[:],
GetAuxBlob: true,
Privilege: &report.Privilege{
Level: level,
},
}
resp, err := linuxtsm.GetReport(req)
if err != nil {
return nil, err
}
return append(resp.OutBlob, resp.AuxBlob...), nil
}

// GetRawQuote returns byte format attestation plus certificate table via ConfigFS.
func (p *LinuxConfigFsQuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
req := &report.Request{
InBlob: reportData[:],
GetAuxBlob: true,
}
resp, err := linuxtsm.GetReport(req)
if err != nil {
return nil, err
}
return append(resp.OutBlob, resp.AuxBlob...), nil
}

// GetQuoteProvider returns a supported SEV-SNP QuoteProvider.
func GetQuoteProvider() (QuoteProvider, error) {
preferred := &LinuxConfigFsQuoteProvider{}
if !preferred.IsSupported() {
return &LinuxIoctlQuoteProvider{}, nil
}
return preferred, nil
}

// GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider.
func GetLeveledQuoteProvider() (QuoteProvider, error) {
preferred := &LinuxConfigFsQuoteProvider{}
if !preferred.IsSupported() {
return &LinuxIoctlQuoteProvider{}, nil
}
return preferred, nil
}
29 changes: 29 additions & 0 deletions client/client_macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
const DefaultSevGuestDevicePath = "unknown"

// MacOSDevice implements the Device interface with Linux ioctls.
// Deprecated: Use MacOSQuoteProvider.
type MacOSDevice struct{}

// Open is not supported on MacOS.
Expand All @@ -52,3 +53,31 @@ func (*MacOSDevice) Ioctl(_ uintptr, _ any) (uintptr, error) {
func (*MacOSDevice) Product() *spb.SevProduct {
return &spb.SevProduct{}
}

// MacOSQuoteProvider implements the QuoteProvider interface with Linux's configfs-tsm.
type MacOSQuoteProvider struct{}

// IsSupported checks if the quote provider is supported.
func (*MacOSQuoteProvider) IsSupported() bool {
return false
}

// GetRawQuote returns byte format attestation plus certificate table via ConfigFS.
func (*MacOSQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) {
return nil, fmt.Errorf("MacOS is unsupported")
}

// GetRawQuoteAtLevel returns byte format attestation plus certificate table via ConfigFS.
func (*MacOSQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]byte, error) {
return nil, fmt.Errorf("MacOS is unsupported")
}

// GetQuoteProvider returns a supported SEV-SNP QuoteProvider.
func GetQuoteProvider() (QuoteProvider, error) {
return nil, fmt.Errorf("MacOS is unsupported")
}

// GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider.
func GetLeveledQuoteProvider() (LeveledQuoteProvider, error) {
return nil, fmt.Errorf("MacOS is unsupported")
}
29 changes: 29 additions & 0 deletions client/client_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
)

// WindowsDevice implements the Device interface with Linux ioctls.
// Deprecated: Use WindowsQuoteProvider.
type WindowsDevice struct{}

// Open is not supported on Windows.
Expand Down Expand Up @@ -50,3 +51,31 @@ func (*WindowsDevice) Ioctl(_ uintptr, _ any) (uintptr, error) {
func (*WindowsDevice) Product() *spb.SevProduct {
return &spb.SevProduct{}
}

// WindowsQuoteProvider implements the QuoteProvider interface with Linux's configfs-tsm.
type WindowsQuoteProvider struct{}

// IsSupported checks if the quote provider is supported.
func (*WindowsQuoteProvider) IsSupported() bool {
return false
}

// GetRawQuote returns byte format attestation plus certificate table via ConfigFS.
func (*WindowsQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) {
return nil, fmt.Errorf("Windows is unsupported")
}

// GetRawQuoteAtLevel returns byte format attestation plus certificate table via ConfigFS.
func (*WindowsQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, level uint) ([]byte, error) {
return nil, fmt.Errorf("Windows is unsupported")
}

// GetQuoteProvider returns a supported SEV-SNP QuoteProvider.
func GetQuoteProvider() (QuoteProvider, error) {
return nil, fmt.Errorf("Windows is unsupported")
}

// GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider.
func GetLeveledQuoteProvider() (LeveledQuoteProvider, error) {
return nil, fmt.Errorf("Windows is unsupported")
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
require (
github.com/golang/protobuf v1.5.3
github.com/google/go-cmp v0.5.7
github.com/google/go-configfs-tsm v0.2.2
github.com/google/logger v1.1.1
github.com/pborman/uuid v1.2.1
github.com/pkg/errors v0.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down
75 changes: 74 additions & 1 deletion testing/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func SkipUnmockableTestCase(tc *test.TestCase) bool {
return !client.UseDefaultSevGuest() && tc.FwErr != 0
}

// GetSevGuest is a cross-platform testing helper function that retrives the
// GetSevGuest is a cross-platform testing helper function that retrieves the
// appropriate SEV-guest device from the flags passed into "go test".
//
// If using a test guest device, this will also produce a fake AMD-SP that produces the signed
Expand Down Expand Up @@ -101,3 +101,76 @@ func GetSevGuest(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (
}
return client, nil, badSnpRoot, kdsImpl
}

// GetSevQuoteProvider is a cross-platform testing helper function that retrieves the
// appropriate SEV-guest device from the flags passed into "go test".
//
// If using a test guest device, this will also produce a fake AMD-SP that produces the signed
// versions of given attestation reports based on different nonce input. Its returned roots of trust
// are based on the fake's signing credentials.
func GetSevQuoteProvider(tcs []test.TestCase, opts *test.DeviceOptions, tb testing.TB) (client.QuoteProvider, map[string][]*trust.AMDRootCerts, map[string][]*trust.AMDRootCerts, trust.HTTPSGetter) {
tb.Helper()
if client.UseDefaultSevGuest() {
sevQp, err := test.TcQuoteProvider(tcs, opts)
if err != nil {
tb.Fatalf("failed to create test device: %v", err)
}
goodSnpRoot := map[string][]*trust.AMDRootCerts{
"Milan": {
{
Product: "Milan",
ProductCerts: &trust.ProductCerts{
Ask: sevQp.Signer.Ask,
Ark: sevQp.Signer.Ark,
Asvk: sevQp.Signer.Asvk,
},
},
},
}
badSnpRoot := map[string][]*trust.AMDRootCerts{
"Milan": {
{
Product: "Milan",
ProductCerts: &trust.ProductCerts{
// No ASK, oops.
Ask: sevQp.Signer.Ark,
Ark: sevQp.Signer.Ark,
Asvk: sevQp.Signer.Ark,
},
},
},
}
fakekds, err := test.FakeKDSFromSigner(sevQp.Signer)
if err != nil {
tb.Fatalf("failed to create fake KDS from signer: %v", err)
}
return sevQp, goodSnpRoot, badSnpRoot, fakekds
}

client, err := client.GetQuoteProvider()
if err != nil {
tb.Fatalf("Failed to open SEV guest device: %v", err)
}
kdsImpl := test.GetKDS(tb)

badSnpRoot := make(map[string][]*trust.AMDRootCerts)
for product, rootCerts := range trust.DefaultRootCerts {
// Supplement the defaults with the missing x509 certificates.
pc, err := trust.GetProductChain(product, abi.VcekReportSigner, kdsImpl)
if err != nil {
tb.Fatalf("failed to get product chain for %q: %v", product, err)
}
// By removing the ASK intermediate, we ensure that the attestation will never verify.
badSnpRoot[product] = []*trust.AMDRootCerts{{
Product: product,
ProductCerts: &trust.ProductCerts{
Ark: pc.Ark,
Ask: pc.Ark,
Asvk: pc.Ark,
},
AskSev: rootCerts.ArkSev,
ArkSev: rootCerts.AskSev,
}}
}
return client, nil, badSnpRoot, kdsImpl
}
37 changes: 37 additions & 0 deletions testing/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,43 @@ func (d *Device) Product() *spb.SevProduct {
return d.SevProduct
}

// QuoteProvider represents a SEV-SNP backed configfs-tsm with pre-programmed responses to attestations.
type QuoteProvider struct {
ReportDataRsp map[string]any
Certs []byte
Signer *AmdSigner
SevProduct *spb.SevProduct
}

// IsSupported returns true
func (*QuoteProvider) IsSupported() bool {
return true
}

// GetRawQuote returns the raw report assigned for given reportData.
func (p *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
mockRspI, ok := p.ReportDataRsp[hex.EncodeToString(reportData[:])]
if !ok {
return nil, fmt.Errorf("test error: no response for %v", reportData)
}
mockRsp, ok := mockRspI.(*GetReportResponse)
if !ok {
return nil, fmt.Errorf("test error: incorrect response type %v", mockRspI)
}
if mockRsp.FwErr != 0 {
return nil, syscall.Errno(unix.EIO)
}
report := mockRsp.Resp.Data[:abi.ReportSize]
r, s, err := p.Signer.Sign(abi.SignedComponent(report))
if err != nil {
return nil, fmt.Errorf("test error: could not sign report: %v", err)
}
if err := abi.SetSignature(r, s, report); err != nil {
return nil, fmt.Errorf("test error: could not set signature: %v", err)
}
return append(report, p.Certs...), nil
}

// GetResponse controls how often (Occurrences) a certain response should be
// provided.
type GetResponse struct {
Expand Down
Loading

0 comments on commit 869dc68

Please sign in to comment.