Repository: google/go-tpm (go)

Issue:
Invalid handling of KnownName() and Empty Buffer responses
KnownName() has a case for `TPMHTTransient` that conflates transient handles with sequence object handle values of a TPMS_CONTEXT.  Undefined behavior will result when more than one transient handle exists.

The TPM specification also defines an Empty Buffer as the Name for sequence objects (Part 1: Architecture, section 32.4.5), which is a special case that must be handled for `TPM2_SequenceUpdate` and `TPM2_SequenceComplete` commands.  Users of the library will receive command errors of the form:

```text
SequenceComplete failed: missing Name for 'SequenceHandle' parameter
```

This error should not occur because `Name` for these commands is the Empty Buffer.

Pull request description:
Fix KnownName() and Empty Buffer sequence handle Names
This PR resolves a bug introduced to KnownName handling and introduces a struct tag for commands that allow for the `Name` to be the Empty Buffer, namely TPM2_SequenceUpdate and TPM2_SequenceComplete.

- [x] Add tests for multiple transient objects in an HMAC sequence
- [x] Handle special case for sequence object handle Name fields
- [x] Relax HmacStart.Handle type from AuthHandle to handle
- [x] Fix prior RSA test that silently succeeded due to KnownName() bug

Fixes #370 

Patch:
diff --git a/tpm2/reflect.go b/tpm2/reflect.go
index 60c9d58..d32473e 100644
--- a/tpm2/reflect.go
+++ b/tpm2/reflect.go
@@ -813,6 +813,16 @@ func cmdNames[R any](cmd Command[R, *R]) ([]TPM2BName, error) {
 			return nil, fmt.Errorf("invalid 'handle'-tagged member of %q: %v",
 				reflect.TypeOf(cmd), err)
 		}
