Skip to content

Commit

Permalink
attestation: validate GCP machine state instead of PCR 0
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasten committed Mar 5, 2023
1 parent 1950767 commit a43dfae
Show file tree
Hide file tree
Showing 14 changed files with 58 additions and 91 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/build-os-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,6 @@ jobs:
gcp)
yq e '.csp = "GCP" |
.image = "${{ needs.build-settings.outputs.imageNameShort }}" |
.measurements.0.warnOnly = false |
.measurements.0.expected = "0f35c214608d93c7a6e68ae7359b4a8be5a0e99eea9107ece427c4dea4e439cf" |
.measurements.1.warnOnly = true |
.measurements.1.expected = "745f2fb4235e4647aa0ad5ace781cd929eb68c28870e7dd5d1a1535854325e56" |
.measurements.2.warnOnly = true |
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/architecture/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ The latter means that the value can be generated offline and compared to the one

| PCR | Components | Measured by | Reproducible and verifiable |
| ----------- | ---------------------------------------------------------------- | ----------------------------- | --------------------------- |
| 0 | CVM constant string | GCP | No |
| 0 | CVM version and technology | GCP | No |
| 1 | Firmware | GCP | No |
| 2 | Firmware | GCP | No |
| 3 | Firmware | GCP | No |
Expand Down
3 changes: 2 additions & 1 deletion internal/attestation/aws/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"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/tpm2"
)

Expand Down Expand Up @@ -54,7 +55,7 @@ func getTrustedKey(akPub []byte, instanceInfo []byte) (crypto.PublicKey, error)
}

// tpmEnabled verifies if the virtual machine has the tpm2.0 feature enabled.
func (v *Validator) tpmEnabled(attestation vtpm.AttestationDocument) error {
func (v *Validator) tpmEnabled(attestation vtpm.AttestationDocument, _ *attest.MachineState) error {
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-nitrotpm-support-on-ami.html
// 1. Get the vm's ami (from IdentiTyDocument.imageId)
// 2. Check the value of key "TpmSupport": {"Value": "v2.0"}"
Expand Down
2 changes: 1 addition & 1 deletion internal/attestation/aws/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func TestTpmEnabled(t *testing.T) {
},
}

err := v.tpmEnabled(tc.attDoc)
err := v.tpmEnabled(tc.attDoc, nil)
if tc.wantErr {
assert.Error(err)
} else {
Expand Down
3 changes: 2 additions & 1 deletion internal/attestation/azure/snp/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
internalCrypto "github.com/edgelesssys/constellation/v2/internal/crypto"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm/tpm2"
)

Expand Down Expand Up @@ -55,7 +56,7 @@ func NewValidator(pcrs measurements.M, idKeyDigests idkeydigest.IDKeyDigests, en
}

// validateCVM is a stub, since SEV-SNP attestation is already verified in trustedKeyFromSNP().
func validateCVM(attestation vtpm.AttestationDocument) error {
func validateCVM(vtpm.AttestationDocument, *attest.MachineState) error {
return nil
}

Expand Down
23 changes: 1 addition & 22 deletions internal/attestation/azure/snp/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,28 +307,7 @@ func int32ToByte(val uint32) []byte {
}

func TestValidateAzureCVM(t *testing.T) {
testCases := map[string]struct {
attDoc vtpm.AttestationDocument
wantErr bool
}{
"success": {
attDoc: vtpm.AttestationDocument{},
wantErr: false,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)

err := validateCVM(tc.attDoc)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
})
}
assert.NoError(t, validateCVM(vtpm.AttestationDocument{}, nil))
}

func TestNewSNPReportFromBytes(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion internal/attestation/azure/trustedlaunch/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
certutil "github.com/edgelesssys/constellation/v2/internal/crypto"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm/tpm2"
)

Expand Down Expand Up @@ -97,7 +98,7 @@ func (v *Validator) verifyAttestationKey(akPub, instanceInfo []byte) (crypto.Pub
}

