Repository: google/go-tpm
Pull request #208 (2020-08-11 15:17:09)

TPM2: Fix PolicyOR 
Add Encode/Decode function for TPMLDigest struct to generate valid byte slice and parse byte slice into structure
Add test for PolicyOR
Add test for Encode/Decode

Signed-off-by: Christopher Meis <christopher.meis@9elements.com>

ChriMarMe: #209 
ChriMarMe: No API constraints, only rookie coder who tries to play with the big guys. I code Go for a year now, and sometimes I'm just missing the experience and/or patience to solve a problem and I'm not very familiar with tpm2 spec. :P 

Anyway, thanks for your input and review.  
ChriMarMe: I have to thank you for your patience, guidance and quick responses @chrisfenner :+1:  
chrisfenner: It's my pleasure to help how I can! You're the one doing the actual work to implement a new command in go-tpm, so I have to thank you back for that 😄
ChriMarMe: @twitchy-jsonp @josephlr Would be nice to get the last bits correct :) can you check it please? 
ChriMarMe: What do I miss? I can't tell :(

Patch:
diff --git a/go.mod b/go.mod
index 689d7a2..b016e77 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/google/go-tpm
 go 1.12
 
 require (
+	github.com/google/go-cmp v0.5.2
 	github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845
 	golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a
 )
diff --git a/go.sum b/go.sum
index 786c652..48b41d2 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
 github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845 h1:2WNNKKRI+a5OZi5xiJVfDoOiUyfK/BU1D4w+N6967F4=
 github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0=
@@ -32,5 +34,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/tpm2/structures.go b/tpm2/structures.go
index f66499e..02bc6e1 100644
--- a/tpm2/structures.go
+++ b/tpm2/structures.go
@@ -19,6 +19,7 @@ import (
 	"crypto"
 	"crypto/ecdsa"
 	"crypto/rsa"
+	"encoding/binary"
 	"errors"
 	"fmt"
 	"math/big"
@@ -1023,8 +1024,45 @@ type AuthCommand struct {
 
 // TPMLDigest represents the TPML_Digest structure
 // It is used to convey a list of digest values.
-//This type is used in TPM2_PolicyOR() and in TPM2_PCR_Read()
+// This type is used in TPM2_PolicyOR() and in TPM2_PCR_Read()
 type TPMLDigest struct {
-	Count   uint32
 	Digests []tpmutil.U16Bytes
 }
+
+// Encode converts the TPMLDigest structure into a byte slice
+func (list *TPMLDigest) Encode() ([]byte, error) {
+	res, err := tpmutil.Pack(uint32(len(list.Digests)))
+	if err != nil {
+		return nil, err
+	}
+	for _, item := range list.Digests {
+		b, err := tpmutil.Pack(item)
+		if err != nil {
+			return nil, err
+		}
+		res = append(res, b...)
+
+	}
+	return res, nil
+}
+
+// DecodeTPMLDigest decodes a TPML_Digest part of a message.
+func DecodeTPMLDigest(buf []byte) (*TPMLDigest, error) {
+	in := bytes.NewBuffer(buf)
+	var tpmld TPMLDigest
+	var count uint32
+	if err := binary.Read(in, binary.BigEndian, &count); err != nil {
+		return nil, fmt.Errorf("decoding TPML_Digest: %v", err)
+	}
+	for in.Len() > 0 {
+		var hash tpmutil.U16Bytes
+		if err := hash.TPMUnmarshal(in); err != nil {
+			return nil, err
+		}
+		tpmld.Digests = append(tpmld.Digests, hash)
+	}
+	if count != uint32(len(tpmld.Digests)) {
+		return nil, fmt.Errorf("expected size and read size does not match")
+	}
+	return &tpmld, nil
+}
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index 83f5d84..f662daa 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -802,7 +802,11 @@ func PolicyPCR(rw io.ReadWriter, session tpmutil.Handle, expectedDigest []byte,
 // to a Zero Digest. Then policySession→Digest is extended by the concatenation of
 // TPM_CC_PolicyOR and the concatenation of all of the digests.
 func PolicyOr(rw io.ReadWriter, session tpmutil.Handle, digests TPMLDigest) error {
-	data, err := tpmutil.Pack(session, digests)
+	d, err := digests.Encode()
+	if err != nil {
+		return err
+	}
+	data, err := tpmutil.Pack(session, d)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/tpm2/encoding_test.go b/tpm2/encoding_test.go
index 9d969db..4462e4e 100644
--- a/tpm2/encoding_test.go
+++ b/tpm2/encoding_test.go
@@ -24,6 +24,7 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-tpm/tpmutil"
 )
 
@@ -477,3 +478,44 @@ func TestSignEncode(t *testing.T) {
 		}
 	})
 }
