Skip to content

Commit

Permalink
Add basic Canonical Event Log (CEL) operations
Browse files Browse the repository at this point in the history
Defined CEL TLV struct, added encoding/decoding functions
  • Loading branch information
jkl73 committed Nov 16, 2021
1 parent 8a35101 commit 3e88ce7
Show file tree
Hide file tree
Showing 3 changed files with 428 additions and 0 deletions.
334 changes: 334 additions & 0 deletions cel/canonical_eventlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
package cel

import (
"bytes"
"crypto"
"encoding/binary"
"fmt"
"io"

"github.com/google/go-tpm/tpm2"
)

var cryptoHashToTpmAlg = map[crypto.Hash]tpm2.Algorithm{
crypto.SHA1: tpm2.AlgSHA1,
crypto.SHA256: tpm2.AlgSHA256,
crypto.SHA384: tpm2.AlgSHA384,
crypto.SHA512: tpm2.AlgSHA512,
}
var tpmAlgToCryptoHash = map[tpm2.Algorithm]crypto.Hash{
tpm2.AlgSHA1: crypto.SHA1,
tpm2.AlgSHA256: crypto.SHA256,
tpm2.AlgSHA384: crypto.SHA384,
tpm2.AlgSHA512: crypto.SHA512,
}

const (
// Canonical EventLog Spec (Draft) Version: TCG_IWG_CEL_v1_r0p37.
// CEL spec 5.1
recnumTypeValue uint8 = 0
pcrTypeValue uint8 = 1
// nvIndexTagValue uint8 = 2 // nvindex field is not supported yet
digestsTypeValue uint8 = 3

tlvTagFieldLength int = 1
tlvLengthFieldLength int = 4

recnumValueLength uint32 = 8 // support up to 2^64 records
pcrValueLength uint32 = 1 // support up to 256 PCRs
)

// TLV definition according to CEL spec TCG_IWG_CEL_v1_r0p37, page 16.
type TLV struct {
Type uint8
Length uint32 // big-endian when encoding
Value []byte // with size of Length
}

// TLVMarshal marhsals a TLV to a byte slice.
func (tlv *TLV) TLVMarshal() ([]byte, error) {
if tlv.Length != uint32(len(tlv.Value)) {
return nil, fmt.Errorf("length of tlv.Value [%d] doesn't equal to tlv.Length [%d]",
len(tlv.Value), tlv.Length)
}

buf := make([]byte, tlv.Length+uint32(tlvTagFieldLength)+uint32(tlvLengthFieldLength))

buf[0] = tlv.Type
binary.BigEndian.PutUint32(buf[tlvTagFieldLength:], tlv.Length)
copy(buf[tlvTagFieldLength+tlvLengthFieldLength:], tlv.Value)

return buf, nil
}

// TLVUnmarshal reads and parse the first TLV from the bytes buffer. The function will
// return io.EOF if the buf ends unexpectedly or cannot filled the TLV.
func TLVUnmarshal(buf *bytes.Buffer) (*TLV, error) {
var tlv TLV
var err error

// read tag
tlv.Type, err = buf.ReadByte()
if err != nil {
return nil, err
}

// read length
b := make([]byte, tlvLengthFieldLength)
l, err := buf.Read(b)
if err != nil {
return nil, err
}
if l < tlvLengthFieldLength {
return nil, io.EOF
}
tlv.Length = binary.BigEndian.Uint32(b)

// read value
tlv.Value = make([]byte, tlv.Length)
l, err = buf.Read(tlv.Value)
if err != nil {
return nil, err
}
if l < int(tlv.Length) {
return nil, io.EOF
}
return &tlv, nil
}

// CELR represents a Canonical Eventlog Record.
type CELR struct {
RECNUM TLV
PCR TLV
Digests TLV
Content TLV
}

// CELContent is a interface for the content in CELR.
type CELContent interface {
GenerateDigest(crypto.Hash) ([]byte, error)
GetTLV() TLV
}

// CEL represents a Canonical Eventlog, which contains a list of CELR.
type CEL struct {
Records []CELR
}

// AppendEvent appends a new record to the CEL.
func (c *CEL) AppendEvent(tpm io.ReadWriteCloser, pcr int, hashAlgos []crypto.Hash, payload CELContent) error {
digestsMap := make(map[crypto.Hash][]byte)
for _, hashAlgo := range hashAlgos {
digest, err := payload.GenerateDigest(hashAlgo)
if err != nil {
return err
}
digestsMap[hashAlgo] = digest

// TODO: extend the digest to TPM PCR
}

celr, err := createCELR(uint64(len(c.Records)), uint8(pcr), digestsMap, payload)
if err != nil {
return err
}

c.Records = append(c.Records, celr)
return nil
}

