Skip to content

Commit

Permalink
Add a 'show' tool and more abi functions
Browse files Browse the repository at this point in the history
This allows for better visualization support for viewing attestation
reports.

Signed-off-by: Dionna Glaze <dionnaglaze@google.com>
  • Loading branch information
deeglaze committed Jan 30, 2024
1 parent 76997c0 commit 5c79536
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 69 deletions.
48 changes: 48 additions & 0 deletions abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,54 @@ func (c *CertTable) GetByGUIDString(guid string) ([]byte, error) {
return nil, fmt.Errorf("cert not found for GUID %s", guid)
}

// CertsFromProto returns the CertTable represented in the given certificate chain.
func CertsFromProto(chain *pb.CertificateChain) *CertTable {
c := &CertTable{}
if len(chain.GetArkCert()) != 0 {
c.Entries = append(c.Entries,
CertTableEntry{GUID: uuid.Parse(ArkGUID), RawCert: chain.GetArkCert()})
}
if len(chain.GetAskCert()) != 0 {
c.Entries = append(c.Entries,
CertTableEntry{GUID: uuid.Parse(AskGUID), RawCert: chain.GetAskCert()})
}
if len(chain.GetVcekCert()) != 0 {
c.Entries = append(c.Entries,
CertTableEntry{GUID: uuid.Parse(VcekGUID), RawCert: chain.GetVcekCert()})
}
if len(chain.GetVlekCert()) != 0 {
c.Entries = append(c.Entries,
CertTableEntry{GUID: uuid.Parse(VlekGUID), RawCert: chain.GetVlekCert()})
}
for guid, cert := range chain.GetExtras() {
c.Entries = append(c.Entries,
CertTableEntry{GUID: uuid.Parse(guid), RawCert: cert})
}
return c
}

// Marshal returns the CertTable in its GUID table ABI format.
func (c *CertTable) Marshal() []byte {
if len(c.Entries) == 0 {
return nil
}
headerSize := uint32((len(c.Entries) + 1) * CertTableEntrySize)
var dataSize uint32
for _, entry := range c.Entries {
dataSize += uint32(len(entry.RawCert))
}
output := make([]byte, dataSize+headerSize)
cursor := headerSize
for i, entry := range c.Entries {
size := uint32(len(entry.RawCert))
h := &CertTableHeaderEntry{GUID: entry.GUID, Offset: cursor, Length: size}
copy(output[cursor:], entry.RawCert)
h.Write(output[i*CertTableEntrySize:])
cursor += size
}
return output
}

// Proto returns the certificate chain represented in an extended guest request's
// data pages. The GHCB specification allows any number of entries in the pages,
// so missing certificates aren't an error. If certificates are missing, you can
Expand Down
4 changes: 4 additions & 0 deletions abi/abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ func TestCertTableProto(t *testing.T) {
if !ok || !bytes.Equal(gotExtra, extraraw) {
t.Fatalf("Extras[%q] = %v, want %v", extraGUID, gotExtra, extraraw)
}
bs := c.Marshal()
if !bytes.Equal(bs, result) {
t.Errorf("c.Marshal() = %v, want %v", bs, result)
}
}

