Skip to content

Commit

Permalink
Add configfs-tsm support for attestation reports
Browse files Browse the repository at this point in the history
Given Dan Williams's configfs-tsm patch set to the Linux kernel, all
attestation reports ought to be requested through configfs and not
ioctls.

This marks the deprecation of the Device interface for reports, but not
for keys.

Signed-off-by: Dionna Glaze <[email protected]>
  • Loading branch information
deeglaze committed Jan 16, 2024
1 parent 16e7331 commit ead5519
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() error
GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error)
}

// QuoteProvider encapsulates calls to collect an extended attestation report.
type QuoteProvider interface {
IsSupported() error
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() error {
d, err := OpenDevice()
if err != nil {
return err
}
d.Close()
return nil
}

// 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() error {
_, err := linuxtsm.MakeClient()
return err
}

// 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 err := preferred.IsSupported(); err != nil {
return &LinuxIoctlQuoteProvider{}, nil
}
return preferred, nil
}

// GetLeveledQuoteProvider returns a supported SEV-SNP LeveledQuoteProvider.
func GetLeveledQuoteProvider() (QuoteProvider, error) {
preferred := &LinuxConfigFsQuoteProvider{}
if err := preferred.IsSupported(); err != nil {
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() error {
return fmt.Errorf("MacOS is unsupported")
}

// 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() error {
return fmt.Errorf("Windows is unsupported")
}

// 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 nil.
func (*QuoteProvider) IsSupported() error {
return nil
}

// 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 ead5519

Please sign in to comment.