func createRecNumField(recNum uint64) TLV {
payload := make([]byte, recnumValueLength)
binary.BigEndian.PutUint64(payload, recNum)
return TLV{recnumTypeValue, recnumValueLength, payload}
}

// UnmarshalRecNum takes in a TLV with its tag equals to the recnum tag value (0), and
// return its record number.
func UnmarshalRecNum(tlv TLV) (uint64, error) {
if tlv.Type != recnumTypeValue {
return 0, fmt.Errorf("tag of the TLV [%d] indicates it is not a recnum field [%d]",
tlv.Type, recnumTypeValue)
}
return binary.BigEndian.Uint64(tlv.Value), nil
}

func createPCRField(pcrNum uint8) TLV {
return TLV{pcrTypeValue, pcrValueLength, []byte{pcrNum}}
}

// UnmarshalPCR takes in a TLV with its tag equals to the PCR tag value (1), and
// return its PCR number.
func UnmarshalPCR(tlv TLV) (pcrNum uint8, err error) {
if tlv.Type != pcrTypeValue {
return 0, fmt.Errorf("tag of the TLV [%d] indicates it is not a PCR field [%d]",
tlv.Type, pcrTypeValue)
}
return tlv.Value[0], nil
}

func createDigestField(digestMap map[crypto.Hash][]byte) (TLV, error) {
var buf bytes.Buffer
for hashAlgo, hash := range digestMap {
if len(hash) != hashAlgo.Size() {
return TLV{}, fmt.Errorf("digest length [%d] doesn't match the expected length [%d] for the hash algorithm",
len(hash), hashAlgo.Size())
}
singledigestTLV := TLV{uint8(cryptoHashToTpmAlg[hashAlgo]), uint32(len(hash)), hash}
d, err := singledigestTLV.TLVMarshal()
if err != nil {
return TLV{}, err
}
_, err = buf.Write(d)
if err != nil {
return TLV{}, err
}
}
return TLV{digestsTypeValue, uint32(buf.Len()), buf.Bytes()}, nil
}

// UnmarshalDigests takes in a TLV with its tag equals to the digests Tag value (3), and
// return its digests content in a map, the key is its TPM hash algorithm.
func UnmarshalDigests(tlv TLV) (digestsMap map[crypto.Hash][]byte, err error) {
if tlv.Type != digestsTypeValue {
return nil, fmt.Errorf("tag of the TLV indicates it doesn't contain digests")
}

buf := bytes.NewBuffer(tlv.Value)
digestsMap = make(map[crypto.Hash][]byte)

for buf.Len() > 0 {
digestTLV, err := TLVUnmarshal(buf)
if err == io.EOF {
return nil, fmt.Errorf("buffer ends unexpectedly")
} else if err != nil {
return nil, err
}
digestsMap[tpmAlgToCryptoHash[tpm2.Algorithm(digestTLV.Type)]] = digestTLV.Value
}
return digestsMap, nil
}

func createCELR(recNum uint64, pcr uint8, digestsmap map[crypto.Hash][]byte, event CELContent) (celr CELR, err error) {
recnumField := createRecNumField(recNum)
pcrField := createPCRField(pcr)

digestField, err := createDigestField(digestsmap)
if err != nil {
return CELR{}, err
}
celr = CELR{recnumField, pcrField, digestField, event.GetTLV()}

return celr, nil
}

// EncodeCELR encodes the CELR to bytes according to the CEL spec and write them
// to the bytes byffer.
func (r *CELR) EncodeCELR(buf *bytes.Buffer) error {
recnumField, err := r.RECNUM.TLVMarshal()
if err != nil {
return err
}
pcrField, err := r.PCR.TLVMarshal()
if err != nil {
return err
}
digestsField, err := r.Digests.TLVMarshal()
if err != nil {
return err
}
eventField, err := r.Content.TLVMarshal()
if err != nil {
return err
}
_, err = buf.Write(recnumField)
if err != nil {
return err
}
_, err = buf.Write(pcrField)
if err != nil {
return err
}
buf.Write(digestsField)
if err != nil {
return err
}
buf.Write(eventField)
if err != nil {
return err
}
return nil
}

