-
Notifications
You must be signed in to change notification settings - Fork 72
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
364 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,266 @@ | ||
package client | ||
|
||
import ( | ||
"bytes" | ||
"crypto" | ||
"encoding/binary" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/google/go-tpm/tpm2" | ||
) | ||
|
||
var hashAlgoMapping = map[crypto.Hash]tpm2.Algorithm{ | ||
crypto.SHA1: tpm2.AlgSHA1, | ||
crypto.SHA256: tpm2.AlgSHA256, | ||
crypto.SHA384: tpm2.AlgSHA384, | ||
crypto.SHA512: tpm2.AlgSHA512, | ||
} | ||
|
||
const ( | ||
// CEL spec 5.1 | ||
recNumTagValue uint8 = 0 | ||
pcrTagValue uint8 = 1 | ||
// nvIndexTagValue uint8 = 2 // nvindex field is not supported | ||
digestsTagValue 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, page 16 | ||
type TLV struct { | ||
Tag uint8 | ||
Length uint32 // big-endian when encoding | ||
Value []byte // with size of Length | ||
} | ||
|
||
// TLVMarshal marhsal a TLV to a byte slice | ||
func TLVMarshal(tlv TLV) []byte { | ||
buf := make([]byte, tlv.Length+uint32(tlvTagFieldLength)+uint32(tlvLengthFieldLength)) | ||
|
||
buf[0] = tlv.Tag | ||
binary.BigEndian.PutUint32(buf[tlvTagFieldLength:], tlv.Length) | ||
copy(buf[tlvTagFieldLength+tlvLengthFieldLength:], tlv.Value) | ||
|
||
return buf | ||
} | ||
|
||
// TLVUnmarshal reads and parse the first TLV from the bytes buffer. | ||
func TLVUnmarshal(buf *bytes.Buffer) (TLV, error) { | ||
var tlv TLV | ||
var err error | ||
|
||
// read tag | ||
tlv.Tag, err = buf.ReadByte() | ||
if err != nil { | ||
return TLV{}, err | ||
} | ||
|
||
// read length | ||
b := make([]byte, tlvLengthFieldLength) | ||
l, err := buf.Read(b) | ||
|
||
if err != nil || l < tlvLengthFieldLength { | ||
return TLV{}, 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 || l < int(tlv.Length) { | ||
return TLV{}, io.EOF | ||
} | ||
return tlv, nil | ||
} | ||
|
||
// CELR represents a Canoical Eventlog Record | ||
type CELR struct { | ||
RecNum TLV | ||
PCR TLV | ||
Digests TLV | ||
Content TLV | ||
} | ||
|
||
// CELREventData is a interface for the content in CELR. | ||
type CELREventData 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 CELREventData) error { | ||
digestsMap := make(map[tpm2.Algorithm][]byte) | ||
for _, hashAlgo := range hashAlgos { | ||
digest, err := payload.GenerateDigest(hashAlgo) | ||
if err != nil { | ||
return err | ||
} | ||
digestsMap[hashAlgoMapping[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{recNumTagValue, 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.Tag != recNumTagValue { | ||
return 0, fmt.Errorf("tag of the TLV indicates it is not a recnum field") | ||
} | ||
return binary.BigEndian.Uint64(tlv.Value), nil | ||
} | ||
|
||
func createPCRField(pcrNum uint8) TLV { | ||
return TLV{pcrTagValue, pcrValueLength, []byte{pcrNum}} | ||
} | ||
|
||
// UnmarshalPCR takes in a TLV with its tag equals to the PCR tag value (3), and | ||
// return its PCR number. | ||
func UnmarshalPCR(tlv TLV) (pcrNum uint8, err error) { | ||
if tlv.Tag != pcrTagValue { | ||
return 0, fmt.Errorf("tag of the TLV indicates it is not a PCR field") | ||
} | ||
return tlv.Value[0], nil | ||
} | ||
|
||
func createDigestField(digestMap map[tpm2.Algorithm][]byte) TLV { | ||
var buf bytes.Buffer | ||
|
||
for hashAlgo, hash := range digestMap { | ||
singledigest := TLV{uint8(hashAlgo), uint32(len(hash)), hash} | ||
_, err := buf.Write(TLVMarshal(singledigest)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
return TLV{digestsTagValue, uint32(buf.Len()), buf.Bytes()} | ||
} | ||
|
||
// 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[tpm2.Algorithm][]byte, err error) { | ||
if tlv.Tag != digestsTagValue { | ||
return nil, fmt.Errorf("tag of the TLV indicates it doesn't contain digests") | ||
} | ||
|
||
buf := bytes.NewBuffer(tlv.Value) | ||
digestsMap = make(map[tpm2.Algorithm][]byte) | ||
|
||
for buf.Len() > 0 { | ||
digestTLV, err := TLVUnmarshal(buf) | ||
if err == io.EOF { | ||
return nil, fmt.Errorf("buffer ends unexpectedly") | ||
} | ||
digestsMap[tpm2.Algorithm(digestTLV.Tag)] = digestTLV.Value | ||
} | ||
return digestsMap, nil | ||
} | ||
|
||
func createCELR(recNum uint64, pcr uint8, digestsmap map[tpm2.Algorithm][]byte, event CELREventData) (celr CELR, err error) { | ||
recnumField := createRecNumField(recNum) | ||
pcrField := createPCRField(pcr) | ||
|
||
digestField := createDigestField(digestsmap) | ||
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 byte byffer. | ||
func (r *CELR) EncodeCELR(buf *bytes.Buffer) error { | ||
// encode CEL and write the binary eventlog to the buf | ||
recnumField := TLVMarshal(TLV(r.RecNum)) | ||
pcrField := TLVMarshal(TLV(r.PCR)) | ||
digestsField := TLVMarshal(TLV(r.Digests)) | ||
eventField := TLVMarshal(TLV(r.Content)) | ||
|
||
buf.Write(recnumField) | ||
buf.Write(pcrField) | ||
buf.Write(digestsField) | ||
buf.Write(eventField) | ||
return nil | ||
} | ||
|
||
// EncodeCEL encodes the CEL to bytes according to the CEL spec and write them | ||
// to the byte 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") | ||
} | ||
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 the TLV from the buffer. | ||
func DecodeToCELR(buf *bytes.Buffer) (CELR, error) { | ||
recNum, err := TLVUnmarshal(buf) | ||
if err != nil { | ||
return CELR{}, err | ||
} | ||
|
||
pcr, err := TLVUnmarshal(buf) | ||
if err != nil { | ||
return CELR{}, err | ||
} | ||
|
||
digests, err := TLVUnmarshal(buf) | ||
if err != nil { | ||
return CELR{}, err | ||
} | ||
|
||
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,69 @@ | ||
package client | ||
|
||
import ( | ||
"bytes" | ||
"crypto" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/google/go-tpm/tpm2" | ||
) | ||
|
||
func TestCELEncodingDecoding(t *testing.T) { | ||
hashAlgoList := []crypto.Hash{crypto.SHA256, crypto.SHA1} | ||
cel := &CEL{} | ||
|
||
someEvent := make([]byte, 10) | ||
cosEvent := COSTLV{COSTagValue, uint32(10), someEvent} | ||
cel.AppendEvent(nil, 13, hashAlgoList, cosEvent) | ||
|
||
someEvent2 := make([]byte, 10) | ||
cosEvent2 := COSTLV{COSTagValue, 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[tpm2.AlgSHA256]) != 32 { | ||
t.Errorf("SHA256 digest length doesn't match") | ||
} | ||
|
||
if len(digestsMap[tpm2.AlgSHA1]) != 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") | ||
} | ||
} |
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,29 @@ | ||
package client | ||
|
||
import "crypto" | ||
|
||
const ( | ||
// COSTagValue indicate the CELR event is a COS content | ||
// TODO: the value needs to be reserved in the CEL spec | ||
COSTagValue uint8 = 88 | ||
) | ||
|
||
// COSTLV is a specific event type created for the COS (Google Container-Optimized OS), | ||
// used as a CEL content. | ||
type COSTLV TLV | ||
|
||
// GetTLV returns the TLV representation of the COS TLV. | ||
func (c COSTLV) GetTLV() TLV { | ||
return TLV(c) | ||
} | ||
|
||
// GenerateDigest generates the digest for the given COS TLV. The whole TLV struct will | ||
// be marshaled to bytes and feed into the hash algo. | ||
func (c COSTLV) GenerateDigest(hashAlgo crypto.Hash) ([]byte, error) { | ||
hash := hashAlgo.New() | ||
_, err := hash.Write(TLVMarshal(c.GetTLV())) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return hash.Sum(nil), nil | ||
} |