diff --git a/cel/canonical_eventlog.go b/cel/canonical_eventlog.go index 3cfcfdb7a..e9fd583e1 100644 --- a/cel/canonical_eventlog.go +++ b/cel/canonical_eventlog.go @@ -9,23 +9,27 @@ import ( "fmt" "io" - pb "github.com/google/go-tpm-tools/proto/tpm" + "github.com/google/go-configfs-tsm/configfs/configfsi" + "github.com/google/go-tdx-guest/rtmr" "github.com/google/go-tpm/legacy/tpm2" "github.com/google/go-tpm/tpmutil" ) const ( // CEL spec 5.1 - recnumTypeValue uint8 = 0 - pcrTypeValue uint8 = 1 + recnumTypeValue uint8 = 0 + // PcrTypeValue indicates a PCR event index + PcrTypeValue uint8 = 1 _ uint8 = 2 // nvindex field is not supported yet digestsTypeValue uint8 = 3 + // RtmrTypeValue indicates a RTMR event index + RtmrTypeValue uint8 = 108 // not in the CEL spec tlvTypeFieldLength int = 1 tlvLengthFieldLength int = 4 - recnumValueLength uint32 = 8 // support up to 2^64 records - pcrValueLength uint32 = 1 // support up to 256 PCRs + recnumValueLength uint32 = 8 // support up to 2^64 records + regIndexValueLength uint32 = 1 // support up to 256 registers ) // TLV definition according to CEL spec TCG_IWG_CEL_v1_r0p37, page 16. @@ -100,10 +104,13 @@ func UnmarshalFirstTLV(buf *bytes.Buffer) (tlv TLV, err error) { // Record represents a Canonical Eventlog Record. type Record struct { - RecNum uint64 - PCR uint8 - Digests map[crypto.Hash][]byte - Content TLV + RecNum uint64 + // Generic Measurement Register index number, register type + // is determined by IndexType + Index uint8 + IndexType uint8 + Digests map[crypto.Hash][]byte + Content TLV } // Content is a interface for the content in CELR. @@ -117,25 +124,62 @@ type CEL struct { Records []Record } -// AppendEvent appends a new record to the CEL. -func (c *CEL) AppendEvent(tpm io.ReadWriteCloser, pcr int, hashAlgos []crypto.Hash, event Content) error { - if len(hashAlgos) == 0 { - return fmt.Errorf("need to specify at least one hash algorithm") - } +// generateDigestMap computes hashes with the given hash algos and the given event +func generateDigestMap(hashAlgos []crypto.Hash, event Content) (map[crypto.Hash][]byte, error) { digestsMap := make(map[crypto.Hash][]byte) - for _, hashAlgo := range hashAlgos { digest, err := event.GenerateDigest(hashAlgo) if err != nil { - return err + return digestsMap, err } digestsMap[hashAlgo] = digest + } + return digestsMap, nil +} - tpm2Alg, err := tpm2.HashToAlgorithm(hashAlgo) +// AppendEventRTMR appends a new RTMR record to the CEL. rtmrIndex indicates the RTMR to extend. +// The index showing up in the record will be rtmrIndex + 1. +func (c *CEL) AppendEventRTMR(client configfsi.Client, rtmrIndex int, event Content) error { + digestsMap, err := generateDigestMap([]crypto.Hash{crypto.SHA384}, event) + if err != nil { + return err + } + + eventTlv, err := event.GetTLV() + if err != nil { + return err + } + + err = rtmr.ExtendDigestClient(client, rtmrIndex, digestsMap[crypto.SHA384]) + if err != nil { + return err + } + + celrRTMR := Record{ + RecNum: uint64(len(c.Records)), + Index: uint8(rtmrIndex) + 1, + Digests: digestsMap, + Content: eventTlv, + IndexType: RtmrTypeValue, + } + + c.Records = append(c.Records, celrRTMR) + return nil +} + +// AppendEventPCR appends a new PCR record to the CEL. +func (c *CEL) AppendEventPCR(tpm io.ReadWriteCloser, pcr int, hashAlgos []crypto.Hash, event Content) error { + digestsMap, err := generateDigestMap(hashAlgos, event) + if err != nil { + return err + } + + for hs, dgst := range digestsMap { + tpm2Alg, err := tpm2.HashToAlgorithm(hs) if err != nil { return err } - if err := tpm2.PCRExtend(tpm, tpmutil.Handle(pcr), tpm2Alg, digest, ""); err != nil { + if err := tpm2.PCRExtend(tpm, tpmutil.Handle(pcr), tpm2Alg, dgst, ""); err != nil { return fmt.Errorf("failed to extend event to PCR%d: %v", pcr, err) } } @@ -145,14 +189,15 @@ func (c *CEL) AppendEvent(tpm io.ReadWriteCloser, pcr int, hashAlgos []crypto.Ha return err } - celr := Record{ - RecNum: uint64(len(c.Records)), - PCR: uint8(pcr), - Digests: digestsMap, - Content: eventTlv, + celrPCR := Record{ + RecNum: uint64(len(c.Records)), + Index: uint8(pcr), + Digests: digestsMap, + Content: eventTlv, + IndexType: PcrTypeValue, } - c.Records = append(c.Records, celr) + c.Records = append(c.Records, celrPCR) return nil } @@ -177,24 +222,24 @@ func unmarshalRecNum(tlv TLV) (uint64, error) { return binary.BigEndian.Uint64(tlv.Value), nil } -func createPCRField(pcrNum uint8) TLV { - return TLV{pcrTypeValue, []byte{pcrNum}} +func createIndexField(indexType uint8, indexNum uint8) TLV { + return TLV{indexType, []byte{indexNum}} } // UnmarshalPCR takes in a TLV with its type equals to the PCR type value (1), and // return its PCR number. -func unmarshalPCR(tlv TLV) (pcrNum uint8, err error) { - if tlv.Type != pcrTypeValue { - return 0, fmt.Errorf("type of the TLV [%d] indicates it is not a PCR field [%d]", - tlv.Type, pcrTypeValue) +func unmarshalIndex(tlv TLV) (indexType uint8, pcrNum uint8, err error) { + if tlv.Type != PcrTypeValue && tlv.Type != RtmrTypeValue { + return 0, 0, fmt.Errorf("type of the TLV [%d] indicates it is not a PCR [%d] or a RTMR [%d] field ", + tlv.Type, PcrTypeValue, RtmrTypeValue) } - if uint32(len(tlv.Value)) != pcrValueLength { - return 0, fmt.Errorf( - "length of the value of the TLV [%d] doesn't match the defined length [%d] of value for a PCR field", - len(tlv.Value), pcrValueLength) + if uint32(len(tlv.Value)) != regIndexValueLength { + return 0, 0, fmt.Errorf( + "length of the value of the TLV [%d] doesn't match the defined length [%d] of value for a register index field", + len(tlv.Value), regIndexValueLength) } - return tlv.Value[0], nil + return tlv.Type, tlv.Value[0], nil } func createDigestField(digestMap map[crypto.Hash][]byte) (TLV, error) { @@ -254,7 +299,8 @@ func (r *Record) EncodeCELR(buf *bytes.Buffer) error { if err != nil { return err } - pcrField, err := createPCRField(r.PCR).MarshalBinary() + + indexField, err := createIndexField(r.IndexType, r.Index).MarshalBinary() if err != nil { return err } @@ -274,7 +320,7 @@ func (r *Record) EncodeCELR(buf *bytes.Buffer) error { if err != nil { return err } - _, err = buf.Write(pcrField) + _, err = buf.Write(indexField) if err != nil { return err } @@ -329,11 +375,11 @@ func DecodeToCELR(buf *bytes.Buffer) (r Record, err error) { return Record{}, err } - pcr, err := UnmarshalFirstTLV(buf) + regIndex, err := UnmarshalFirstTLV(buf) if err != nil { return Record{}, err } - r.PCR, err = unmarshalPCR(pcr) + r.IndexType, r.Index, err = unmarshalIndex(regIndex) if err != nil { return Record{}, err } @@ -355,18 +401,13 @@ func DecodeToCELR(buf *bytes.Buffer) (r Record, err error) { } // Replay takes the digests from a Canonical Event Log and carries out the -// extend sequence for each PCR in the log. It then compares the final digests -// against a bank of PCR values to see if they match. -func (c *CEL) Replay(bank *pb.PCRs) error { - tpm2Alg := tpm2.Algorithm(bank.GetHash()) - cryptoHash, err := tpm2Alg.Hash() - if err != nil { - return err - } +// extend sequence for each register (PCR, RTMR) in the log. It then compares +// the final digests against a bank of register values to see if they match. +func (c *CEL) Replay(register map[uint32][]byte, cryptoHash crypto.Hash) error { replayed := make(map[uint8][]byte) for _, record := range c.Records { - if _, ok := replayed[record.PCR]; !ok { - replayed[record.PCR] = make([]byte, cryptoHash.Size()) + if _, ok := replayed[record.Index]; !ok { + replayed[record.Index] = make([]byte, cryptoHash.Size()) } hasher := cryptoHash.New() digestsMap := record.Digests @@ -374,16 +415,16 @@ func (c *CEL) Replay(bank *pb.PCRs) error { if !ok { return fmt.Errorf("the CEL record did not contain a %v digest", cryptoHash) } - hasher.Write(replayed[record.PCR]) + hasher.Write(replayed[record.Index]) hasher.Write(digest) - replayed[record.PCR] = hasher.Sum(nil) + replayed[record.Index] = hasher.Sum(nil) } var failedReplayPcrs []uint8 for replayPcr, replayDigest := range replayed { - bankDigest, ok := bank.Pcrs[uint32(replayPcr)] + bankDigest, ok := register[uint32(replayPcr)] if !ok { - return fmt.Errorf("the CEL contained record(s) for PCR%d without a matching PCR in the bank to verify", replayPcr) + return fmt.Errorf("the CEL contained record(s) for register %d without a matching register in the bank to verify", replayPcr) } if !bytes.Equal(bankDigest, replayDigest) { failedReplayPcrs = append(failedReplayPcrs, replayPcr) @@ -394,7 +435,7 @@ func (c *CEL) Replay(bank *pb.PCRs) error { return nil } - return fmt.Errorf("CEL replay failed for these PCRs in bank %v: %v", cryptoHash, failedReplayPcrs) + return fmt.Errorf("CEL replay failed for these registers in bank %v: %v", cryptoHash, failedReplayPcrs) } // VerifyDigests checks the digest generated by the given record's content to make sure they are equal to diff --git a/cel/canonical_eventlog_test.go b/cel/canonical_eventlog_test.go index 82779bb32..8e8654b2c 100644 --- a/cel/canonical_eventlog_test.go +++ b/cel/canonical_eventlog_test.go @@ -8,6 +8,9 @@ import ( "reflect" "testing" + "github.com/google/go-configfs-tsm/configfs/configfsi" + "github.com/google/go-configfs-tsm/configfs/fakertmr" + configfstsmrtmr "github.com/google/go-configfs-tsm/rtmr" "github.com/google/go-tpm-tools/client" "github.com/google/go-tpm-tools/internal/test" pb "github.com/google/go-tpm-tools/proto/tpm" @@ -24,10 +27,10 @@ func TestCELEncodingDecoding(t *testing.T) { cel := &CEL{} cosEvent := CosTlv{ImageDigestType, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")} - appendOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent) + appendPcrEventOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent) cosEvent2 := CosTlv{ImageRefType, []byte("docker.io/bazel/experimental/test:latest")} - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent2) + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent2) var buf bytes.Buffer if err := cel.EncodeCEL(&buf); err != nil { @@ -46,10 +49,16 @@ func TestCELEncodingDecoding(t *testing.T) { if decodedcel.Records[1].RecNum != 1 { t.Errorf("recnum mismatch") } - if decodedcel.Records[0].PCR != uint8(test.DebugPCR) { + if decodedcel.Records[0].IndexType != PcrTypeValue { + t.Errorf("index type mismatch") + } + if decodedcel.Records[0].Index != uint8(test.DebugPCR) { t.Errorf("pcr value mismatch") } - if decodedcel.Records[1].PCR != uint8(test.ApplicationPCR) { + if decodedcel.Records[1].IndexType != PcrTypeValue { + t.Errorf("index type mismatch") + } + if decodedcel.Records[1].Index != uint8(test.ApplicationPCR) { t.Errorf("pcr value mismatch") } @@ -62,6 +71,8 @@ func TestCELMeasureAndReplay(t *testing.T) { tpm := test.GetTPM(t) defer client.CheckedClose(t, tpm) + fakeRTMR := fakertmr.CreateRtmrSubsystem(t.TempDir()) + err := tpm2.PCRReset(tpm, tpmutil.Handle(test.DebugPCR)) if err != nil { t.Fatal(err) @@ -72,23 +83,36 @@ func TestCELMeasureAndReplay(t *testing.T) { } cel := &CEL{} + celRTMR := &CEL{} cosEvent := CosTlv{ImageRefType, []byte("docker.io/bazel/experimental/test:latest")} + someEvent2 := make([]byte, 10) rand.Read(someEvent2) cosEvent2 := CosTlv{ImageDigestType, someEvent2} - appendOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent) - appendOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent2) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent2) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) + appendPcrEventOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent) + appendRtmrEventOrFatal(t, celRTMR, fakeRTMR, CosRTMR, cosEvent) + + appendPcrEventOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent2) + appendRtmrEventOrFatal(t, celRTMR, fakeRTMR, CosRTMR, cosEvent) + + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent2) + appendRtmrEventOrFatal(t, celRTMR, fakeRTMR, CosRTMR, cosEvent2) + + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) + appendRtmrEventOrFatal(t, celRTMR, fakeRTMR, CosRTMR, cosEvent) + + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) + appendRtmrEventOrFatal(t, celRTMR, fakeRTMR, CosRTMR, cosEvent) replay(t, cel, tpm, measuredHashes, []int{test.DebugPCR, test.ApplicationPCR}, true /*shouldSucceed*/) // Supersets should pass. replay(t, cel, tpm, measuredHashes, []int{0, 13, 14, test.DebugPCR, 22, test.ApplicationPCR}, true /*shouldSucceed*/) + + replayRTMR(t, celRTMR, fakeRTMR, crypto.SHA384, []int{0, 1, 2, 3}, true /*shouldSucceed*/) } func TestCELReplayFailTamperedDigest(t *testing.T) { @@ -99,15 +123,14 @@ func TestCELReplayFailTamperedDigest(t *testing.T) { cosEvent := CosTlv{ImageRefType, []byte("docker.io/bazel/experimental/test:latest")} someEvent2 := make([]byte, 10) - rand.Read(someEvent2) cosEvent2 := CosTlv{ImageDigestType, someEvent2} - appendOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent) - appendOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent2) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent2) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) + appendPcrEventOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent) + appendPcrEventOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, cosEvent2) + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent2) + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, cosEvent) modifiedRecord := cel.Records[3] for hash := range modifiedRecord.Digests { @@ -137,8 +160,10 @@ func TestCELReplayFailMissingPCRsInBank(t *testing.T) { someEvent := make([]byte, 10) someEvent2 := make([]byte, 10) rand.Read(someEvent2) - appendOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, CosTlv{ImageRefType, someEvent}) - appendOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, CosTlv{ImageDigestType, someEvent2}) + + appendPcrEventOrFatal(t, cel, tpm, test.DebugPCR, measuredHashes, CosTlv{ImageRefType, someEvent}) + appendPcrEventOrFatal(t, cel, tpm, test.ApplicationPCR, measuredHashes, CosTlv{ImageDigestType, someEvent2}) + replay(t, cel, tpm, measuredHashes, []int{test.DebugPCR}, false /*shouldSucceed*/) replay(t, cel, tpm, measuredHashes, @@ -161,15 +186,38 @@ func replay(t *testing.T, cel *CEL, tpm io.ReadWriteCloser, measuredHashes []cry for index, val := range pcrMap { pbPcr.Pcrs[uint32(index)] = val } - if err := cel.Replay(pbPcr); shouldSucceed && err != nil { + + if err := cel.Replay(pbPcr.GetPcrs(), hash); shouldSucceed && err != nil { t.Errorf("failed to replay CEL on %v bank: %v", pb.HashAlgo_name[int32(pbPcr.Hash)], err) } } } -func appendOrFatal(t *testing.T, cel *CEL, tpm io.ReadWriteCloser, pcr int, hashAlgos []crypto.Hash, event Content) { - if err := cel.AppendEvent(tpm, pcr, hashAlgos, event); err != nil { - t.Fatalf("failed to append event: %v", err) +func replayRTMR(t *testing.T, cel *CEL, rtmr *fakertmr.RtmrSubsystem, measuredHash crypto.Hash, rtmrs []int, shouldSucceed bool) { + mrMap := make(map[uint32][]byte) + // RTMR 0 to 3 + for _, rtmrIndex := range rtmrs { + digest, err := configfstsmrtmr.GetDigest(rtmr, rtmrIndex) + if err != nil { + t.Fatal(err) + } + // Shift because the [RTMR register + 1] = [eventlog index] + mrMap[uint32(rtmrIndex)+1] = digest.Digest + } + if err := cel.Replay(mrMap, measuredHash); shouldSucceed && err != nil { + t.Errorf("failed to replay RTMR: %v", err) + } +} + +func appendPcrEventOrFatal(t *testing.T, cel *CEL, tpm io.ReadWriteCloser, pcr int, hashAlgos []crypto.Hash, event Content) { + if err := cel.AppendEventPCR(tpm, pcr, hashAlgos, event); err != nil { + t.Fatalf("failed to append PCR event: %v", err) + } +} + +func appendRtmrEventOrFatal(t *testing.T, cel *CEL, rtmrClient configfsi.Client, rtmr int, event Content) { + if err := cel.AppendEventRTMR(rtmrClient, rtmr, event); err != nil { + t.Fatalf("failed to append RTMR event: %v", err) } } diff --git a/cel/cos_tlv.go b/cel/cos_tlv.go index 5cbd2ccd8..1389f6dc3 100644 --- a/cel/cos_tlv.go +++ b/cel/cos_tlv.go @@ -14,6 +14,18 @@ const ( CosEventType uint8 = 80 // CosEventPCR is the PCR which should be used for CosEventType events. CosEventPCR = 13 + // CosRTMR is the RTMR to be extended for COS events + // According to https://uefi.org/specs/UEFI/2.10/38_Confidential_Computing.html + // CCELMRIndex TDX Register + // 0 MRTD + // 1 RTMR[0] + // 2 RTMR[1] + // 3 RTMR[2] + // So: + // 4 RTMR[3] + CosRTMR = 3 + // CosCCELMRIndex is the register index to use in eventlog for COS events. + CosCCELMRIndex = 4 ) // CosType represent a COS content type in a CEL record content. diff --git a/cel/cos_tlv_test.go b/cel/cos_tlv_test.go index 73934bcd4..c7486b4a1 100644 --- a/cel/cos_tlv_test.go +++ b/cel/cos_tlv_test.go @@ -14,7 +14,6 @@ import ( func TestCosEventlog(t *testing.T) { tpm := test.GetTPM(t) defer client.CheckedClose(t, tpm) - cel := &CEL{} testEvents := []struct { @@ -41,9 +40,10 @@ func TestCosEventlog(t *testing.T) { } for _, testEvent := range testEvents { - cos := CosTlv{testEvent.cosNestedEventType, testEvent.eventPayload} - if err := cel.AppendEvent(tpm, testEvent.pcr, measuredHashes, cos); err != nil { - t.Fatal(err.Error()) + cosEvent := CosTlv{testEvent.cosNestedEventType, testEvent.eventPayload} + + if err := cel.AppendEventPCR(tpm, testEvent.pcr, measuredHashes, cosEvent); err != nil { + t.Fatal(err) } } diff --git a/go.mod b/go.mod index 185093f61..6e596a561 100644 --- a/go.mod +++ b/go.mod @@ -6,17 +6,18 @@ require ( github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786 github.com/google/go-attestation v0.5.1 github.com/google/go-cmp v0.6.0 + github.com/google/go-configfs-tsm v0.3.3-0.20240910040719-1cc7e25d9272 github.com/google/go-sev-guest v0.11.1 - github.com/google/go-tdx-guest v0.3.1 + github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9 github.com/google/go-tpm v0.9.0 github.com/google/logger v1.1.1 - google.golang.org/protobuf v1.34.1 + google.golang.org/protobuf v1.34.2 ) require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/certificate-transparency-go v1.1.2 // indirect - github.com/google/go-configfs-tsm v0.2.2 // indirect + github.com/google/go-eventlog v0.0.1 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 7829ceca9..f712e8cf4 100644 --- a/go.sum +++ b/go.sum @@ -306,8 +306,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= -github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-configfs-tsm v0.3.3-0.20240910040719-1cc7e25d9272 h1:ut0AQwF/hqo+dY3FiGtTJ8E8TMCg4PgRdj/6Z79hY9k= +github.com/google/go-configfs-tsm v0.3.3-0.20240910040719-1cc7e25d9272/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-eventlog v0.0.1 h1:7lV3gf61LNDhfS9gQplqaJc/j9ztLhKKgZk/lR6vv4Q= +github.com/google/go-eventlog v0.0.1/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -315,8 +317,8 @@ github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVgg github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vvXN24k= github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w= -github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= -github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE= +github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9 h1:hk7vjuJgvYnHMZYI0cIDSXiC5XBmOlzRotA5bJ7nb+c= +github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= @@ -1222,8 +1224,8 @@ google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX7 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.work.sum b/go.work.sum index 31a3480c8..248e27d9d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -686,6 +686,7 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -693,6 +694,8 @@ github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZat github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-configfs-tsm v0.3.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-configfs-tsm v0.3.3-0.20240910040719-1cc7e25d9272/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-sev-guest v0.8.0/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs= @@ -703,6 +706,7 @@ github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= @@ -876,6 +880,7 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= @@ -944,6 +949,7 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/launcher/agent/agent.go b/launcher/agent/agent.go index 06878d7f2..7c43e9562 100644 --- a/launcher/agent/agent.go +++ b/launcher/agent/agent.go @@ -98,7 +98,7 @@ func (a *agent) Close() error { func (a *agent) MeasureEvent(event cel.Content) error { a.tpmMu.Lock() defer a.tpmMu.Unlock() - return a.cosCel.AppendEvent(a.tpm, cel.CosEventPCR, defaultCELHashAlgo, event) + return a.cosCel.AppendEventPCR(a.tpm, cel.CosEventPCR, defaultCELHashAlgo, event) } // Attest fetches the nonce and connection ID from the Attestation Service, diff --git a/launcher/go.mod b/launcher/go.mod index cd012c0f8..405fa7814 100644 --- a/launcher/go.mod +++ b/launcher/go.mod @@ -45,9 +45,9 @@ require ( github.com/google/certificate-transparency-go v1.1.2 // indirect github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786 // indirect github.com/google/go-attestation v0.5.1 // indirect - github.com/google/go-configfs-tsm v0.2.2 // indirect + github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect github.com/google/go-sev-guest v0.11.1 // indirect - github.com/google/go-tdx-guest v0.3.1 // indirect + github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/logger v1.1.1 // indirect github.com/google/s2a-go v0.1.7 // indirect diff --git a/launcher/go.sum b/launcher/go.sum index f81ac130c..5b15f3d4f 100644 --- a/launcher/go.sum +++ b/launcher/go.sum @@ -354,8 +354,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= -github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU= +github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-eventlog v0.0.1 h1:7lV3gf61LNDhfS9gQplqaJc/j9ztLhKKgZk/lR6vv4Q= +github.com/google/go-eventlog v0.0.1/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -363,8 +365,8 @@ github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVgg github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vvXN24k= github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w= -github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= -github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE= +github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9 h1:hk7vjuJgvYnHMZYI0cIDSXiC5XBmOlzRotA5bJ7nb+c= +github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= diff --git a/proto/attest.proto b/proto/attest.proto index 23420a053..3385408c0 100644 --- a/proto/attest.proto +++ b/proto/attest.proto @@ -29,7 +29,7 @@ message Attestation { bytes event_log = 3; // Optional information about a GCE instance, unused outside of GCE GCEInstanceInfo instance_info = 4; - // A TCG Canonical Event Log. + // A COS event log using the TCG Canonical Event Log format bytes canonical_event_log = 5; // Attestation Key (AK) Certificate, encoded as ASN.1 DER. // Optional. diff --git a/proto/attest/attest.pb.go b/proto/attest/attest.pb.go index 77484c11a..8be2672ed 100644 --- a/proto/attest/attest.pb.go +++ b/proto/attest/attest.pb.go @@ -284,7 +284,7 @@ type Attestation struct { EventLog []byte `protobuf:"bytes,3,opt,name=event_log,json=eventLog,proto3" json:"event_log,omitempty"` // Optional information about a GCE instance, unused outside of GCE InstanceInfo *GCEInstanceInfo `protobuf:"bytes,4,opt,name=instance_info,json=instanceInfo,proto3" json:"instance_info,omitempty"` - // A TCG Canonical Event Log. + // A COS event log using the TCG Canonical Event Log format CanonicalEventLog []byte `protobuf:"bytes,5,opt,name=canonical_event_log,json=canonicalEventLog,proto3" json:"canonical_event_log,omitempty"` // Attestation Key (AK) Certificate, encoded as ASN.1 DER. // Optional. diff --git a/server/eventlog.go b/server/eventlog.go index 0518ef4ef..e54f98e6f 100644 --- a/server/eventlog.go +++ b/server/eventlog.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/google/go-attestation/attest" + "github.com/google/go-eventlog/register" "github.com/google/go-tpm-tools/cel" pb "github.com/google/go-tpm-tools/proto/attest" tpmpb "github.com/google/go-tpm-tools/proto/tpm" @@ -88,17 +89,44 @@ func parsePCClientEventLog(rawEventLog []byte, pcrs *tpmpb.PCRs, loader Bootload }, createGroupedError("failed to fully parse MachineState:", errors) } -func parseCanonicalEventLog(rawCanonicalEventLog []byte, pcrs *tpmpb.PCRs) (*pb.MachineState, error) { +// ParseCosCanonicalEventLogPCR takes in a raw COS CEL and PCR banks, validates and returns it's +// COS states as parts of the MachineState. +func ParseCosCanonicalEventLogPCR(cosEventLog []byte, pcrs *tpmpb.PCRs) (*pb.MachineState, error) { + tpm2Alg := tpm2.Algorithm(pcrs.GetHash()) + cryptoHash, err := tpm2Alg.Hash() + if err != nil { + return nil, err + } + return getCosStateFromCEL(cosEventLog, cryptoHash, pcrs.GetPcrs(), cel.PcrTypeValue) +} + +// ParseCosCanonicalEventLogRTMR takes in a raw COS CEL and a RTMR bank, validates and returns it's +// COS states as parts of the MachineState. +func ParseCosCanonicalEventLogRTMR(cosEventLog []byte, regs register.RTMRBank) (*pb.MachineState, error) { + mrMap := make(map[uint32][]byte) + mrHash := crypto.SHA384 + + for _, reg := range regs.RTMRs { + if len(reg.Digest) != mrHash.Size() { + return nil, fmt.Errorf("got unexpected hash size for RTMR %d, want %d", len(reg.Digest), mrHash.Size()) + } + mrMap[uint32(reg.Idx())] = reg.Digest + } + + return getCosStateFromCEL(cosEventLog, mrHash, mrMap, cel.RtmrTypeValue) +} + +func getCosStateFromCEL(rawCanonicalEventLog []byte, cryptoHash crypto.Hash, registers map[uint32][]byte, trustingRegisterType uint8) (*pb.MachineState, error) { decodedCEL, err := cel.DecodeToCEL(bytes.NewBuffer(rawCanonicalEventLog)) if err != nil { return nil, err } // Validate the COS event log first. - if err := decodedCEL.Replay(pcrs); err != nil { + if err := decodedCEL.Replay(registers, cryptoHash); err != nil { return nil, err } - cosState, err := getVerifiedCosState(decodedCEL) + cosState, err := getVerifiedCosState(decodedCEL, trustingRegisterType) if err != nil { return nil, err } @@ -117,7 +145,9 @@ func contains(set [][]byte, value []byte) bool { return false } -func getVerifiedCosState(coscel cel.CEL) (*pb.AttestedCosState, error) { +// getVerifiedCosState takes in CEL and a register type (can be PCR or RTMR), and returns the state +// in the CEL. It will only include events using the correct trustingRegisterType. +func getVerifiedCosState(coscel cel.CEL, trustingRegisterType uint8) (*pb.AttestedCosState, error) { cosState := &pb.AttestedCosState{} cosState.Container = &pb.ContainerState{} cosState.HealthMonitoring = &pb.HealthMonitoringState{} @@ -127,9 +157,22 @@ func getVerifiedCosState(coscel cel.CEL) (*pb.AttestedCosState, error) { seenSeparator := false for _, record := range coscel.Records { - // COS State only comes from the CosEventPCR - if record.PCR != cel.CosEventPCR { - return nil, fmt.Errorf("found unexpected PCR %d in CEL log", record.PCR) + // if a record is not using under trustingRegisterType, skip the record + if record.IndexType != trustingRegisterType { + continue + } + + switch record.IndexType { + case cel.PcrTypeValue: + if record.Index != cel.CosEventPCR { + return nil, fmt.Errorf("found unexpected PCR %d in COS CEL log", record.Index) + } + case cel.RtmrTypeValue: + if record.Index != cel.CosCCELMRIndex { + return nil, fmt.Errorf("found unexpected RTMR %d in COS CEL log", record.Index) + } + default: + return nil, fmt.Errorf("unknown COS CEL log index type %d", record.IndexType) } // The Content.Type is not verified at this point, so we have to fail diff --git a/server/eventlog_test.go b/server/eventlog_test.go index 18c8112c0..5018d35f8 100644 --- a/server/eventlog_test.go +++ b/server/eventlog_test.go @@ -10,6 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-configfs-tsm/configfs/fakertmr" + configfstsmrtmr "github.com/google/go-configfs-tsm/rtmr" + "github.com/google/go-eventlog/register" "github.com/google/go-tpm-tools/cel" "github.com/google/go-tpm-tools/client" "github.com/google/go-tpm-tools/internal/test" @@ -689,6 +692,137 @@ func TestParseSecureBootState(t *testing.T) { } } +func getRTMR(t *testing.T, fakeRTMR *fakertmr.RtmrSubsystem) [][]byte { + mrMap := [][]byte{} + // RTMR 0 to 3 + for i := 0; i < 4; i++ { + digest, err := configfstsmrtmr.GetDigest(fakeRTMR, i) + if err != nil { + t.Fatal(err) + } + mrMap = append(mrMap, digest.Digest) + } + return mrMap +} + +func TestParsingRTMREventlog(t *testing.T) { + coscel := &cel.CEL{} + emptyCosState := attestpb.ContainerState{} + emptyHealthMonitoringState := attestpb.HealthMonitoringState{} + + var buf bytes.Buffer + // First, encode an empty CEL and try to parse it. + if err := coscel.EncodeCEL(&buf); err != nil { + t.Fatal(err) + } + + fakeRTMR := fakertmr.CreateRtmrSubsystem(t.TempDir()) + + rtmrBank := register.RTMRBank{} + for i, digest := range getRTMR(t, fakeRTMR) { + rtmrBank.RTMRs = append(rtmrBank.RTMRs, register.RTMR{Index: i, Digest: digest}) + } + + msState, err := ParseCosCanonicalEventLogRTMR(buf.Bytes(), rtmrBank) + if err != nil { + t.Errorf("expecting no error from parseCanonicalEventLogPCR(), but get %v", err) + } + if diff := cmp.Diff(msState.Cos.Container, &emptyCosState, protocmp.Transform()); diff != "" { + t.Errorf("unexpected container state difference:\n%v", diff) + } + if diff := cmp.Diff(msState.Cos.HealthMonitoring, &emptyHealthMonitoringState, protocmp.Transform()); diff != "" { + t.Errorf("unexpected health monitoring difference:\n%v", diff) + } + if msState.Cos.HealthMonitoring.MemoryEnabled != nil { + t.Errorf("unexpected MemoryEnabled state, want nil, but got %v", *msState.Cos.HealthMonitoring.MemoryEnabled) + } + + // add events + testCELEvents := []struct { + cosNestedEventType cel.CosType + register int + eventPayload []byte + }{ + {cel.ImageRefType, cel.CosRTMR, []byte("docker.io/bazel/experimental/test:latest")}, + {cel.ImageDigestType, cel.CosRTMR, []byte("sha256:781d8dfdd92118436bd914442c8339e653b83f6bf3c1a7a98efcfb7c4fed7483")}, + {cel.RestartPolicyType, cel.CosRTMR, []byte(attestpb.RestartPolicy_Always.String())}, + {cel.ImageIDType, cel.CosRTMR, []byte("sha256:5DF4A1AC347DCF8CF5E9D0ABC04B04DB847D1B88D3B1CC1006F0ACB68E5A1F4B")}, + {cel.EnvVarType, cel.CosRTMR, []byte("foo=bar")}, + {cel.EnvVarType, cel.CosRTMR, []byte("bar=baz")}, + {cel.EnvVarType, cel.CosRTMR, []byte("baz=foo=bar")}, + {cel.EnvVarType, cel.CosRTMR, []byte("empty=")}, + {cel.ArgType, cel.CosRTMR, []byte("--x")}, + {cel.ArgType, cel.CosRTMR, []byte("--y")}, + {cel.ArgType, cel.CosRTMR, []byte("")}, + {cel.MemoryMonitorType, cel.CosRTMR, []byte{1}}, + } + + expectedEnvVars := make(map[string]string) + expectedEnvVars["foo"] = "bar" + expectedEnvVars["bar"] = "baz" + expectedEnvVars["baz"] = "foo=bar" + expectedEnvVars["empty"] = "" + + wantContainerState := attestpb.ContainerState{ + ImageReference: string(testCELEvents[0].eventPayload), + ImageDigest: string(testCELEvents[1].eventPayload), + RestartPolicy: attestpb.RestartPolicy_Always, + ImageId: string(testCELEvents[3].eventPayload), + EnvVars: expectedEnvVars, + Args: []string{string(testCELEvents[8].eventPayload), string(testCELEvents[9].eventPayload), string(testCELEvents[10].eventPayload)}, + } + enabled := true + wantHealthMonitoringState := attestpb.HealthMonitoringState{ + MemoryEnabled: &enabled, + } + + for _, testEvent := range testCELEvents { + cosEvent := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload} + if err := coscel.AppendEventRTMR(fakeRTMR, testEvent.register, cosEvent); err != nil { + t.Fatal(err) + } + } + buf = bytes.Buffer{} + if err := coscel.EncodeCEL(&buf); err != nil { + t.Fatal(err) + } + + rtmrBank = register.RTMRBank{} + for i, digest := range getRTMR(t, fakeRTMR) { + rtmrBank.RTMRs = append(rtmrBank.RTMRs, register.RTMR{Index: i, Digest: digest}) + } + + if msState, err := ParseCosCanonicalEventLogRTMR(buf.Bytes(), rtmrBank); err != nil { + t.Errorf("expecting no error from parseCosEventLogRTMR(), but get %v", err) + } else { + if diff := cmp.Diff(msState.Cos.Container, &wantContainerState, protocmp.Transform()); diff != "" { + t.Errorf("unexpected container state difference:\n%v", diff) + } + if diff := cmp.Diff(msState.Cos.HealthMonitoring, &wantHealthMonitoringState, protocmp.Transform()); diff != "" { + t.Errorf("unexpected health monitoring state difference:\n%v", diff) + } + } + + // Faking PCR with RTMR + imposterPcrBank := map[uint32][]byte{} + imposterPcrBank[1] = rtmrBank.RTMRs[0].Digest + imposterPcrBank[2] = rtmrBank.RTMRs[1].Digest + imposterPcrBank[3] = rtmrBank.RTMRs[2].Digest + imposterPcrBank[4] = rtmrBank.RTMRs[3].Digest + imposterPcrs := &pb.PCRs{Hash: pb.HashAlgo_SHA384, Pcrs: imposterPcrBank} + + if msState, err = ParseCosCanonicalEventLogPCR(buf.Bytes(), imposterPcrs); err != nil { + t.Errorf("expecting no error from parseCosEventLogRTMR(), but get %v", err) + } else { + if diff := cmp.Diff(msState.Cos.Container, &attestpb.ContainerState{}, protocmp.Transform()); diff != "" { + t.Error("expect no claims when replaying against imposter PCR") + } + if diff := cmp.Diff(msState.Cos.HealthMonitoring, &attestpb.HealthMonitoringState{}, protocmp.Transform()); diff != "" { + t.Error("expect no claims when replaying against imposter PCR") + } + } +} + func TestParsingCELEventLog(t *testing.T) { test.SkipForRealTPM(t) tpm := test.GetTPM(t) @@ -720,9 +854,9 @@ func TestParsingCELEventLog(t *testing.T) { for _, bank := range banks { // pcrs can have any value here, since the coscel has no records, the replay should always success. - msState, err := parseCanonicalEventLog(buf.Bytes(), bank) + msState, err := ParseCosCanonicalEventLogPCR(buf.Bytes(), bank) if err != nil { - t.Errorf("expecting no error from parseCanonicalEventLog(), but get %v", err) + t.Errorf("expecting no error from ParseCosCanonicalEventLogPCR(), but get %v", err) } if diff := cmp.Diff(msState.Cos.Container, &emptyCosState, protocmp.Transform()); diff != "" { t.Errorf("unexpected container state difference:\n%v", diff) @@ -774,8 +908,9 @@ func TestParsingCELEventLog(t *testing.T) { MemoryEnabled: &enabled, } for _, testEvent := range testCELEvents { - cos := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload} - if err := coscel.AppendEvent(tpm, testEvent.pcr, implementedHashes, cos); err != nil { + cosEvent := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload} + + if err := coscel.AppendEventPCR(tpm, testEvent.pcr, implementedHashes, cosEvent); err != nil { t.Fatal(err) } } @@ -788,7 +923,7 @@ func TestParsingCELEventLog(t *testing.T) { t.Fatal(err) } for _, bank := range banks { - if msState, err := parseCanonicalEventLog(buf.Bytes(), bank); err != nil { + if msState, err := ParseCosCanonicalEventLogPCR(buf.Bytes(), bank); err != nil { t.Errorf("expecting no error from parseCanonicalEventLog(), but get %v", err) } else { if diff := cmp.Diff(msState.Cos.Container, &wantContainerState, protocmp.Transform()); diff != "" { @@ -827,7 +962,7 @@ func TestParsingCELEventLog(t *testing.T) { t.Fatal(err) } for _, bank := range banks { - _, err := parseCanonicalEventLog(buf.Bytes(), bank) + _, err := ParseCosCanonicalEventLogPCR(buf.Bytes(), bank) if err == nil { t.Errorf("expected error when parsing event log with unknown content type") } @@ -837,7 +972,7 @@ func TestParsingCELEventLog(t *testing.T) { func generateNonCosCelEvent(hashAlgoList []crypto.Hash) (cel.Record, error) { randRecord := cel.Record{} randRecord.RecNum = 0 - randRecord.PCR = cel.CosEventPCR + randRecord.Index = cel.CosEventPCR contentValue := make([]byte, 10) rand.Read(contentValue) randRecord.Content = cel.TLV{Type: 250, Value: contentValue} diff --git a/server/verify.go b/server/verify.go index b0fa77f25..17954d334 100644 --- a/server/verify.go +++ b/server/verify.go @@ -130,7 +130,7 @@ func VerifyAttestation(attestation *pb.Attestation, opts VerifyOpts) (*pb.Machin continue } - celState, err := parseCanonicalEventLog(attestation.GetCanonicalEventLog(), pcrs) + celState, err := ParseCosCanonicalEventLogPCR(attestation.GetCanonicalEventLog(), pcrs) if err != nil { lastErr = fmt.Errorf("failed to validate the Canonical event log: %w", err) continue @@ -397,6 +397,8 @@ func parseMachineStateFromTPM(attestation *pb.Attestation, pcrs *tpmpb.PCRs, opt return nil, fmt.Errorf("failed to validate the PCClient event log: %w", err) } + // TODO move verifyGceTechnology out of this function, as this function may get called + // for each PCR bank, which seems redundant. tech := ms.GetPlatform().Technology if err := verifyGceTechnology(attestation, tech, &opts); err != nil { return nil, fmt.Errorf("failed to verify memory encryption technology: %w", err) diff --git a/server/verify_test.go b/server/verify_test.go index 0d0508b30..573a592b2 100644 --- a/server/verify_test.go +++ b/server/verify_test.go @@ -459,8 +459,8 @@ func TestVerifyAttestationWithCEL(t *testing.T) { {cel.MemoryMonitorType, cel.CosEventPCR, []byte{1}}, } for _, testEvent := range testEvents { - cos := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload} - if err := coscel.AppendEvent(rwc, testEvent.pcr, measuredHashes, cos); err != nil { + cosEvent := cel.CosTlv{EventType: testEvent.cosNestedEventType, EventContent: testEvent.eventPayload} + if err := coscel.AppendEventPCR(rwc, testEvent.pcr, measuredHashes, cosEvent); err != nil { t.Fatal(err) } } @@ -531,10 +531,12 @@ 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, cel.CosEventPCR, measuredHashes, cosEvent); err != nil { + + if err := c.AppendEventPCR(rwc, cel.CosEventPCR, measuredHashes, cosEvent); err != nil { t.Fatalf("failed to append event: %v", err) } - if err := c.AppendEvent(rwc, cel.CosEventPCR, measuredHashes, cosEvent2); err != nil { + + if err := c.AppendEventPCR(rwc, cel.CosEventPCR, measuredHashes, cosEvent2); err != nil { t.Fatalf("failed to append event: %v", err) } diff --git a/verifier/go.mod b/verifier/go.mod index 10ce8fae5..ff8040b7d 100644 --- a/verifier/go.mod +++ b/verifier/go.mod @@ -10,6 +10,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-cmp v0.6.0 github.com/google/go-sev-guest v0.11.1 + github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9 github.com/google/go-tpm v0.9.0 github.com/google/go-tpm-tools v0.4.4 github.com/opencontainers/go-digest v1.0.0 @@ -35,8 +36,7 @@ require ( github.com/google/certificate-transparency-go v1.1.2 // indirect github.com/google/gce-tcb-verifier v0.2.3-0.20240905212129-12f728a62786 // indirect github.com/google/go-attestation v0.5.1 // indirect - github.com/google/go-configfs-tsm v0.2.2 // indirect - github.com/google/go-tdx-guest v0.3.1 // indirect + github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/logger v1.1.1 // indirect github.com/google/s2a-go v0.1.7 // indirect diff --git a/verifier/go.sum b/verifier/go.sum index be929d161..9c74cee5f 100644 --- a/verifier/go.sum +++ b/verifier/go.sum @@ -323,8 +323,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= -github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU= +github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= +github.com/google/go-eventlog v0.0.1 h1:7lV3gf61LNDhfS9gQplqaJc/j9ztLhKKgZk/lR6vv4Q= +github.com/google/go-eventlog v0.0.1/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -332,8 +334,8 @@ github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVgg github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vvXN24k= github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w= -github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= -github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE= +github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9 h1:hk7vjuJgvYnHMZYI0cIDSXiC5XBmOlzRotA5bJ7nb+c= +github.com/google/go-tdx-guest v0.3.2-0.20240902060211-1f7f7b9b42b9/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=