attestation: validate GCP machine state

This commit is contained in:
Thomas Tendyck 2023-03-06 09:17:08 +01:00 committed by Thomas Tendyck
parent 2535073df8
commit 0a344e4cf6
3 changed files with 44 additions and 51 deletions

View File

@ -7,13 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
package gcp
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"time"
@ -23,11 +21,12 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm-tools/server"
"github.com/googleapis/gax-go/v2"
"google.golang.org/api/option"
)
const minimumGceVersion = 1
// Validator for GCP confidential VM attestation.
type Validator struct {
oid.GCPSEVES
@ -40,7 +39,7 @@ func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
Validator: vtpm.NewValidator(
pcrs,
trustedKeyFromGCEAPI(newInstanceClient),
gceNonHostInfoEvent,
validateCVM,
log,
),
}
@ -102,22 +101,18 @@ func trustedKeyFromGCEAPI(getClient func(ctx context.Context, opts ...option.Cli
}
}
// gceNonHostInfoEvent looks for the GCE Non-Host info event in an event log.
// Returns an error if the event is not found, or if the event is missing the required flag to mark the VM confidential.
func gceNonHostInfoEvent(attDoc vtpm.AttestationDocument, _ *attest.MachineState) error {
if attDoc.Attestation == nil {
return errors.New("missing attestation in attestation document")
// validateCVM checks that the machine state represents a GCE AMD-SEV VM.
func validateCVM(_ vtpm.AttestationDocument, state *attest.MachineState) error {
gceVersion := state.Platform.GetGceVersion()
if gceVersion < minimumGceVersion {
return fmt.Errorf("outdated GCE version: %v (require >= %v)", gceVersion, minimumGceVersion)
}
// The event log of a GCE VM contains the GCE Non-Host info event
// This event is 32-bytes, followed by one byte 0x01 if it is confidential, 0x00 otherwise,
// followed by 15 reserved bytes.
// See https://pkg.go.dev/github.com/google/go-tpm-tools@v0.3.1/server#pkg-variables
idx := bytes.Index(attDoc.Attestation.EventLog, server.GCENonHostInfoSignature)
if idx <= 0 {
return fmt.Errorf("event log is missing GCE Non-Host info event")
}
if attDoc.Attestation.EventLog[idx+len(server.GCENonHostInfoSignature)] != 0x01 {
return fmt.Errorf("GCE Non-Host info is missing confidential bit")
tech := state.Platform.Technology
wantTech := attest.GCEConfidentialTechnology_AMD_SEV
if tech != wantTech {
return fmt.Errorf("unexpected confidential technology: %v (expected: %v)", tech, wantTech)
}
return nil
}

View File

@ -23,38 +23,35 @@ import (
"google.golang.org/protobuf/proto"
)
func TestGceNonHostInfoEvent(t *testing.T) {
func TestValidateCVM(t *testing.T) {
testCases := map[string]struct {
attDoc vtpm.AttestationDocument
state *attest.MachineState
wantErr bool
}{
"is cvm": {
attDoc: vtpm.AttestationDocument{
Attestation: &attest.Attestation{
EventLog: []byte("\x00\x00\x00GCE NonHostInfo\x00\x01\x00\x00"),
},
},
"is current cvm": {
state: &attest.MachineState{Platform: &attest.PlatformState{
Firmware: &attest.PlatformState_GceVersion{GceVersion: minimumGceVersion},
Technology: attest.GCEConfidentialTechnology_AMD_SEV,
}},
},
"attestation is nil": {
attDoc: vtpm.AttestationDocument{
Attestation: nil,
},
wantErr: true,
"is newer cvm": {
state: &attest.MachineState{Platform: &attest.PlatformState{
Firmware: &attest.PlatformState_GceVersion{GceVersion: minimumGceVersion + 1},
Technology: attest.GCEConfidentialTechnology_AMD_SEV,
}},
},
"missing GCE Non-Host info event": {
attDoc: vtpm.AttestationDocument{
Attestation: &attest.Attestation{
EventLog: []byte("No GCE Event"),
},
},
"is older cvm": {
state: &attest.MachineState{Platform: &attest.PlatformState{
Firmware: &attest.PlatformState_GceVersion{GceVersion: minimumGceVersion - 1},
Technology: attest.GCEConfidentialTechnology_AMD_SEV,
}},
wantErr: true,
},
"not a cvm": {
attDoc: vtpm.AttestationDocument{
Attestation: &attest.Attestation{
EventLog: []byte("\x00\x00\x00GCE NonHostInfo\x00\x00\x00\x00"),
},
},
state: &attest.MachineState{Platform: &attest.PlatformState{
Firmware: &attest.PlatformState_GceVersion{GceVersion: minimumGceVersion},
Technology: attest.GCEConfidentialTechnology_NONE,
}},
wantErr: true,
},
}
@ -62,7 +59,7 @@ func TestGceNonHostInfoEvent(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
err := gceNonHostInfoEvent(tc.attDoc, nil)
err := validateCVM(vtpm.AttestationDocument{}, tc.state)
if tc.wantErr {
assert.Error(err)
} else {

View File

@ -198,23 +198,24 @@ func (v *Validator) Validate(attDocRaw []byte, nonce []byte) (userData []byte, e
return nil, fmt.Errorf("validating attestation public key: %w", err)
}
// Validate confidential computing capabilities of the VM
if err := v.validateCVM(attDoc, nil); err != nil {
return nil, fmt.Errorf("verifying VM confidential computing capabilities: %w", err)
}
// Verify the TPM attestation
if _, err := tpmServer.VerifyAttestation(
state, err := tpmServer.VerifyAttestation(
attDoc.Attestation,
tpmServer.VerifyOpts{
Nonce: makeExtraData(attDoc.UserData, nonce),
TrustedAKs: []crypto.PublicKey{aKP},
AllowSHA1: false,
},
); err != nil {
)
if err != nil {
return nil, fmt.Errorf("verifying attestation document: %w", err)
}
// Validate confidential computing capabilities of the VM
if err := v.validateCVM(attDoc, state); err != nil {
return nil, fmt.Errorf("verifying VM confidential computing capabilities: %w", err)
}
// Verify PCRs
quoteIdx, err := GetSHA256QuoteIndex(attDoc.Attestation.Quotes)
if err != nil {