Repository: google/go-tpm
Pull request #133 (2019-09-17 17:29:26)

Add SignWithSession
SignWithSession is needed to support Signing Keys with a PCR policy.  


Patch:
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index edc8a75..f8a5de9 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -781,6 +781,15 @@ func encodePolicyPCR(session tpmutil.Handle, expectedDigest tpmutil.U16Bytes, se
 }
 
 // PolicyPCR sets PCR state binding for authorization on a session.
+//
+// expectedDigest is optional. When specified, it's compared against the digest
+// of PCRs matched by sel.
+//
+// Note that expectedDigest must be a *digest* of the expected PCR value. You
+// must compute the digest manually. ReadPCR returns raw PCR values, not their
+// digests.
+// If you wish to select multiple PCRs, concatenate their values before
+// computing the digest. See "TPM 2.0 Part 1, Selecting Multiple PCR".
 func PolicyPCR(rw io.ReadWriter, session tpmutil.Handle, expectedDigest []byte, sel PCRSelection) error {
 	cmd, err := encodePolicyPCR(session, expectedDigest, sel)
 	if err != nil {
@@ -1289,12 +1298,12 @@ func Shutdown(rw io.ReadWriter, typ StartupType) error {
 	return err
 }
 
-func encodeSign(key tpmutil.Handle, password string, digest tpmutil.U16Bytes, sigScheme *SigScheme) ([]byte, error) {
+func encodeSign(sessionHandle, key tpmutil.Handle, password string, digest tpmutil.U16Bytes, sigScheme *SigScheme) ([]byte, error) {
 	ha, err := tpmutil.Pack(key)
 	if err != nil {
 		return nil, err
 	}
-	auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)})
+	auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)})
 	if err != nil {
 		return nil, err
 	}
@@ -1327,10 +1336,10 @@ func decodeSign(buf []byte) (*Signature, error) {
 	return DecodeSignature(in)
 }
 
-// Sign computes a signature for digest using a given loaded key. Signature
-// algorithm depends on the key type.
-func Sign(rw io.ReadWriter, key tpmutil.Handle, password string, digest []byte, sigScheme *SigScheme) (*Signature, error) {
-	cmd, err := encodeSign(key, password, digest, sigScheme)
+// SignWithSession computes a signature for digest using a given loaded key. Signature
+// algorithm depends on the key type. Used for keys with non-password authorization policies.
+func SignWithSession(rw io.ReadWriter, sessionHandle, key tpmutil.Handle, password string, digest []byte, sigScheme *SigScheme) (*Signature, error) {
+	cmd, err := encodeSign(sessionHandle, key, password, digest, sigScheme)
 	if err != nil {
 		return nil, err
 	}
@@ -1341,6 +1350,12 @@ func Sign(rw io.ReadWriter, key tpmutil.Handle, password string, digest []byte,
 	return decodeSign(resp)
 }
 
+// Sign computes a signature for digest using a given loaded key. Signature
+// algorithm depends on the key type.
+func Sign(rw io.ReadWriter, key tpmutil.Handle, password string, digest []byte, sigScheme *SigScheme) (*Signature, error) {
+	return SignWithSession(rw, HandlePasswordSession, key, password, digest, sigScheme)
+}
+
 func encodeCertify(parentAuth, ownerAuth string, object, signer tpmutil.Handle, qualifyingData tpmutil.U16Bytes) ([]byte, error) {
 	ha, err := tpmutil.Pack(object, signer)
 	if err != nil {


Test patch:
diff --git a/tpm2/encoding_test.go b/tpm2/encoding_test.go
index 84d3e04..22da5b2 100644
--- a/tpm2/encoding_test.go
+++ b/tpm2/encoding_test.go
@@ -450,7 +450,7 @@ func TestSignEncode(t *testing.T) {
 			t.Fatal(err)
 		}
 		digest := []byte{0x01, 0x02, 0x03}
-		cmdBytes, err := encodeSign(tpmutil.Handle(0x80000001), defaultPassword, digest, nil)
+		cmdBytes, err := encodeSign(HandlePasswordSession, tpmutil.Handle(0x80000001), defaultPassword, digest, nil)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -465,7 +465,7 @@ func TestSignEncode(t *testing.T) {
 			t.Fatal(err)
 		}
 		digest := []byte{0x01, 0x02, 0x03}
-		cmdBytes, err := encodeSign(tpmutil.Handle(0x80000001), defaultPassword, digest, &SigScheme{
+		cmdBytes, err := encodeSign(HandlePasswordSession, tpmutil.Handle(0x80000001), defaultPassword, digest, &SigScheme{
 			Alg:  AlgRSASSA,
 			Hash: AlgSHA256,
 		})
diff --git a/tpm2/test/tpm2_test.go b/tpm2/test/tpm2_test.go
index 10e6fbf..97e3158 100644
--- a/tpm2/test/tpm2_test.go
+++ b/tpm2/test/tpm2_test.go
@@ -1478,3 +1478,58 @@ func TestPlainImport(t *testing.T) {
 		t.Errorf("Got %X, expected %X", outBuff, inBuff)
 	}
 }
+
+func TestPolicyPCR(t *testing.T) {
+	rw := openTPM(t)
+	defer rw.Close()
+
+	sessHandle, _, err := StartAuthSession(rw, HandleNull, HandleNull, make([]byte, 16), nil, SessionPolicy, AlgNull, AlgSHA1)
+	if err != nil {
+		t.Fatalf("StartAuthSession failed: %v", err)
+	}
+	defer FlushContext(rw, sessHandle)
+
+	sel := PCRSelection{Hash: AlgSHA1, PCRs: []int{0, 1, 2, 3}}
+	pcrs, err := ReadPCRs(rw, sel)
+	if err != nil {
+		t.Fatalf("ReadPCRs failed: %s", err)
+	}
+
+	// From TPM 2.0 Part 1, Selecting Multiple PCR:
+	// "The list of selectors is processed in order. The selected PCR are
+	// concatenated, with the lowest numbered PCR in the first selector being
+	// the first in the list and the highest numbered PCR in the last selector
+	// being the last."
+	//
+	// Concatenate all selected PCR values before hashing. sel.PCRs is already
+	// sorted in ascending order.
+	var expectedVal []byte
+	for _, pcr := range sel.PCRs {
+		expectedVal = append(expectedVal, pcrs[pcr]...)
+	}
+	t.Logf("expectedVal=%x", expectedVal)
+
+	// Hash algorithm must match the one in StartAuthSession.
+	expectedDigest := sha1.Sum(expectedVal)
+	t.Logf("expectedDigest=%x", expectedDigest)
+
+	tests := []struct {
+		desc           string
+		expectedDigest []byte
+		wantErr        bool
+	}{
+		// PolicyPCR expects a *digest* of the PCR value, not the value itself.
+		// Make sure PolicyPCR with value actually fails.
+		{desc: "success: digest passed in expectedDigest", expectedDigest: expectedDigest[:]},
+		{desc: "fail: value passed in expectedDigest", expectedDigest: expectedVal, wantErr: true},
+		{desc: "success: nil passed in expectedDigest", expectedDigest: nil},
+	}
+	for _, tt := range tests {
+		t.Run(tt.desc, func(t *testing.T) {
+			err := PolicyPCR(rw, sessHandle, tt.expectedDigest, sel)
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("got error: %v, want error: %v", err != nil, tt.wantErr)
+			}
+		})
+	}
+}