// EncodeCEL encodes the CEL to bytes according to the CEL spec and write them
// to the bytes buffer.
func (c *CEL) EncodeCEL(buf *bytes.Buffer) error {
for _, record := range c.Records {
if err := record.EncodeCELR(buf); err != nil {
return err
}
}
return nil
}

// DecodeToCEL will read the buf for CEL, will return err if the buffer
// is not complete.
func DecodeToCEL(buf *bytes.Buffer) (CEL, error) {
var cel CEL
for buf.Len() > 0 {
celr, err := DecodeToCELR(buf)
if err == io.EOF {
return CEL{}, fmt.Errorf("buffer ends unexpectedly")
}
if err != nil {
return CEL{}, err
}
cel.Records = append(cel.Records, celr)
}
return cel, nil
}

// DecodeToCELR will read the buf for the next CELR, will return err if
// failed to unmarshal a correct CELR TLV from the buffer.
func DecodeToCELR(buf *bytes.Buffer) (CELR, error) {
recnum, err := TLVUnmarshal(buf)
if err != nil {
return CELR{}, err
}
if recnum.Type != recnumTypeValue {
return CELR{}, fmt.Errorf("recnum TLV doesn't have the correct type [%d], got [%d]",
recnumTypeValue, recnum.Type)
}

pcr, err := TLVUnmarshal(buf)
if err != nil {
return CELR{}, err
}
if pcr.Type != pcrTypeValue {
return CELR{}, fmt.Errorf("pcr TLV doesn't have the correct type [%d], got [%d]",
pcrTypeValue, pcr.Type)
}

digests, err := TLVUnmarshal(buf)
if err != nil {
return CELR{}, err
}
if digests.Type != digestsTypeValue {
return CELR{}, fmt.Errorf("digests TLV doesn't have the correct type [%d], got [%d]",
digestsTypeValue, digests.Type)
}

content, err := TLVUnmarshal(buf)
if err != nil {
return CELR{}, err
}

celr := CELR{
*recnum,
*pcr,
*digests,
*content,
}

return celr, nil
}
60 changes: 60 additions & 0 deletions cel/canonical_eventlog_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cel

import (
"bytes"
"crypto"
"reflect"
"testing"
)

func TestCELEncodingDecoding(t *testing.T) {
hashAlgoList := []crypto.Hash{crypto.SHA256, crypto.SHA1}
cel := &CEL{}

someEvent := make([]byte, 10)
cosEvent := CosTlv{CosTypeValue, uint32(10), someEvent}
cel.AppendEvent(nil, 13, hashAlgoList, cosEvent)

someEvent2 := make([]byte, 10)
cosEvent2 := CosTlv{CosTypeValue, uint32(10), someEvent2}
cel.AppendEvent(nil, 14, hashAlgoList, cosEvent2)

var buf bytes.Buffer
err := cel.EncodeCEL(&buf)
if err != nil {
t.Fatalf(err.Error())
}
decodedcel, err := DecodeToCEL(&buf)
if err != nil {
t.Fatalf(err.Error())
}
if len(decodedcel.Records) != 2 {
t.Errorf("should have two records")
}
if recnum, err := UnmarshalRecNum(decodedcel.Records[0].RECNUM); err != nil || recnum != uint64(0) {
t.Errorf("recnum mismatch")
}
if recnum, err := UnmarshalRecNum(decodedcel.Records[1].RECNUM); err != nil || recnum != uint64(1) {
t.Errorf("recnum mismatch")
}
if pcr, err := UnmarshalPCR(decodedcel.Records[0].PCR); err != nil || pcr != uint8(13) {
t.Errorf("pcr value mismatch")
}
if pcr, err := UnmarshalPCR(decodedcel.Records[1].PCR); err != nil || pcr != uint8(14) {
t.Errorf("pcr value mismatch")
}

digestsMap, err := UnmarshalDigests(decodedcel.Records[0].Digests)
if err != nil {
t.Error(err)
}
if len(digestsMap[crypto.SHA256]) != 32 {
t.Errorf("SHA256 digest length doesn't match")
}
if len(digestsMap[crypto.SHA1]) != 20 {
t.Errorf("SHA1 digest length doesn't match")
}
if reflect.DeepEqual(decodedcel.Records, (*cel).Records) == false {
t.Errorf("decoded CEL doesn't equal to the original one")
}
}
Loading

0 comments on commit 3e88ce7

Please sign in to comment.