func TestSevProduct(t *testing.T) {
Expand Down
71 changes: 2 additions & 69 deletions tools/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
spb "github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/testing"
"github.com/google/go-sev-guest/tools/lib/cmdline"
"github.com/google/go-sev-guest/tools/lib/report"
"github.com/google/go-sev-guest/validate"
"github.com/google/go-sev-guest/verify"
"github.com/google/go-sev-guest/verify/testdata"
Expand Down Expand Up @@ -137,74 +138,6 @@ var (
product = &spb.SevProduct{}
)

func parseAttestationBytes(b []byte) (*spb.Attestation, error) {
// This format is the attestation report in AMD's specified ABI format, immediately
// followed by the certificate table bytes.
if len(b) < abi.ReportSize {
return nil, fmt.Errorf("attestation contents too small (0x%x bytes). Want at least 0x%x bytes", len(b), abi.ReportSize)
}
reportBytes := b[0:abi.ReportSize]
certBytes := b[abi.ReportSize:]

report, err := abi.ReportToProto(reportBytes)
if err != nil {
return nil, fmt.Errorf("could not parse attestation report: %v", err)
}

certs := new(abi.CertTable)
if err := certs.Unmarshal(certBytes); err != nil {
return nil, fmt.Errorf("could not parse certificate table: %v", err)
}
return &spb.Attestation{Report: report, CertificateChain: certs.Proto()}, nil
}

func parseAttestation(b []byte) (*spb.Attestation, error) {
switch *inform {
case "bin":
return parseAttestationBytes(b)
case "proto":
result := &spb.Attestation{}
if err := proto.Unmarshal(b, result); err != nil {
return nil, fmt.Errorf("could not parse %q as proto: %v", *infile, err)
}
case "textproto":
result := &spb.Attestation{}
if err := prototext.Unmarshal(b, result); err != nil {
return nil, fmt.Errorf("could not parse %q as textproto: %v", *infile, err)
}
default:
return nil, fmt.Errorf("unknown value -inform=%s", *inform)
}
// This should be impossible.
return nil, errors.New("internal error")
}

func getAttestation() (*spb.Attestation, error) {
var in io.Reader
var f *os.File
if *infile == "-" {
in = os.Stdin
} else {
file, err := os.Open(*infile)
if err != nil {
return nil, fmt.Errorf("could not open %q: %v", *infile, err)
}
f = file
in = file
}
defer func() {
if f != nil {
f.Close()
}
}()

contents, err := io.ReadAll(in)
if err != nil {
return nil, fmt.Errorf("could not read %q: %v", *infile, err)
}
return parseAttestation(contents)
}

func parseHashes(s string) ([][]byte, error) {
hexhashes := strings.Split(s, ",")
if len(hexhashes) == 1 && hexhashes[0] == "" {
Expand Down Expand Up @@ -543,7 +476,7 @@ func main() {
die(errors.New("cannot specify both -check_crl=true and -network=false"))
}

attestation, err := getAttestation()
attestation, err := report.GetAttestation(*infile, *inform)
if err != nil {
die(err)
}
Expand Down
139 changes: 139 additions & 0 deletions tools/lib/report/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Package report provides functions for reading and writing attestation reports of various formats.
package report

import (
"errors"
"fmt"
"io"
"os"

"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/kds"
"go.uber.org/multierr"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"

spb "github.com/google/go-sev-guest/proto/sevsnp"
)

func parseAttestationBytes(b []byte) (*spb.Attestation, error) {
// This format is the attestation report in AMD's specified ABI format, immediately
// followed by the certificate table bytes.
if len(b) < abi.ReportSize {
return nil, fmt.Errorf("attestation contents too small (0x%x bytes). Want at least 0x%x bytes", len(b), abi.ReportSize)
}
reportBytes := b[0:abi.ReportSize]
certBytes := b[abi.ReportSize:]

report, err := abi.ReportToProto(reportBytes)
if err != nil {
return nil, fmt.Errorf("could not parse attestation report: %v", err)
}

certs := new(abi.CertTable)
if err := certs.Unmarshal(certBytes); err != nil {
return nil, fmt.Errorf("could not parse certificate table: %v", err)
}
return &spb.Attestation{Report: report, CertificateChain: certs.Proto()}, nil
}

// ParseAttestation parses an attestation report from a byte slice as a given format.
func ParseAttestation(b []byte, inform string) (*spb.Attestation, error) {
switch inform {
case "bin":
// May have empty certificate buffer to be just a report.
return parseAttestationBytes(b)
case "proto":
result := &spb.Attestation{}
aerr := proto.Unmarshal(b, result)
var rerr error
if aerr != nil {
result.Report = &spb.Report{}
rerr = proto.Unmarshal(b, result.Report)
if rerr != nil {
return nil, fmt.Errorf("could not parse as proto: %v", multierr.Append(aerr, rerr))
}
}
return result, nil
case "textproto":
result := &spb.Attestation{}
aerr := prototext.Unmarshal(b, result)
var rerr error
if aerr != nil {
result.Report = &spb.Report{}
rerr = prototext.Unmarshal(b, result.Report)
if rerr != nil {
return nil, fmt.Errorf("could not parse as textproto: %v", multierr.Append(aerr, rerr))
}
}
default:
return nil, fmt.Errorf("unknown inform: %q", inform)
}
// This should be impossible.
return nil, errors.New("internal error")
}

// GetAttestation reads an attestation report from a file.
func GetAttestation(infile, inform string) (*spb.Attestation, error) {
var in io.Reader
var f *os.File
if infile == "-" {
in = os.Stdin
} else {
file, err := os.Open(infile)
if err != nil {
return nil, fmt.Errorf("could not open %q: %v", infile, err)
}
f = file
in = file
}
defer func() {
if f != nil {
f.Close()
}
}()

contents, err := io.ReadAll(in)
if err != nil {
return nil, fmt.Errorf("could not read %q: %v", infile, err)
}
return ParseAttestation(contents, inform)
}

func asBin(report *spb.Attestation) ([]byte, error) {
r, err := abi.ReportToAbiBytes(report.Report)
if err != nil {
return nil, err
}
certs := abi.CertsFromProto(report.CertificateChain).Marshal()
return append(r, certs...), nil
}

func tcbBreakdown(tcb uint64) string {
parts := kds.DecomposeTCBVersion(kds.TCBVersion(tcb))
return fmt.Sprintf("0x%x:{ucode: %d, snp: %d, tee: %d, bl: %d}", tcb, parts.UcodeSpl, parts.SnpSpl,
parts.TeeSpl, parts.BlSpl)
}

func tcbText(report *spb.Attestation) ([]byte, error) {
return []byte(fmt.Sprintf("current_tcb=%s\ncommitted_tcb=%s\nlaunch_tcb=%s\n",
tcbBreakdown(report.Report.GetCurrentTcb()),
tcbBreakdown(report.Report.GetCommittedTcb()),
tcbBreakdown(report.Report.GetLaunchTcb()))), nil
}

// Transform returns the attestation in the outform marshalled format.
func Transform(report *spb.Attestation, outform string) ([]byte, error) {
switch outform {
case "bin":
return asBin(report)
case "proto":
return proto.Marshal(report)
case "textproto":
return prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(report)
case "tcb":
return tcbText(report)
default:
return nil, fmt.Errorf("unknown outform: %q", outform)
}
}
46 changes: 46 additions & 0 deletions tools/show/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// show reads an attestation report and outputs it in a preferred format.
package main

import (
"flag"
"os"

"github.com/google/go-sev-guest/tools/lib/report"
"github.com/google/logger"
)

var (
infile = flag.String("in", "-", "Path to attestation file, or - for stdin.")
inform = flag.String("inform", "in", "Format of the attestation file. "+
"One of bin, proto, textproto")
outfile = flag.String("out", "-", "Path to output file, or - for stdout.")
outform = flag.String("outform", "textproto", "Format of the output file. "+
"One of bin, proto, textproto, tcb. Tcb is human-readable.")
)

func main() {
logger.Init("", false, false, os.Stderr)
flag.Parse()

attestation, err := report.GetAttestation(*infile, *inform)
if err != nil {
logger.Fatal(err)
}

bin, err := report.Transform(attestation, *outform)
if err != nil {
logger.Fatal(err)
}

out := os.Stdout
if *outfile != "-" {
out, err = os.OpenFile(*outfile, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
logger.Fatalf("Could not open %q: %v", *outfile, err)
}
}

if _, err := out.Write(bin); err != nil {
logger.Fatalf("Could not write attestation to %q: %v", *outfile, err)
}
}

0 comments on commit 5c79536

Please sign in to comment.