Skip to content

Commit

Permalink
Be much stricter when processing Attested COS State
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Richey <joerichey@google.com>
  • Loading branch information
josephlr committed Sep 10, 2022
1 parent 7e6cdc9 commit 4a50da2
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 84 deletions.
6 changes: 5 additions & 1 deletion cel/cos_tlv.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
)

const (
// CosEventType indicate the CELR event is a COS content
// CosEventType indicates the CELR event is a COS content
// TODO: the value needs to be reserved in the CEL spec
CosEventType uint8 = 80
// CosEventPCR is the PCR which should be used for CosEventType events.
CosEventPCR = 13
)

// CosType represent a COS content type in a CEL record content.
Expand All @@ -27,6 +29,8 @@ const (
EnvVarType
OverrideArgType
OverrideEnvType
// EventContent is empty on success, or contains an error message on failure.
LaunchSeparatorType
)

// CosTlv is a specific event type created for the COS (Google Container-Optimized OS),
Expand Down
8 changes: 8 additions & 0 deletions internal/test/test_tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ func GetTPM(tb testing.TB) io.ReadWriteCloser {
return noClose{tpm}
}

// SkipForRealTPM causes a test or benchmark to be skipped if we are not using
// a test TPM. This lets us avoid clobbering important PCRs on a real machine.
func SkipForRealTPM(tb testing.TB) {
if useRealTPM() {
tb.Skip("Running against a real TPM, Skipping Test")
}
}