+
+		// Special case: handles with an empty name buffer (anonymous:anon)
+		// See part 1: Architecture, section 32.4.5:
+		// The Name of a sequence object is an Empty Buffer (sized array with no
+		// data; indicated by a size field of zero followed by an array
+		// containing no elements)
+		if hasTag(reflect.ValueOf(cmd).Type().Field(i), "anon") {
+			continue
+		}
+
 		name := h.KnownName()
 		if name == nil {
 			return nil, fmt.Errorf("missing Name for '%v' parameter",
diff --git a/tpm2/structures.go b/tpm2/structures.go
index 78b031e..11a0261 100644
--- a/tpm2/structures.go
+++ b/tpm2/structures.go
@@ -127,14 +127,6 @@ func (h TPMHandle) KnownName() *TPM2BName {
 		result := make([]byte, 4)
 		binary.BigEndian.PutUint32(result, h.HandleValue())
 		return &TPM2BName{Buffer: result}
-	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
 }
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index 7b124f7..ede45ee 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -658,8 +658,8 @@ type HashSequenceStartResponse struct {
 // 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"`
+	// HMAC key handle
+	Handle handle `gotpm:"handle,auth"`
 	// authorization value for subsequent use of the sequence
 	Auth TPM2BAuth
 	// the hash algorithm to use for the hmac sequence
@@ -689,7 +689,7 @@ type HmacStartResponse struct {
 // See definition in Part 3, Commands, section 17.4
 type SequenceUpdate struct {
 	// handle for the sequence object
-	SequenceHandle handle `gotpm:"handle,auth"`
+	SequenceHandle handle `gotpm:"handle,auth,anon"`
 	// data to be added to hash
 	Buffer TPM2BMaxBuffer
 }
@@ -713,7 +713,7 @@ type SequenceUpdateResponse struct{}
 // See definition in Part 3, Commands, section 17.5
 type SequenceComplete struct {
 	// authorization for the sequence
-	SequenceHandle handle `gotpm:"handle,auth"`
+	SequenceHandle handle `gotpm:"handle,auth,anon"`
 	// data to be added to the hash/HMAC
 	Buffer TPM2BMaxBuffer
 	// hierarchy of the ticket for a hash


Test patch:
diff --git a/tpm2/test/hmac_start_test.go b/tpm2/test/hmac_start_test.go
index 88ef4a4..cfb6111 100644
--- a/tpm2/test/hmac_start_test.go
+++ b/tpm2/test/hmac_start_test.go
@@ -88,6 +88,7 @@ func TestHmacStart(t *testing.T) {
 			Handle: rspHS.SequenceHandle,
 			Auth:   PasswordAuth(password),
 		}
+
 		for len(data) > maxInputBuffer {
 			sequenceUpdate := SequenceUpdate{
 				SequenceHandle: authHandle,
@@ -123,10 +124,15 @@ func TestHmacStart(t *testing.T) {
 	password := make([]byte, 8)
 	_, _ = rand.Read(password)
 
+	hierarchies := map[string]TPMHandle{
+		"Null":        TPMRHNull,
+		"Owner":       TPMRHOwner,
+		"Endorsement": TPMRHEndorsement}
+
 	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) {
+		for name, hierarchy := range hierarchies {
+			t.Run(fmt.Sprintf("%s hierarchy [bufferSize=%d]", name, 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
@@ -138,4 +144,229 @@ func TestHmacStart(t *testing.T) {
 			})
 		}
 	}
+
+	t.Run("Same key multiple sequences", func(t *testing.T) {
+		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: TPMRHNull,
+				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,
+		}
+
+		rspHS1, err := hmacStart.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("HmacStart failed: %v", err)
+		}
+
+		authHandle1 := AuthHandle{
+			Handle: rspHS1.SequenceHandle,
+			Auth:   PasswordAuth(password),
+		}
+
+		rspHS2, err := hmacStart.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("HmacStart failed: %v", err)
+		}
+
+		authHandle2 := AuthHandle{
+			Handle: rspHS2.SequenceHandle,
+			Auth:   PasswordAuth(password),
+		}
+
+		if rspHS1.SequenceHandle.HandleValue() == rspHS2.SequenceHandle.HandleValue() {
+			t.Error("sequence handles are not unique")
+		}
+
+		sequenceComplete1 := SequenceComplete{
+			SequenceHandle: authHandle1,
+		}
+
+		_, err = sequenceComplete1.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("SequenceComplete failed: %v", err)
+		}
+
+		sequenceComplete2 := SequenceComplete{
+			SequenceHandle: authHandle2,
+		}
+
+		_, err = sequenceComplete2.Execute(thetpm)
+		if err != nil {
+			t.Fatalf("SequenceComplete failed: %v", err)
+		}
+
+	})
+
+}
+
+func TestHmacStartNoKeyAuth(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
+
+		createPrimary := CreatePrimary{
+			PrimaryHandle: hierarchy,
+			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: NamedHandle{
+				Handle: rspCP.ObjectHandle,
+				Name:   rspCP.Name,
+			},
+			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
+
+	}
+
+	password := make([]byte, 8)
+	_, _ = rand.Read(password)
+
+	hierarchies := map[string]TPMHandle{
+		"Null":        TPMRHNull,
+		"Owner":       TPMRHOwner,
+		"Endorsement": TPMRHEndorsement}
+
+	data := make([]byte, 1024)
+	for name, hierarchy := range hierarchies {
+		t.Run(fmt.Sprintf("%s hierarchy [bufferSize=1024]", name), 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)
+			}
+		})
+	}
+
 }
diff --git a/tpm2/test/rsa_encryption_test.go b/tpm2/test/rsa_encryption_test.go
index 926e23d..1701185 100644
--- a/tpm2/test/rsa_encryption_test.go
+++ b/tpm2/test/rsa_encryption_test.go
@@ -114,7 +114,10 @@ func TestRSAEncryption(t *testing.T) {
 	}
 
 	decryptCmd := RSADecrypt{
-		KeyHandle:  loadRsp.ObjectHandle,
+		KeyHandle: NamedHandle{
+			Handle: loadRsp.ObjectHandle,
+			Name:   loadRsp.Name,
+		},
 		CipherText: TPM2BPublicKeyRSA{Buffer: encryptRsp.OutData.Buffer},
 		InScheme: TPMTRSADecrypt{
 			Scheme: TPMAlgOAEP,