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

attestation: validate GCP machine state instead of PCR 0 #1343

Merged
merged 3 commits into from
Mar 6, 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
4 changes: 0 additions & 4 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 All @@ -680,8 +678,6 @@ jobs:
.measurements.7.expected = "b1e9b305325c51b93da58cbf7f92512d8eebfa01143e4d8844e40e062e9b6cd5" |
.measurements.8.warnOnly = false |
.measurements.9.warnOnly = false |
.measurements.10.warnOnly = true |
.measurements.10.expected = "7f96fbc55e1d2a0de46e5d44658c06ef102d1198703efa69f2ea6b5aa1c9a176" |
.measurements.11.warnOnly = false |
.measurements.12.warnOnly = false |
.measurements.13.warnOnly = false |
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(_ 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