Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configfs-tsm support for attestation reports #99

Merged
merged 1 commit into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a log that this interface is getting deprecated?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the deprecation notice will be in the release notes.

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
Loading