-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic Canonical Event Log (CEL) operations
Defined CEL TLV struct, added encoding/decoding functions
- Loading branch information
Showing
3 changed files
with
428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
Oops, something went wrong.