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

Integrate nonce verification as part of the TDX quote validation procedure. #395

Merged
merged 7 commits into from
Dec 28, 2023
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
12 changes: 10 additions & 2 deletions cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,16 @@ var debugCmd = &cobra.Command{
var validateOpts interface{}
switch attestation.GetTeeAttestation().(type) {
case *pb.Attestation_TdxAttestation:
validateOpts = &server.VerifyTdxOpts{
Verification: tv.DefaultOptions(),
if len(teeNonce) != 0 {
validateOpts = &server.VerifyTdxOpts{
Validation: server.TdxDefaultValidateOpts(teeNonce),
Verification: tv.DefaultOptions(),
}
} else {
validateOpts = &server.VerifyTdxOpts{
Validation: server.TdxDefaultValidateOpts(nonce),
Verification: tv.DefaultOptions(),
}
jrjatin marked this conversation as resolved.
Show resolved Hide resolved
}
case *pb.Attestation_SevSnpAttestation:
if len(teeNonce) != 0 {
Expand Down
74 changes: 74 additions & 0 deletions cmd/verify_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package cmd

import (
"encoding/hex"
"fmt"
"os"
"strings"
"testing"

tgtest "github.com/google/go-tdx-guest/testing"
tgtestclient "github.com/google/go-tdx-guest/testing/client"
tgtestdata "github.com/google/go-tdx-guest/testing/testdata"
"github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm-tools/internal/test"
pb "github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
)
Expand Down Expand Up @@ -143,3 +149,71 @@ func TestHwAttestationPass(t *testing.T) {
})
}
}

func TestTdxAttestation(t *testing.T) {
dir := t.TempDir()
file1, err := os.Create(dir + "/attestFile")
if err != nil {
t.Fatal(err)
}
file2 := makeOutputFile(t, "verifyFile")
defer os.RemoveAll(file2)
tpmNonce := "1234"
teeNonce := hex.EncodeToString(test.TdxReportData)
wrongTeeNonce := hex.EncodeToString([]byte("wrongTdxNonce"))
attestation, err := createAttestationWithFakeTdx([]byte(tpmNonce), test.TdxReportData, t)
if err != nil {
t.Fatal(err)
}
out := []byte(marshalOptions.Format(attestation))
file1.Write(out)
hexTpmNonce := hex.EncodeToString([]byte(tpmNonce))
tests := []struct {
name string
tdxNonce string
wantErr string
}{
{"Correct TEE Nonce", teeNonce, ""},
{"Incorrect TEE Nonce", wrongTeeNonce, "quote field REPORT_DATA"},
}

for _, op := range tests {
t.Run(op.name, func(t *testing.T) {
RootCmd.SetArgs([]string{"verify", "debug", "--nonce", hexTpmNonce, "--input", file1.Name(), "--output", file2, "--tee-nonce", op.tdxNonce, "--format", "textproto"})
if err := RootCmd.Execute(); (err == nil && op.wantErr != "") ||
(err != nil && !strings.Contains(err.Error(), op.wantErr)) {
t.Errorf("Expected error: %v, got: %v", op.wantErr, err)
}
})
}
}

func createAttestationWithFakeTdx(tpmNonce []byte, teeNonce []byte, tb *testing.T) (*pb.Attestation, error) {
tdxEventLog := test.CreateTpm2EventLog(3) // Enum 3- TDX
rwc := test.GetSimulatorWithLog(tb, tdxEventLog)
defer client.CheckedClose(tb, rwc)
ak, err := client.AttestationKeyRSA(rwc)
if err != nil {
return nil, fmt.Errorf("failed to generate AK: %v", err)
}
defer ak.Close()
var teeNonce64 [64]byte
copy(teeNonce64[:], teeNonce)
tdxTestDevice := tgtestclient.GetTdxGuest([]tgtest.TestCase{
{
Input: teeNonce64,
Quote: tgtestdata.RawQuote,
},
}, tb)

defer tdxTestDevice.Close()
attestation, err := ak.Attest(client.AttestOpts{
Nonce: tpmNonce,
TEEDevice: &client.TdxDevice{Device: tdxTestDevice},
TEENonce: teeNonce64[:],
})
if err != nil {
return nil, fmt.Errorf("failed to attest: %v", err)
}
return attestation, nil
}
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
Expand Down
1 change: 1 addition & 0 deletions internal/test/tdx_test_files/tdxReportData.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lbÞÁ¸I£«I å2£YDÞ¤|®ñù€†9“Ù‰•Eët£í1;˜zF}¬êÖðÈzmvlföòŸŠË(
6 changes: 6 additions & 0 deletions internal/test/test_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,9 @@ var GCECertPEMs = [][]byte{
GCEEncryptECCCertUCA, GCESignECCCertUCA,
GCEEncryptRSACertUCA, GCESignRSACertUCA,
}

// TDX test files
var (
//go:embed tdx_test_files/tdxReportData.bin
TdxReportData []byte // Use as tdx nonce
)
76 changes: 76 additions & 0 deletions internal/test/test_tpm.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package test

import (
"encoding/binary"
"io"
"sync"
"testing"

"github.com/google/go-attestation/attest"
"github.com/google/go-tpm-tools/simulator"
"github.com/google/go-tpm/legacy/tpm2"
gtpm2 "github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil"
)

Expand Down Expand Up @@ -145,3 +147,77 @@ func extendOnePcr(tb testing.TB, rw io.ReadWriter, pcr int, hashAlg tpm2.Algorit
tb.Fatalf("PCRExtend failed: %v", err)
}
}

