Repository: google/go-tpm
Pull request #351 (2024-02-16 18:49:29)

Add TPM2_HMAC_Start implementation
- See definition in Part 3, Commands, section 17.2

nckrss: I updated the `Dockerfile` to use `golang:1.21` because using `golang:latest` introduces incompatible changes between the linter v1.52.2 and go 1.22.  Updating the linter to v1.56.2 (latest) raises new lint errors not in scope for this PR.  If you're open to updating dependencies, I can open a separate PR to update the dependencies and fix lint errors.

Patch:
diff --git a/Dockerfile b/Dockerfile
index 347204c..a5f2021 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:latest
+FROM golang:1.21
 # We need OpenSSL headers to build the simulator
 RUN apt-get update && apt-get install -y \
     libssl-dev \
diff --git a/tpm2/constants.go b/tpm2/constants.go
index bb517c3..df87c36 100644
--- a/tpm2/constants.go
+++ b/tpm2/constants.go
@@ -634,6 +634,18 @@ const (
 	TPMHTAC            TPMHT = 0x90
 )
 
+// Saved Context transient object handles.
+// See definition in Part 2: Structures, section 14.6.2
+// Context Handle Values come from table 211
+const (
+	// an ordinary transient object
+	TPMIDHSavedTransient TPMIDHSaved = 0x80000000
+	// a sequence object
+	TPMIDHSavedSequence TPMIDHSaved = 0x80000001
+	// a transient object with the stClear attribute SET
+	TPMIDHSavedTransientClear TPMIDHSaved = 0x80000002
+)
+
 // TPMHandle represents a TPM_HANDLE.
 // See definition in Part 2: Structures, section 7.1.
 type TPMHandle uint32