// validateVM returns nil.
func validateVM(attestation vtpm.AttestationDocument) error {
func validateVM(vtpm.AttestationDocument, *attest.MachineState) error {
return nil
}

Expand Down
33 changes: 14 additions & 19 deletions internal/attestation/gcp/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand All @@ -40,7 +39,7 @@ func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
Validator: vtpm.NewValidator(
pcrs,
trustedKeyFromGCEAPI(newInstanceClient),
gceNonHostInfoEvent,
validateCVM,
log,
),
}
Expand Down Expand Up @@ -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) error {
if attDoc.Attestation == nil {
return errors.New("missing attestation in attestation document")
}
// 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/[email protected]/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")
// validateCVM checks that the machine state represents a GCE AMD-SEV VM.
func validateCVM(attDoc vtpm.AttestationDocument, state *attest.MachineState) error {
gceVersion := state.Platform.GetGceVersion()
if gceVersion < minimumGceVersion {
return fmt.Errorf("outdated GCE version: %v (require >= %v)", gceVersion, minimumGceVersion)
}
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
}
47 changes: 22 additions & 25 deletions internal/attestation/gcp/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,43 @@ 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,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
err := gceNonHostInfoEvent(tc.attDoc)
err := validateCVM(vtpm.AttestationDocument{}, tc.state)
if tc.wantErr {
assert.Error(err)
} else {
Expand Down
4 changes: 0 additions & 4 deletions internal/attestation/measurements/measurements_oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ func DefaultsFor(provider cloudprovider.Provider) M {
}
case cloudprovider.GCP:
return M{
0: {
Expected: [32]byte{0x0F, 0x35, 0xC2, 0x14, 0x60, 0x8D, 0x93, 0xC7, 0xA6, 0xE6, 0x8A, 0xE7, 0x35, 0x9B, 0x4A, 0x8B, 0xE5, 0xA0, 0xE9, 0x9E, 0xEA, 0x91, 0x07, 0xEC, 0xE4, 0x27, 0xC4, 0xDE, 0xA4, 0xE4, 0x39, 0xCF},
WarnOnly: false,
},
4: PlaceHolderMeasurement(),
8: WithAllBytes(0x00, false),
9: PlaceHolderMeasurement(),
Expand Down
3 changes: 2 additions & 1 deletion internal/attestation/qemu/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"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/tpm2"
)

Expand All @@ -27,7 +28,7 @@ func NewValidator(pcrs measurements.M, log vtpm.AttestationLogger) *Validator {
Validator: vtpm.NewValidator(
pcrs,
unconditionalTrust,
func(attestation vtpm.AttestationDocument) error { return nil },
func(vtpm.AttestationDocument, *attest.MachineState) error { return nil },
log,
),
}
Expand Down
17 changes: 9 additions & 8 deletions internal/attestation/vtpm/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type (
// GetInstanceInfo returns VM metdata.
GetInstanceInfo func(tpm io.ReadWriteCloser) ([]byte, error)
// ValidateCVM validates confidential computing capabilities of the instance issuing the attestation.
ValidateCVM func(attestation AttestationDocument) error
ValidateCVM func(attestation AttestationDocument, state *attest.MachineState) error
)

// AttestationLogger is a logger used to print warnings and infos during attestation validation.
Expand Down Expand Up @@ -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); 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 {
Expand Down
4 changes: 2 additions & 2 deletions internal/attestation/vtpm/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func fakeGetInstanceInfo(tpm io.ReadWriteCloser) ([]byte, error) {
func TestValidate(t *testing.T) {
require := require.New(t)

fakeValidateCVM := func(AttestationDocument) error { return nil }
fakeValidateCVM := func(AttestationDocument, *attest.MachineState) error { return nil }
fakeGetTrustedKey := func(aKPub, instanceInfo []byte) (crypto.PublicKey, error) {
pubArea, err := tpm2.DecodePublic(aKPub)
if err != nil {
Expand Down Expand Up @@ -186,7 +186,7 @@ func TestValidate(t *testing.T) {
validator: NewValidator(
testExpectedPCRs,
fakeGetTrustedKey,
func(attestation AttestationDocument) error {
func(AttestationDocument, *attest.MachineState) error {
return errors.New("untrusted")
},
warnLog),
Expand Down
3 changes: 0 additions & 3 deletions internal/config/testdata/configGCPV2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ provider:
stateDiskType: pd-ssd
deployCSIDriver: true
measurements:
0:
expected: 0f35c214608d93c7a6e68ae7359b4a8be5a0e99eea9107ece427c4dea4e439cf
warnOnly: false
4:
expected: "1234123412341234123412341234123412341234123412341234123412341234"
warnOnly: false
Expand Down

0 comments on commit a43dfae

Please sign in to comment.