From be004c971da880a790709661b5e929d76177a8e2 Mon Sep 17 00:00:00 2001 From: Malte Poll Date: Mon, 11 Apr 2022 10:34:15 +0200 Subject: [PATCH] Coordinator vTPM: add method to check for previous node initialization Signed-off-by: Malte Poll --- coordinator/attestation/vtpm/initialize.go | 57 +++++++++++++++++++ .../attestation/vtpm/initialize_test.go | 50 ++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/coordinator/attestation/vtpm/initialize.go b/coordinator/attestation/vtpm/initialize.go index b105a3cd0..9087d7e55 100644 --- a/coordinator/attestation/vtpm/initialize.go +++ b/coordinator/attestation/vtpm/initialize.go @@ -1,6 +1,9 @@ package vtpm import ( + "errors" + "fmt" + "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil" ) @@ -29,3 +32,57 @@ func MarkNodeAsInitialized(openTPM TPMOpenFunc, ownerID, clusterID []byte) error // clusterID is used to uniquely identify this running instance of Constellation return tpm2.PCREvent(tpm, PCRIndexClusterID, clusterID) } + +// IsNodeInitialized checks if a node is already initialized by reading PCRs. +func IsNodeInitialized(openTPM TPMOpenFunc) (bool, error) { + tpm, err := openTPM() + if err != nil { + return false, err + } + defer tpm.Close() + + idxOwner := int(PCRIndexOwnerID) + idxCluster := int(PCRIndexClusterID) + selection := tpm2.PCRSelection{ + Hash: tpm2.AlgSHA256, + PCRs: []int{idxOwner, idxCluster}, + } + + pcrs, err := tpm2.ReadPCRs(tpm, selection) + if err != nil { + return false, err + } + + if len(pcrs[idxOwner]) == 0 { + return false, errors.New("owner ID PCR does not exist") + } + if len(pcrs[idxCluster]) == 0 { + return false, errors.New("cluster ID PCR does not exist") + } + + ownerInitialized := pcrInitialized(pcrs[idxOwner]) + clusterInitialized := pcrInitialized(pcrs[idxCluster]) + + if ownerInitialized == clusterInitialized { + return ownerInitialized && clusterInitialized, nil + } + ownerState := "not initialized" + if ownerInitialized { + ownerState = "initialized" + } + clusterState := "not initialized" + if clusterInitialized { + clusterState = "initialized" + } + return false, fmt.Errorf("PCRs %v and %v are not consistent: PCR[%v]=%v (%v), PCR[%v]=%v (%v)", idxOwner, idxCluster, idxOwner, pcrs[idxOwner], ownerState, idxCluster, pcrs[idxCluster], clusterState) +} + +// pcrInitialized checks if a PCR value is set to a non-zero value. +func pcrInitialized(pcr []byte) bool { + for _, b := range pcr { + if b != 0 { + return true + } + } + return false +} diff --git a/coordinator/attestation/vtpm/initialize_test.go b/coordinator/attestation/vtpm/initialize_test.go index c010a7058..a2db1ac1d 100644 --- a/coordinator/attestation/vtpm/initialize_test.go +++ b/coordinator/attestation/vtpm/initialize_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/google/go-tpm-tools/client" + "github.com/google/go-tpm/tpm2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -47,3 +48,52 @@ func TestFailOpener(t *testing.T) { assert.Error(MarkNodeAsInitialized(func() (io.ReadWriteCloser, error) { return nil, errors.New("failed") }, []byte{0x0, 0x1, 0x2, 0x3}, []byte{0x0, 0x1, 0x2, 0x3})) } + +func TestIsNodeInitialized(t *testing.T) { + testCases := map[string]struct { + pcrValueOwnerID []byte + pcrValueClusterID []byte + expectInitialized bool + expectErr bool + }{ + "uninitialized PCRs results in uninitialized node": {}, + "initializing PCRs result in initialized node": { + pcrValueOwnerID: []byte{0x0, 0x1, 0x2, 0x3}, + pcrValueClusterID: []byte{0x4, 0x5, 0x6, 0x7}, + expectInitialized: true, + }, + "initializing ownerID alone fails": { + pcrValueOwnerID: []byte{0x0, 0x1, 0x2, 0x3}, + expectErr: true, + }, + "initializing clusterID alone fails": { + pcrValueClusterID: []byte{0x4, 0x5, 0x6, 0x7}, + expectErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := require.New(t) + require := require.New(t) + tpm, err := OpenSimulatedTPM() + require.NoError(err) + defer tpm.Close() + if tc.pcrValueOwnerID != nil { + require.NoError(tpm2.PCREvent(tpm, PCRIndexOwnerID, tc.pcrValueOwnerID)) + } + if tc.pcrValueClusterID != nil { + require.NoError(tpm2.PCREvent(tpm, PCRIndexClusterID, tc.pcrValueClusterID)) + } + initialized, err := IsNodeInitialized(func() (io.ReadWriteCloser, error) { + return &simTPMNOPCloser{tpm}, nil + }) + if tc.expectErr { + assert.Error(err) + return + } + require.NoError(err) + require.Equal(tc.expectInitialized, initialized) + }) + } +}