diff --git a/tpm2/structures.go b/tpm2/structures.go
index 3f9bd04..b8b9d33 100644
--- a/tpm2/structures.go
+++ b/tpm2/structures.go
@@ -107,14 +107,21 @@ func (h TPMHandle) HandleValue() uint32 {
 // only PCR, session, and permanent values have known constant Names.
 // See definition in part 1: Architecture, section 16.
 func (h TPMHandle) KnownName() *TPM2BName {
-	switch (byte)(h >> 24) {
-	case 0x00, 0x02, 0x03, 0x40:
+	switch (TPMHT)(h >> 24) {
+	case TPMHTPCR, TPMHTHMACSession, TPMHTPolicySession, TPMHTPermanent:
 		result := make([]byte, 4)
 		binary.BigEndian.PutUint32(result, h.HandleValue())
 		return &TPM2BName{Buffer: result}
-	default:
-		return nil
+	case TPMHTTransient:
+		// The Name of a sequence object is an Empty Buffer
+		// See part 1: Architecture, section 32.4.5
+		if h == TPMIDHSavedSequence {
+			return &TPM2BName{
+				Buffer: []byte{},
+			}
+		}
 	}
+	return nil
 }
 
 // TPMAAlgorithm represents a TPMA_ALGORITHM.
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index 9b0556b..45a4bdf 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -535,6 +535,36 @@ type HashSequenceStartResponse struct {
 	SequenceHandle TPMIDHObject
 }
 
+// HmacStart is the input to TPM2_HMAC_Start.
+// See definition in Part 3, Commands, section 17.2.2
+type HmacStart struct {
+	// HMAC key handle requiring an authorization session for the USER role
+	Handle AuthHandle `gotpm:"handle,auth"`
+	// authorization value for subsequent use of the sequence
+	Auth TPM2BAuth
+	// the hash algorithm to use for the hmac sequence
+	HashAlg TPMIAlgHash
+}
+
+// Command implements the Command interface.
+func (HmacStart) Command() TPMCC { return TPMCCHMACStart }
+
+// Execute executes the command and returns the response.
+func (cmd HmacStart) Execute(t transport.TPM, s ...Session) (*HmacStartResponse, error) {
+	var rsp HmacStartResponse
+	if err := execute[HmacStartResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// HmacStartResponse is the response from TPM2_HMAC_Start.
+// See definition in Part 3, Commands, section 17.2.2
+type HmacStartResponse struct {
+	// a handle to reference the sequence
+	SequenceHandle TPMIDHObject `gotpm:"handle"`
+}
+
 // SequenceUpdate is the input to TPM2_SequenceUpdate.
 // See definition in Part 3, Commands, section 17.4
 type SequenceUpdate struct {


Test patch:
diff --git a/tpm2/test/hmac_start_test.go b/tpm2/test/hmac_start_test.go
new file mode 100644
index 0000000..88ef4a4
--- /dev/null
+++ b/tpm2/test/hmac_start_test.go
@@ -0,0 +1,141 @@
+package tpm2test
+
+import (
+	"bytes"
+	"crypto/rand"
+	"fmt"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestHmacStart(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	run := func(t *testing.T, data []byte, password []byte, hierarchy TPMHandle, thetpm transport.TPM) []byte {
+		maxInputBuffer := 1024
+
+		sas, sasCloser, err := HMACSession(thetpm, TPMAlgSHA256, 16)
+		if err != nil {
+			t.Fatalf("could not create hmac key authorization session: %v", err)
+		}
+		defer func() {
+			_ = sasCloser()
+		}()
+
+		createPrimary := CreatePrimary{
+			PrimaryHandle: AuthHandle{
+				Handle: hierarchy,
+				Auth:   sas,
+			},
+			InPublic: New2B(TPMTPublic{
+				Type:    TPMAlgKeyedHash,
+				NameAlg: TPMAlgSHA256,
+				ObjectAttributes: TPMAObject{
+					SignEncrypt:         true,
+					FixedTPM:            true,
+					FixedParent:         true,
+					SensitiveDataOrigin: true,
+					UserWithAuth:        true,
+				},
+				Parameters: NewTPMUPublicParms(TPMAlgKeyedHash,
+					&TPMSKeyedHashParms{
+						Scheme: TPMTKeyedHashScheme{
+							Scheme: TPMAlgHMAC,
+							Details: NewTPMUSchemeKeyedHash(TPMAlgHMAC,
+								&TPMSSchemeHMAC{
+									HashAlg: TPMAlgSHA256,
+								}),
+						},
+					}),
+			}),
+		}
+
+		rspCP, err := createPrimary.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("CreatePrimary HMAC key failed: %v", err)
+		}
+
+		flushContext := FlushContext{FlushHandle: rspCP.ObjectHandle}
+		defer func() {
+			_, _ = flushContext.Execute(thetpm)
+		}()
+
+		hmacStart := HmacStart{
+			Handle: AuthHandle{
+				Handle: rspCP.ObjectHandle,
+				Name:   rspCP.Name,
+				Auth:   sas,
+			},
+			Auth: TPM2BAuth{
+				Buffer: password,
+			},
+			HashAlg: TPMAlgNull,
+		}
+
+		rspHS, err := hmacStart.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("HmacStart failed: %v", err)
+		}
+
+		authHandle := AuthHandle{
+			Handle: rspHS.SequenceHandle,
+			Auth:   PasswordAuth(password),
+		}
+		for len(data) > maxInputBuffer {
+			sequenceUpdate := SequenceUpdate{
+				SequenceHandle: authHandle,
+				Buffer: TPM2BMaxBuffer{
+					Buffer: data[:maxInputBuffer],
+				},
+			}
+			_, err = sequenceUpdate.Execute(thetpm)
+			if err != nil {
+				t.Fatalf("SequenceUpdate failed: %v", err)
+			}
+
+			data = data[maxInputBuffer:]
+		}
+
+		sequenceComplete := SequenceComplete{
+			SequenceHandle: authHandle,
+			Buffer: TPM2BMaxBuffer{
+				Buffer: data,
+			},
+			Hierarchy: hierarchy,
+		}
+
+		rspSC, err := sequenceComplete.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("SequenceComplete failed: %v", err)
+		}
+		return rspSC.Result.Buffer
+
+	}
+
+	bufferSizes := []int{512, 1024, 2048, 4096}
+	password := make([]byte, 8)
+	_, _ = rand.Read(password)
+
+	for _, bufferSize := range bufferSizes {
+		data := make([]byte, bufferSize)
+		for _, hierarchy := range []TPMHandle{TPMRHNull, TPMRHOwner, TPMRHEndorsement} {
+			t.Run(fmt.Sprintf("Null hierarchy [bufferSize=%d]", bufferSize), func(t *testing.T) {
+				_, _ = rand.Read(data)
+				// HMAC Key is not exported and can not be externally validated,
+				// run HMAC twice with same data and confirm they are the same
+				hmac1 := run(t, data, password, hierarchy, thetpm)
+				hmac2 := run(t, data, password, hierarchy, thetpm)
+				if !bytes.Equal(hmac1, hmac2) {
+					t.Errorf("hmac %x is not expected %x", hmac1, hmac2)
+				}
+			})
+		}
+	}
+}