Skip to content

Commit

Permalink
Remove VerifyAttestation dependency on SNP/TDX (google#523)
Browse files Browse the repository at this point in the history
Previously, VerifyAttestation required passing a SNP or TDX attestation
when it detected the appropriate NonHostInfo event in the UEFI event
log. This introduces a weak verification requirement that gives no
guarantees in the resulting claim that the vTPM and SNP/TDX devices are
actually on the same machine.

Fixes google#500. We still need to remove the client auto collection of SNP/TDX
attestation (google#504), but that is a bit more work since we also need to
update the gotpm CLI to include SNP and TDX attestation collection.
  • Loading branch information
alexmwu authored Jan 22, 2025
1 parent 09bf13f commit 9c8fac3
Show file tree
Hide file tree
Showing 5 changed files with 6 additions and 693 deletions.
34 changes: 2 additions & 32 deletions cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"fmt"
"io"

sv "github.com/google/go-sev-guest/verify"
tv "github.com/google/go-tdx-guest/verify"
pb "github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm-tools/server"
"github.com/google/go-tpm/legacy/tpm2"
Expand Down Expand Up @@ -49,36 +47,8 @@ var debugCmd = &cobra.Command{
return err
}

var validateOpts interface{}
switch attestation.GetTeeAttestation().(type) {
case *pb.Attestation_TdxAttestation:
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(),
}
}
case *pb.Attestation_SevSnpAttestation:
if len(teeNonce) != 0 {
validateOpts = &server.VerifySnpOpts{
Validation: server.SevSnpDefaultValidateOpts(teeNonce),
Verification: &sv.Options{},
}
} else {
validateOpts = &server.VerifySnpOpts{
Validation: server.SevSnpDefaultValidateOpts(nonce),
Verification: &sv.Options{},
}
}
default:
validateOpts = nil
}
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{cryptoPub}, TEEOpts: validateOpts})
// TODO(#524): create a new subcommand that verifies SNP and TDX attestation.
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
if err != nil {
return fmt.Errorf("verifying attestation: %w", err)
}
Expand Down
112 changes: 0 additions & 112 deletions cmd/verify_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
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-tools/verifier/util"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
Expand Down Expand Up @@ -113,108 +106,3 @@ func TestVerifyWithGCEAK(t *testing.T) {
})
}
}