// GetSimulatorWithLog returns a simulated TPM with PCRs that match the events
// of the passed in eventlog. This allows for testing attestation flows.
func GetSimulatorWithLog(tb testing.TB, eventLog []byte) io.ReadWriteCloser {
Expand Down
4 changes: 1 addition & 3 deletions launcher/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import (
pb "github.com/google/go-tpm-tools/proto/attest"
)

const defaultCELPCR = 13

var defaultCELHashAlgo = []crypto.Hash{crypto.SHA256, crypto.SHA1}

type tpmKeyFetcher func(rw io.ReadWriter) (*client.Key, error)
Expand Down Expand Up @@ -61,7 +59,7 @@ func CreateAttestationAgent(tpm io.ReadWriteCloser, akFetcher tpmKeyFetcher, ver
// MeasureEvent takes in a cel.Content and appends it to the CEL eventlog
// under the attestation agent.
func (a *agent) MeasureEvent(event cel.Content) error {
return a.cosCel.AppendEvent(a.tpm, defaultCELPCR, defaultCELHashAlgo, event)
return a.cosCel.AppendEvent(a.tpm, cel.CosEventPCR, defaultCELHashAlgo, event)
}

// Attest fetches the nonce and connection ID from the Attestation Service,
Expand Down
28 changes: 26 additions & 2 deletions server/eventlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,18 @@ func getVerifiedCosState(coscel cel.CEL, pcrs *tpmpb.PCRs) (*pb.AttestedCosState
cosState.Container.EnvVars = make(map[string]string)
cosState.Container.OverriddenEnvVars = make(map[string]string)

seenSeparator := false
for _, record := range coscel.Records {
// ignore non COS CEL events
if !record.Content.IsCosTlv() {
// COS State only comes from the CosEventPCR
if record.PCR != cel.CosEventPCR {
continue
}

// The Content.Type is not verified at this point, so we have to fail
// if we see any events that we do not understand. This ensures that
// we either verify the digest of event event in this PCR, or we fail
// to replay the event log.
// TODO: See if we can fix this to have the Content Type be verified.
cosTlv, err := record.Content.ParseToCosTlv()
if err != nil {
return nil, err
Expand All @@ -135,11 +141,22 @@ func getVerifiedCosState(coscel cel.CEL, pcrs *tpmpb.PCRs) (*pb.AttestedCosState
return nil, err
}

// TODO: Add support for post-separator container data
if seenSeparator {
return nil, fmt.Errorf("found COS Event Type %v after LaunchSeparator event", cosTlv.EventType)
}

switch cosTlv.EventType {
case cel.ImageRefType:
if cosState.Container.GetImageReference() != "" {
return nil, fmt.Errorf("found duplicate ImageRef event")
}
cosState.Container.ImageReference = string(cosTlv.EventContent)

case cel.ImageDigestType:
if cosState.Container.GetImageDigest() != "" {
return nil, fmt.Errorf("found duplicate ImageDigest event")
}
cosState.Container.ImageDigest = string(cosTlv.EventContent)

case cel.RestartPolicyType:
Expand All @@ -150,6 +167,9 @@ func getVerifiedCosState(coscel cel.CEL, pcrs *tpmpb.PCRs) (*pb.AttestedCosState
cosState.Container.RestartPolicy = pb.RestartPolicy(restartPolicy)

case cel.ImageIDType:
if cosState.Container.GetImageId() != "" {
return nil, fmt.Errorf("found duplicate ImageId event")
}
cosState.Container.ImageId = string(cosTlv.EventContent)

case cel.EnvVarType:
Expand All @@ -171,6 +191,10 @@ func getVerifiedCosState(coscel cel.CEL, pcrs *tpmpb.PCRs) (*pb.AttestedCosState
return nil, err
}
cosState.Container.OverriddenEnvVars[envName] = envVal
case cel.LaunchSeparatorType:
seenSeparator = true
default:
return nil, fmt.Errorf("found unknown COS Event Type %v", cosTlv.EventType)
}

}
Expand Down
101 changes: 47 additions & 54 deletions server/eventlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,14 +510,10 @@ func TestParseSecureBootState(t *testing.T) {
}

func TestParsingCELEventLog(t *testing.T) {
test.SkipForRealTPM(t)
tpm := test.GetTPM(t)
defer client.CheckedClose(t, tpm)

err := tpm2.PCRReset(tpm, tpmutil.Handle(test.DebugPCR))
if err != nil {
t.Fatal(err)
}

coscel := &cel.CEL{}
emptyCosState := attestpb.ContainerState{}

Expand Down Expand Up @@ -552,59 +548,23 @@ func TestParsingCELEventLog(t *testing.T) {
}
}

// Secondly, append a random non-COS event, encode and try to parse it. Because there is no COS TLV event,
// we should get an empty/default CosState in the MachineState.
event, err := generateNonCosCelEvent(implmentedHash)
if err != nil {
t.Fatal(err)
}
coscel.Records = append(coscel.Records, event)
buf = bytes.Buffer{}
if err := coscel.EncodeCEL(&buf); err != nil {
t.Fatal(err)
}
// extend digests to the PCR
for _, hash := range implmentedHash {
algo, err := tpm2.HashToAlgorithm(hash)
if err != nil {
t.Fatal(err)
}
if err := tpm2.PCRExtend(tpm, tpmutil.Handle(test.DebugPCR), algo, event.Digests[hash], ""); err != nil {
t.Fatal(err)
}
}
banks, err = client.ReadAllPCRs(tpm)
if err != nil {
t.Fatal(err)
}
for _, bank := range banks {
msState, err := parseCanonicalEventLog(buf.Bytes(), bank)
if err != nil {
t.Errorf("expecting no error from parseCanonicalEventLog(), but get %v", err)
}
// expect nothing in the CosState
if diff := cmp.Diff(msState.Cos.Container, &emptyCosState, protocmp.Transform()); diff != "" {
t.Errorf("unexpected difference:\n%v", diff)
}
}

// Thirdly, append some real COS events to the CEL. This time we should get content in the CosState.
// Secondly, append some real COS events to the CEL. This time we should get content in the CosState.
testCELEvents := []struct {
cosNestedEventType cel.CosType
pcr int
eventPayload []byte
}{
{cel.ImageRefType, test.DebugPCR, []byte("docker.io/bazel/experimental/test:latest")},
{cel.ImageDigestType, test.DebugPCR, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")},
{cel.RestartPolicyType, test.DebugPCR, []byte(attestpb.RestartPolicy_Always.String())},
{cel.ImageIDType, test.DebugPCR, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")},
{cel.EnvVarType, test.DebugPCR, []byte("foo=bar")},
{cel.EnvVarType, test.DebugPCR, []byte("bar=baz")},
{cel.EnvVarType, test.DebugPCR, []byte("baz=foo=bar")},
{cel.EnvVarType, test.DebugPCR, []byte("empty=")},
{cel.ArgType, test.DebugPCR, []byte("--x")},
{cel.ArgType, test.DebugPCR, []byte("--y")},
{cel.ArgType, test.DebugPCR, []byte("")},
{cel.ImageRefType, cel.CosEventPCR, []byte("docker.io/bazel/experimental/test:latest")},
{cel.ImageDigestType, cel.CosEventPCR, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")},
{cel.RestartPolicyType, cel.CosEventPCR, []byte(attestpb.RestartPolicy_Always.String())},
{cel.ImageIDType, cel.CosEventPCR, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")},
{cel.EnvVarType, cel.CosEventPCR, []byte("foo=bar")},
{cel.EnvVarType, cel.CosEventPCR, []byte("bar=baz")},
{cel.EnvVarType, cel.CosEventPCR, []byte("baz=foo=bar")},
{cel.EnvVarType, cel.CosEventPCR, []byte("empty=")},
{cel.ArgType, cel.CosEventPCR, []byte("--x")},
{cel.ArgType, cel.CosEventPCR, []byte("--y")},
{cel.ArgType, cel.CosEventPCR, []byte("")},
}

expectedEnvVars := make(map[string]string)
Expand Down Expand Up @@ -644,12 +604,45 @@ func TestParsingCELEventLog(t *testing.T) {
}
}
}

// Thirdly, append a random non-COS event, encode and try to parse it.
// Because there is no COS TLV event, attestation should fail as we do not
// understand the content type.
event, err := generateNonCosCelEvent(implmentedHash)
if err != nil {
t.Fatal(err)
}
coscel.Records = append(coscel.Records, event)
buf = bytes.Buffer{}
if err := coscel.EncodeCEL(&buf); err != nil {
t.Fatal(err)
}
// extend digests to the PCR
for _, hash := range implmentedHash {
algo, err := tpm2.HashToAlgorithm(hash)
if err != nil {
t.Fatal(err)
}
if err := tpm2.PCRExtend(tpm, tpmutil.Handle(cel.CosEventPCR), algo, event.Digests[hash], ""); err != nil {
t.Fatal(err)
}
}
banks, err = client.ReadAllPCRs(tpm)
if err != nil {
t.Fatal(err)
}
for _, bank := range banks {
_, err := parseCanonicalEventLog(buf.Bytes(), bank)
if err == nil {
t.Errorf("expected error when parsing event log with unknown content type")
}
}
}

func generateNonCosCelEvent(hashAlgoList []crypto.Hash) (cel.Record, error) {
randRecord := cel.Record{}
randRecord.RecNum = 0
randRecord.PCR = uint8(test.DebugPCR)
randRecord.PCR = cel.CosEventPCR
contentValue := make([]byte, 10)
rand.Read(contentValue)
randRecord.Content = cel.TLV{Type: 250, Value: contentValue}
Expand Down
40 changes: 16 additions & 24 deletions server/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,14 +293,10 @@ func TestVerifySHA1Attestation(t *testing.T) {
}

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

err := tpm2.PCRReset(rwc, 16)
if err != nil {
t.Fatal(err)
}

ak, err := client.AttestationKeyRSA(rwc)
if err != nil {
t.Fatalf("failed to generate AK: %v", err)
Expand All @@ -313,18 +309,18 @@ func TestVerifyAttestationWithCEL(t *testing.T) {
pcr int
eventPayload []byte
}{
{cel.ImageRefType, test.DebugPCR, []byte("docker.io/bazel/experimental/test:latest")},
{cel.ImageDigestType, test.DebugPCR, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")},
{cel.RestartPolicyType, test.DebugPCR, []byte(attestpb.RestartPolicy_Never.String())},
{cel.ImageIDType, test.DebugPCR, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")},
{cel.EnvVarType, test.DebugPCR, []byte("foo=bar")},
{cel.EnvVarType, test.DebugPCR, []byte("bar=baz")},
{cel.EnvVarType, test.DebugPCR, []byte("baz=foo=bar")},
{cel.EnvVarType, test.DebugPCR, []byte("empty=")},
{cel.ArgType, test.DebugPCR, []byte("--x")},
{cel.ArgType, test.DebugPCR, []byte("--y")},
{cel.OverrideArgType, test.DebugPCR, []byte("--x")},
{cel.OverrideEnvType, test.DebugPCR, []byte("empty=")},
{cel.ImageRefType, cel.CosEventPCR, []byte("docker.io/bazel/experimental/test:latest")},
{cel.ImageDigestType, cel.CosEventPCR, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")},
{cel.RestartPolicyType, cel.CosEventPCR, []byte(attestpb.RestartPolicy_Never.String())},
{cel.ImageIDType, cel.CosEventPCR, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")},
{cel.EnvVarType, cel.CosEventPCR, []byte("foo=bar")},
{cel.EnvVarType, cel.CosEventPCR, []byte("bar=baz")},
{cel.EnvVarType, cel.CosEventPCR, []byte("baz=foo=bar")},
{cel.EnvVarType, cel.CosEventPCR, []byte("empty=")},
{cel.ArgType, cel.CosEventPCR, []byte("--x")},
{cel.ArgType, cel.CosEventPCR, []byte("--y")},
{cel.OverrideArgType, cel.CosEventPCR, []byte("--x")},
{cel.OverrideEnvType, cel.CosEventPCR, []byte("empty=")},
}
for _, testEvent := range testEvents {
cos := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload}
Expand Down Expand Up @@ -378,14 +374,10 @@ func TestVerifyAttestationWithCEL(t *testing.T) {
}

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

err := tpm2.PCRReset(rwc, tpmutil.Handle(test.DebugPCR))
if err != nil {
t.Fatal(err)
}

ak, err := client.AttestationKeyRSA(rwc)
if err != nil {
t.Fatalf("failed to generate AK: %v", err)
Expand All @@ -396,10 +388,10 @@ func TestVerifyFailWithTamperedCELContent(t *testing.T) {

cosEvent := cel.CosTlv{EventType: cel.ImageRefType, EventContent: []byte("docker.io/bazel/experimental/test:latest")}
cosEvent2 := cel.CosTlv{EventType: cel.ImageDigestType, EventContent: []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")}
if err := c.AppendEvent(rwc, test.DebugPCR, measuredHashes, cosEvent); err != nil {
if err := c.AppendEvent(rwc, cel.CosEventPCR, measuredHashes, cosEvent); err != nil {
t.Fatalf("failed to append event: %v", err)
}
if err := c.AppendEvent(rwc, test.DebugPCR, measuredHashes, cosEvent2); err != nil {
if err := c.AppendEvent(rwc, cel.CosEventPCR, measuredHashes, cosEvent2); err != nil {
t.Fatalf("failed to append event: %v", err)
}

Expand Down

0 comments on commit 4a50da2

Please sign in to comment.