Repository: google/go-tpm
Pull request #385 (2024-12-23 02:07:46)

Add TPM2_HMAC implementation
- See definition in Part 3, Commands, section 15.4

ludi317: @chrisfenner Kindly requesting your review.
ludi317: @chrisfenner  @alexmwu @jkl73 Can you please review the PR? It is a very straightforward implementation of a command. 

Patch:
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index ede45ee..1d6cba8 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -602,6 +602,35 @@ type HashResponse struct {
 	Validation TPMTTKHashCheck
 }
 
+// Hmac is the input to TPM2_HMAC.
+// See definition in Part 3, Commands, section 15.5.
+type Hmac struct {
+	// HMAC key handle requiring an authorization session for the USER role
+	Handle AuthHandle `gotpm:"handle,auth"`
+	// HMAC data
+	Buffer TPM2BMaxBuffer
+	// Algorithm to use for HMAC
+	HashAlg TPMIAlgHash
+}
+
+// Command implements the Command interface.
+func (Hmac) Command() TPMCC { return TPMCCHMAC }
+
+// Execute executes the command and returns the response.
+func (cmd Hmac) Execute(t transport.TPM, s ...Session) (*HmacResponse, error) {
+	var rsp HmacResponse
+	if err := execute[HmacResponse](t, cmd, &rsp, s...); err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// HmacResponse is the response from TPM2_HMAC.
+type HmacResponse struct {
+	// the returned HMAC in a sized buffer
+	OutHMAC TPM2BDigest
+}
+
 // GetRandom is the input to TPM2_GetRandom.
 // See definition in Part 3, Commands, section 16.1
 type GetRandom struct {


Test patch:
diff --git a/tpm2/test/hmac_test.go b/tpm2/test/hmac_test.go
new file mode 100644
index 0000000..41feb84
--- /dev/null
+++ b/tpm2/test/hmac_test.go
@@ -0,0 +1,228 @@
+package tpm2test
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"io"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+func TestHMAC(t *testing.T) {
+	// connect to TPM simulator
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	// create HMAC key
+	createPrimary := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		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)
+	}()
+
+	data := []byte("test")
+
+	hmacCmd := Hmac{
+		Handle: AuthHandle{
+			Handle: rspCP.ObjectHandle,
+			Name:   rspCP.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		Buffer:  TPM2BMaxBuffer{Buffer: data},
+		HashAlg: TPMAlgSHA256,
+	}
+
+	// HMAC Key is not exportable and cannot be known.
+	// Calculate HMAC twice and confirm they are the same.
+	hmac1, err := hmacCmd.Execute(thetpm)
+	if err != nil {
+		t.Errorf("TPM2_HMAC failed: %v", err)
+	}
+	hmac2, err := hmacCmd.Execute(thetpm)
+	if err != nil {
+		t.Errorf("TPM2_HMAC failed: %v", err)
+	}
+	if !bytes.Equal(hmac1.OutHMAC.Buffer, hmac2.OutHMAC.Buffer) {
+		t.Errorf("TPM2_HMAC failed: hmacs are different")
+	}
+}
+
+func TestImportedHMACKey(t *testing.T) {
+	// configurable values
+	data := []byte("input data")
+	keySensitive := []byte("the hmac key")
+	persistentHandle := TPMHandle(0x81000000)
+
+	// connect to TPM simulator
+	theTPM, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer theTPM.Close()
+
+	// create primary key
+	primaryKey, err := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic:      New2B(RSASRKTemplate),
+	}.Execute(theTPM)
+
+	if err != nil {
+		t.Fatalf("could not generate SRK: %v", err)
+	}
+
+	// hmac template
+	sv := make([]byte, 32)
+	io.ReadFull(rand.Reader, sv)
+	privHash := crypto.SHA256.New()
+	privHash.Write(sv)
+	privHash.Write(keySensitive)
+
+	hmacTemplate := TPMTPublic{
+		Type:    TPMAlgKeyedHash,
+		NameAlg: TPMAlgSHA256,
+		ObjectAttributes: TPMAObject{
+			UserWithAuth: true,
+			SignEncrypt:  true,
+		},
+		AuthPolicy: TPM2BDigest{},
+		Parameters: NewTPMUPublicParms(TPMAlgKeyedHash,
+			&TPMSKeyedHashParms{
+				Scheme: TPMTKeyedHashScheme{
+					Scheme: TPMAlgHMAC,
+					Details: NewTPMUSchemeKeyedHash(TPMAlgHMAC,
+						&TPMSSchemeHMAC{
+							HashAlg: TPMAlgSHA256,
+						}),
+				},
+			}),
+		Unique: NewTPMUPublicID(
+			TPMAlgKeyedHash,
+			&TPM2BDigest{
+				Buffer: privHash.Sum(nil),
+			},
+		),
+	}
+
+	// sensitive data
+	sens2B := Marshal(TPMTSensitive{
+		SensitiveType: TPMAlgKeyedHash,
+		AuthValue:     TPM2BAuth{},
+		SeedValue: TPM2BDigest{
+			Buffer: sv,
+		},
+		Sensitive: NewTPMUSensitiveComposite(
+			TPMAlgKeyedHash,
+			&TPM2BSensitiveData{Buffer: keySensitive},
+		),
+	})
+
+	l := Marshal(TPM2BPrivate{Buffer: sens2B})
+
+	// import hmac key
+	importResponse, err := Import{
+		ParentHandle: AuthHandle{
+			Handle: primaryKey.ObjectHandle,
+			Name:   primaryKey.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		ObjectPublic: New2B(hmacTemplate),
+		Duplicate:    TPM2BPrivate{Buffer: l},
+	}.Execute(theTPM)
+	if err != nil {
+		t.Fatalf("could not import hmac key: %v", err)
+	}
+
+	// load hmac key
+	hmacKey, err := Load{
+		ParentHandle: AuthHandle{
+			Handle: primaryKey.ObjectHandle,
+			Name:   primaryKey.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		InPublic:  New2B(hmacTemplate),
+		InPrivate: importResponse.OutPrivate,
+	}.Execute(theTPM)
+	if err != nil {
+		t.Fatalf("could not load hmac key: %v", err)
+	}
+
+	FlushContext{FlushHandle: primaryKey.ObjectHandle}.Execute(theTPM)
+
+	// persist hmac key
+	_, err = EvictControl{
+		Auth: TPMRHOwner,
+		ObjectHandle: &NamedHandle{
+			Handle: hmacKey.ObjectHandle,
+			Name:   hmacKey.Name,
+		},
+		PersistentHandle: persistentHandle,
+	}.Execute(theTPM)
+	if err != nil {
+		t.Fatalf("could not persist hmac key: %v", err)
+	}
+
+	FlushContext{FlushHandle: hmacKey.ObjectHandle}.Execute(theTPM)
+
+	// calculate hmac using TPM
+	hmacCmd := Hmac{
+		Handle: AuthHandle{
+			Handle: persistentHandle,
+			Name:   ReadPublicName(t, persistentHandle, theTPM),
+			Auth:   PasswordAuth(nil),
+		},
+		Buffer:  TPM2BMaxBuffer{Buffer: data},
+		HashAlg: TPMAlgSHA256,
+	}
+
+	rspHMAC, err := hmacCmd.Execute(theTPM)
+	if err != nil {
+		t.Fatalf("TPM2_HMAC failed: %v", err)
+	}
+
+	// calculate hmac in usual way
+	hmacSha256 := hmac.New(sha256.New, keySensitive)
+	hmacSha256.Write(data)
+	result := hmacSha256.Sum(nil)
+
+	// compare hmac results
+	if !bytes.Equal(result, rspHMAC.OutHMAC.Buffer) {
+		t.Errorf("want %x got %x", result, rspHMAC.OutHMAC.Buffer)
+	}
+}