// CreateTpm2EventLog generates a sample event log that is based on gceConfidentialTechnology
func CreateTpm2EventLog(gceConfidentialTechnologyEnum byte) []byte {
pcr0 := uint32(0)
algorithms := []gtpm2.TPMIAlgHash{gtpm2.TPMAlgSHA1, gtpm2.TPMAlgSHA256, gtpm2.TPMAlgSHA384}
specEventInfo := []byte{
'S', 'p', 'e', 'c', ' ', 'I', 'D', ' ', 'E', 'v', 'e', 'n', 't', '0', '3', 0,
0, 0, 0, 0, // platformClass
0, // specVersionMinor,
2, // specVersionMajor,
0, // specErrata
2, // uintnSize
byte(len(algorithms)), 0, 0, 0} // NumberOfAlgorithms
for _, alg := range algorithms {
var algInfo [4]byte
algo, _ := alg.Hash()
binary.LittleEndian.PutUint16(algInfo[0:2], uint16(alg))
binary.LittleEndian.PutUint16(algInfo[2:4], uint16(algo.Size()))
specEventInfo = append(specEventInfo, algInfo[:]...)
}
vendorInfoSize := byte(0)
specEventInfo = append(specEventInfo, vendorInfoSize)

specEventHeader := make([]byte, 32)
evNoAction := uint32(0x03)
binary.LittleEndian.PutUint32(specEventHeader[0:4], pcr0)
binary.LittleEndian.PutUint32(specEventHeader[4:8], evNoAction)
binary.LittleEndian.PutUint32(specEventHeader[28:32], uint32(len(specEventInfo)))
specEvent := append(specEventHeader, specEventInfo...)

// After the Spec ID Event, all events must use all the specified digest algorithms.
extendHashes := func(buffer []byte, info []byte) []byte {
var numberOfDigests [4]byte
binary.LittleEndian.PutUint32(numberOfDigests[:], uint32(len(algorithms)))
buffer = append(buffer, numberOfDigests[:]...)
for _, alg := range algorithms {
algo, _ := alg.Hash()
digest := make([]byte, 2+algo.Size())
binary.LittleEndian.PutUint16(digest[0:2], uint16(alg))
h := algo.New()
h.Write(info)
copy(digest[2:], h.Sum(nil))
buffer = append(buffer, digest...)
}
return buffer
}
writeTpm2Event := func(buffer []byte, pcr uint32, eventType uint32, info []byte) []byte {
header := make([]byte, 8)
binary.LittleEndian.PutUint32(header[0:4], pcr)
binary.LittleEndian.PutUint32(header[4:8], eventType)
buffer = append(buffer, header...)

buffer = extendHashes(buffer, info)

var eventSize [4]byte
binary.LittleEndian.PutUint32(eventSize[:], uint32(len(info)))
buffer = append(buffer, eventSize[:]...)

return append(buffer, info...)
}
evSCRTMversion := uint32(0x08)
versionEventInfo := []byte{
'G', 0, 'C', 0, 'E', 0, ' ', 0,
'V', 0, 'i', 0, 'r', 0, 't', 0, 'u', 0, 'a', 0, 'l', 0, ' ', 0,
'F', 0, 'i', 0, 'r', 0, 'm', 0, 'w', 0, 'a', 0, 'r', 0, 'e', 0, ' ', 0,
'v', 0, '1', 0, 0, 0}
withVersionEvent := writeTpm2Event(specEvent, pcr0, evSCRTMversion, versionEventInfo)

nonHostEventInfo := []byte{
'G', 'C', 'E', ' ', 'N', 'o', 'n', 'H', 'o', 's', 't', 'I', 'n', 'f', 'o', 0,
gceConfidentialTechnologyEnum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
evNonHostInfo := uint32(0x11)
return writeTpm2Event(withVersionEvent, pcr0, evNonHostInfo, nonHostEventInfo)
}
2 changes: 1 addition & 1 deletion server/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func VerifyGceTechnology(attestation *pb.Attestation, tech pb.GCEConfidentialTec
return fmt.Errorf("TEE attestation is %T, expected a TdxAttestation", attestation.GetTeeAttestation())
}
if opts.TEEOpts == nil {
tdxOpts = TdxDefaultOptions()
tdxOpts = TdxDefaultOptions(opts.Nonce)
} else {
tdxOpts, ok = opts.TEEOpts.(*VerifyTdxOpts)
if !ok {
Expand Down
23 changes: 20 additions & 3 deletions server/verify_tdx.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
package server

import (
tabi "github.com/google/go-tdx-guest/abi"
"github.com/google/go-tdx-guest/validate"
tv "github.com/google/go-tdx-guest/verify"
)

// VerifyTdxOpts allows for customizing the functionality of VerifyAttestation's TDX verification.
type VerifyTdxOpts struct {
Validation *validate.Options
Verification *tv.Options
}

// TdxDefaultOptions returns a default verification options for TDX
// TdxDefaultValidateOpts returns a default validation policy for TDX attestation quote on GCE.
func TdxDefaultValidateOpts(tdxNonce []byte) *validate.Options {
policy := &validate.Options{HeaderOptions: validate.HeaderOptions{},
TdQuoteBodyOptions: validate.TdQuoteBodyOptions{}}
policy.TdQuoteBodyOptions.ReportData = make([]byte, tabi.ReportDataSize)
copy(policy.TdQuoteBodyOptions.ReportData, tdxNonce)
return policy
}

// TdxDefaultOptions returns a default validation policy and verification options for TDX
// attestation quote on GCE.
func TdxDefaultOptions() *VerifyTdxOpts {
func TdxDefaultOptions(tdxNonce []byte) *VerifyTdxOpts {
return &VerifyTdxOpts{
Validation: TdxDefaultValidateOpts(tdxNonce),
Verification: tv.DefaultOptions(),
}
}
Expand All @@ -23,5 +36,9 @@ func TdxDefaultOptions() *VerifyTdxOpts {
// Supported quote formats - QuoteV4.
func VerifyTdxAttestation(tdxAttestationQuote any, opts *VerifyTdxOpts) error {
// Check that the quote contains valid signature and certificates. Do not check revocations.
return tv.TdxQuote(tdxAttestationQuote, opts.Verification)
if err := tv.TdxQuote(tdxAttestationQuote, opts.Verification); err != nil {
return err
}
// Check that the fields of the quote are acceptable
return validate.TdxQuote(tdxAttestationQuote, opts.Validation)
}
Loading
Loading