+
+func TestTPMLDigestEncode(t *testing.T) {
+	hashA := []byte{0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 255}
+	hashB := []byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 255}
+
+	hl := TPMLDigest{Digests: []tpmutil.U16Bytes{
+		hashA, hashB},
+	}
+
+	want := []byte{0, 0, 0, 2, 0, 20, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 255,
+		0, 20, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 255}
+
+	got, err := hl.Encode()
+	if err != nil {
+		t.Errorf("hl.Encode() failed: %v", err)
+	}
+	if !bytes.Equal(want, got) {
+		t.Errorf("TPMLDigest{%v}.Encode() = %v, want: %v", hl, got, want)
+	}
+}
+
+func TestTPMLDigestDecode(t *testing.T) {
+	hashA := []byte{0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 255}
+	hashB := []byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 255}
+
+	want := &TPMLDigest{Digests: []tpmutil.U16Bytes{
+		hashA, hashB},
+	}
+
+	b := []byte{0, 0, 0, 2, 0, 20, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 255,
+		0, 20, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 255}
+
+	got, err := DecodeTPMLDigest(b)
+	if err != nil {
+		t.Errorf("DecodeTPMDigest(b) failed: %v", err)
+	}
+
+	if !cmp.Equal(want, got) {
+		t.Errorf("Digests are not the same.\n Have:\n %v\n - Want:\n %v", got.Digests, want.Digests)
+	}
+}
diff --git a/tpm2/test/tpm2_test.go b/tpm2/test/tpm2_test.go
index b11df6e..4518274 100644
--- a/tpm2/test/tpm2_test.go
+++ b/tpm2/test/tpm2_test.go
@@ -2080,3 +2080,34 @@ func TestDictionaryAttackLockReset(t *testing.T) {
 		t.Fatalf("got %d, expected 0", caps[0].(TaggedProperty).Value)
 	}
 }
+
+func TestPolicyOr(t *testing.T) {
+	rw := openTPM(t)
+	defer rw.Close()
+	hashAlg, err := AlgSHA256.Hash()
+	if err != nil {
+		t.Errorf("looking up hash algo with error: %v", err)
+	}
+	zeroHash := make([]byte, hashAlg.Size())
+	customHash := make([]byte, hashAlg.Size())
+
+	_, err = rand.Read(customHash)
+	if err != nil {
+		t.Errorf("random read into byte slice with error: %v", err)
+	}
+
+	tpml := TPMLDigest{Digests: []tpmutil.U16Bytes{
+		zeroHash, customHash},
+	}
+
+	sessHandle, _, err := StartAuthSession(rw, HandleNull, HandleNull, make([]byte, 16), nil, SessionPolicy, AlgNull, AlgSHA256)
+	defer FlushContext(rw, sessHandle)
+
+	if err != nil {
+		t.Errorf("StartAuthSession failed: %v", err)
+	}
+	err = PolicyOr(rw, sessHandle, tpml)
+	if err != nil {
+		t.Errorf("PolicyOr with error: %v", err)
+	}
+}