func TestHwAttestationPass(t *testing.T) {
rwc := test.GetTPM(t)
defer client.CheckedClose(t, rwc)
ExternalTPM = rwc

inputFile := makeOutputFile(t, "attest")
outputFile := makeOutputFile(t, "attestout")
defer os.RemoveAll(inputFile)
defer os.RemoveAll(outputFile)
teenonce := "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"
tests := []struct {
name string
nonce string
teetech string
wanterr string
}{
{"TdxPass", "1234", "tdx", "failed to create tdx quote provider"},
{"SevSnpPass", "1234", "sev-snp", "failed to open sev-snp device"},
}
for _, op := range tests {
t.Run(op.name, func(t *testing.T) {
attestArgs := []string{"attest", "--nonce", op.nonce, "--key", "AK", "--output", inputFile, "--format", "textproto", "--tee-nonce", teenonce, "--tee-technology", op.teetech}
RootCmd.SetArgs(attestArgs)
if err := RootCmd.Execute(); err != nil {
if !strings.Contains(err.Error(), op.wanterr) {
t.Error(err)
}
} else {
RootCmd.SetArgs([]string{"verify", "debug", "--nonce", op.nonce, "--input", inputFile, "--output", outputFile, "--format", "textproto", "--tee-nonce", teenonce})
if err := RootCmd.Execute(); err != nil {
t.Error(err)
}
}
})
}
}

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 @@ -829,6 +829,7 @@ github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZat
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-configfs-tsm v0.3.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk=
github.com/google/go-eventlog v0.0.1/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ=
Expand Down
103 changes: 3 additions & 100 deletions server/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (

"github.com/google/go-eventlog/proto/state"
"github.com/google/go-eventlog/register"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-tdx-guest/proto/tdx"
"github.com/google/go-tpm-tools/internal"
pb "github.com/google/go-tpm-tools/proto/attest"
tpmpb "github.com/google/go-tpm-tools/proto/tpm"
Expand Down Expand Up @@ -61,6 +59,9 @@ type VerifyOpts struct {
// or can be *VerifyTdxOpts if the TEEAttestation is a TdxAttestation
// If nil, uses Nonce for ReportData and the TEE's verification library's
// embedded root certs for its roots of trust.
//
// Deprecated: go-tpm-tools no longer verifies SNP or TDX attestation.
// Please use go-sev-guest and go-tdx-guest.
TEEOpts interface{}
}

Expand Down Expand Up @@ -347,61 +348,6 @@ func makePool(certs []*x509.Certificate) *x509.CertPool {
return pool
}

// verifyGceTechnology checks the GCE-specific GceNonHost event's Trusted Execution Technology (TEE)
// claim using attestation reports if the technology supports them, and only then validates that a
// particular technology has proven that it is in use.
func verifyGceTechnology(attestation *pb.Attestation, tech pb.GCEConfidentialTechnology, opts *VerifyOpts) error {
switch tech {
case pb.GCEConfidentialTechnology_NONE: // Nothing to verify
if opts.TEEOpts != nil {
return fmt.Errorf("memory encryption technology %v does not support TEEOpts", tech)
}
return nil
case pb.GCEConfidentialTechnology_AMD_SEV: // Not verifiable on GCE
if opts.TEEOpts != nil {
return fmt.Errorf("memory encryption technology %v does not support TEEOpts", tech)
}
return nil
case pb.GCEConfidentialTechnology_AMD_SEV_ES: // Not verifiable on GCE
if opts.TEEOpts != nil {
return fmt.Errorf("memory encryption technology %v does not support TEEOpts", tech)
}
return nil
case pb.GCEConfidentialTechnology_AMD_SEV_SNP:
var snpOpts *VerifySnpOpts
tee, ok := attestation.TeeAttestation.(*pb.Attestation_SevSnpAttestation)
if !ok {
return fmt.Errorf("TEE attestation is %T, expected a SevSnpAttestation", attestation.GetTeeAttestation())
}
if opts.TEEOpts == nil {
snpOpts = SevSnpDefaultOptions(opts.Nonce)
} else {
snpOpts, ok = opts.TEEOpts.(*VerifySnpOpts)
if !ok {
return fmt.Errorf("unexpected value for TEEOpts given a SEV-SNP attestation report: %v",
opts.TEEOpts)
}
}
return VerifySevSnpAttestation(tee.SevSnpAttestation, snpOpts)
case pb.GCEConfidentialTechnology_INTEL_TDX:
var tdxOpts *VerifyTdxOpts
tee, ok := attestation.TeeAttestation.(*pb.Attestation_TdxAttestation)
if !ok {
return fmt.Errorf("TEE attestation is %T, expected a TdxAttestation", attestation.GetTeeAttestation())
}
if opts.TEEOpts == nil {
tdxOpts = TdxDefaultOptions(opts.Nonce)
} else {
tdxOpts, ok = opts.TEEOpts.(*VerifyTdxOpts)
if !ok {
return fmt.Errorf("unexpected value for TEEOpts given a TDX attestation quote: %v", opts.TEEOpts)
}
}
return VerifyTdxAttestation(tee.TdxAttestation, tdxOpts)
}
return fmt.Errorf("unknown GCEConfidentialTechnology: %v", tech)
}

// parseMachineStateFromTPM is a wrapper function around `parsePCClientEventLog` method to:
// 1. parse partial machine state from TPM TCG event logs.
// 2. verify GceTechnology since the GCE Technology event is directly related to the TPM.
Expand All @@ -411,48 +357,5 @@ func parseMachineStateFromTPM(attestation *pb.Attestation, pcrs *tpmpb.PCRs, opt
if err != nil {
return nil, fmt.Errorf("failed to validate the PCClient event log: %w", err)
}

// TODO #500 remove verifyGceTechnology from parseMachineStateFromTPM/VerifyAttestation,
// as it's an application speicified operation.
tech := ms.GetPlatform().Technology
if err := verifyGceTechnology(attestation, tech, &opts); err != nil {
return nil, fmt.Errorf("failed to verify memory encryption technology: %w", err)
}

state, err := parseTEEAttestation(attestation, tech)
if err != nil {
return nil, fmt.Errorf("failed to parse machineState from TEE attestation: %w", err)
}
ms.TeeAttestation = state.TeeAttestation
return ms, nil
}

// parseTEEAttestation parses a machineState from TeeAttestation.
// For now it simply populates the machineState TeeAttestation field with the verified TDX/SNP data.
// In long term, it should parse a full machineState from TeeAttestation.
func parseTEEAttestation(attestation *pb.Attestation, tech pb.GCEConfidentialTechnology) (*pb.MachineState, error) {
switch tech {
case pb.GCEConfidentialTechnology_AMD_SEV_SNP:
tee, ok := attestation.TeeAttestation.(*pb.Attestation_SevSnpAttestation)
if !ok {
return nil, fmt.Errorf("TEE attestation is %T, expected a SevSnpAttestation", attestation.GetTeeAttestation())
}
return &pb.MachineState{
// TODO: needs to populate platform state from SNP report once GCE instance info is binded to HOSTDATA.
TeeAttestation: &pb.MachineState_SevSnpAttestation{
SevSnpAttestation: proto.Clone(tee.SevSnpAttestation).(*sevsnp.Attestation),
}}, nil
case pb.GCEConfidentialTechnology_INTEL_TDX:
tee, ok := attestation.TeeAttestation.(*pb.Attestation_TdxAttestation)
if !ok {
return nil, fmt.Errorf("TEE attestation is %T, expected a TdxAttestation", attestation.GetTeeAttestation())
}
// TODO: needs to populate platform state from TDX quote once GCE instance info is binded to MRCONFIG.
return &pb.MachineState{
TeeAttestation: &pb.MachineState_TdxAttestation{
TdxAttestation: proto.Clone(tee.TdxAttestation).(*tdx.QuoteV4),
}}, nil
default:
return &pb.MachineState{}, nil
}
}
Loading

0 comments on commit 9c8fac3

Please sign in to comment.