From 800190013bd560d00f58f8835df86e3e3eb689c7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:15:26 +0000 Subject: [PATCH 1/6] Add support for HMAC keys on TPM Added TypeHMAC to KeyType and WithHMAC(bits) option. Implemented createLocked, getPublicLocked, and Sign methods for HMAC keys. HMAC keys use SHA256 by default. Added validation to ensure bits is 0 or 256. Added tests for HMAC key creation, signing, and verification. Updated documentation to reflect HMAC support. Co-authored-by: rthellend <8751658+rthellend@users.noreply.github.com> --- README.md | 8 ++--- tpm.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++---- tpm_test.go | 60 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 547d4ca..79b9e73 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # tpm This package is an abstraction on top of the go-tpm libraries to use a local -TPM to create and use RSA, ECC, and AES keys that are bound to that TPM. The -keys can never be used without the TPM that was used to create them. +TPM to create and use RSA, ECC, AES, and HMAC keys that are bound to that TPM. +The keys can never be used without the TPM that was used to create them. Any number of keys can be created and used concurrently. The library takes care loading the right key in the TPM, as needed. -By default, 2048-bit RSA keys are created. AES keys, ECC keys, and RSA keys of -different sizes can also be created if the TPM supports them. +By default, 2048-bit RSA keys are created. AES keys, ECC keys, HMAC keys, and +RSA keys of different sizes can also be created if the TPM supports them. ## Example: diff --git a/tpm.go b/tpm.go index 7d9ff34..5000ef2 100644 --- a/tpm.go +++ b/tpm.go @@ -22,14 +22,14 @@ // SOFTWARE. // Package tpm is an abstraction on top of the go-tpm libraries to use a local -// TPM to create and use RSA, ECC, and AES keys that are bound to that TPM. The -// keys can never be used without the TPM that was used to create them. +// TPM to create and use RSA, ECC, AES, and HMAC keys that are bound to that TPM. +// The keys can never be used without the TPM that was used to create them. // // Any number of keys can be created and used concurrently. The library takes // care loading the right key in the TPM, as needed. // -// By default, 2048-bit RSA keys are created. AES keys, ECC keys, and RSA keys -// of different sizes can also be created if the TPM supports them. +// By default, 2048-bit RSA keys are created. AES keys, ECC keys, HMAC keys, and +// RSA keys of different sizes can also be created if the TPM supports them. package tpm import ( @@ -56,9 +56,10 @@ import ( ) const ( - TypeRSA KeyType = 1 - TypeECC KeyType = 2 - TypeAES KeyType = 3 + TypeRSA KeyType = 1 + TypeECC KeyType = 2 + TypeAES KeyType = 3 + TypeHMAC KeyType = 4 ) var ( @@ -77,6 +78,8 @@ func (t KeyType) String() string { return "ECC" case TypeAES: return "AES" + case TypeHMAC: + return "HMAC" default: return "" } @@ -192,6 +195,15 @@ func WithAES(bits int) KeyOption { } } +// WithHMAC indicates that an HMAC key should be created. +func WithHMAC(bits int) KeyOption { + return func(opts *keyOptions) { + opts.keyType = TypeHMAC + opts.bits = bits + opts.curve = nil + } +} + // CreateKey creates a new key that's ready to use. Keys can be serialized and // stored offline with [Key.Marshal], and restored with [TPM.UnmarshalKey]. The // serialized keys can only be restored using the same TPM. @@ -350,6 +362,51 @@ func (t *TPM) createLocked(opts ...KeyOption) ([]byte, error) { &tpm2.TPM2BDigest{Buffer: unique}, ), } + + case TypeHMAC: + if opt.bits != 0 && opt.bits != 256 { + return nil, fmt.Errorf("HMAC key size %d not supported", opt.bits) + } + unique := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, unique); err != nil { + return nil, fmt.Errorf("rand: %w", err) + } + public = tpm2.TPMTPublic{ + Type: tpm2.TPMAlgKeyedHash, + NameAlg: tpm2.TPMAlgSHA256, + ObjectAttributes: tpm2.TPMAObject{ + FixedTPM: true, + STClear: false, + FixedParent: true, + SensitiveDataOrigin: true, + UserWithAuth: true, + AdminWithPolicy: false, + NoDA: false, + EncryptedDuplication: false, + Restricted: false, + Decrypt: false, + SignEncrypt: true, + }, + Parameters: tpm2.NewTPMUPublicParms( + tpm2.TPMAlgKeyedHash, + &tpm2.TPMSKeyedHashParms{ + Scheme: tpm2.TPMTKeyedHashScheme{ + Scheme: tpm2.TPMAlgHMAC, + Details: tpm2.NewTPMUSchemeKeyedHash( + tpm2.TPMAlgHMAC, + &tpm2.TPMSSchemeHMAC{ + HashAlg: tpm2.TPMAlgSHA256, + }, + ), + }, + }, + ), + Unique: tpm2.NewTPMUPublicID( + tpm2.TPMAlgKeyedHash, + &tpm2.TPM2BDigest{Buffer: unique}, + ), + } + default: return nil, ErrWrongKeyType } @@ -553,6 +610,18 @@ func (k *Key) getPublicLocked() error { k.keyType = TypeAES k.bits = int(*bits) } + + case tpm2.TPMAlgKeyedHash: + keyedHashParms, err := outPublic.Parameters.KeyedHashDetail() + if err != nil { + return fmt.Errorf("TPM2_ReadPublic: %w", err) + } + if keyedHashParms.Scheme.Scheme == tpm2.TPMAlgHMAC { + k.keyType = TypeHMAC + // The key size is not explicitly available in the public area for HMAC keys. + // However, since we default to SHA256, we can assume 256 bits. + k.bits = 256 + } } return nil } @@ -704,6 +773,25 @@ func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signatur }) return b.Bytes() + case TypeHMAC: + resp, err := tpm2.Hmac{ + Handle: tpm2.AuthHandle{ + Handle: k.t.loadedHandle, + Name: tpm2.TPM2BName{ + Buffer: []byte(k.id), + }, + Auth: tpm2.PasswordAuth(k.t.objectAuth), + }, + Buffer: tpm2.TPM2BMaxBuffer{ + Buffer: digest, + }, + HashAlg: hashAlg.HashAlg, + }.Execute(k.t.tpm) + if err != nil { + return nil, fmt.Errorf("TPM2_HMAC: %w", err) + } + return resp.OutHMAC.Buffer, nil + default: return nil, ErrWrongKeyType } diff --git a/tpm_test.go b/tpm_test.go index db9b1e5..9244448 100644 --- a/tpm_test.go +++ b/tpm_test.go @@ -202,6 +202,66 @@ func TestAES(t *testing.T) { } } +func TestHMAC(t *testing.T) { + const ( + keyPassphrase = "blah" + payload = "Hello World!" + ) + + rwc, err := simulator.Get() + if err != nil { + t.Fatalf("simulator.Get: %v", err) + } + + tpm, err := New(WithTPM(rwc), WithObjectAuth([]byte(keyPassphrase))) + if err != nil { + t.Fatalf("New: %v", err) + } + defer tpm.Close() + + // Default size (SHA256) + key, err := tpm.CreateKey(WithHMAC(0)) + if err != nil { + t.Fatalf("tpm.CreateKey: %v", err) + } + + if got, want := key.Type(), TypeHMAC; got != want { + t.Fatalf("key.Type() = %d, want %d", got, want) + } + if got, want := key.Bits(), 256; got != want { + t.Fatalf("key.Bits() = %d, want %d", got, want) + } + + hashed := sha256.Sum256([]byte(payload)) + sig, err := key.Sign(nil, hashed[:], crypto.SHA256) + if err != nil { + t.Fatalf("Sign(): %v", err) + } + + // Verify by signing again + sig2, err := key.Sign(nil, hashed[:], crypto.SHA256) + if err != nil { + t.Fatalf("Sign() 2: %v", err) + } + + if !bytes.Equal(sig, sig2) { + t.Fatal("HMAC verification failed: signatures do not match") + } + + // Verify encryption/decryption fails + if _, err := key.Encrypt([]byte(payload)); err == nil { + t.Fatal("Encrypt should have failed") + } + if _, err := key.Decrypt(nil, []byte(payload), nil); err == nil { + t.Fatal("Decrypt should have failed") + } + + // Test invalid size + if _, err := tpm.CreateKey(WithHMAC(512)); err == nil { + t.Fatal("tpm.CreateKey(512) should have failed") + } +} + func TestMarshal(t *testing.T) { const ( keyPassphrase = "blah" From e33a27daa406f268e3423e1f48704dc5307f176b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:25:24 +0000 Subject: [PATCH 2/6] Add HMAC method to Key struct Implemented HMAC(message []byte) ([]byte, error) for HMAC keys. This allows computing HMAC directly on the message. Updated tests to verify the new method. Co-authored-by: rthellend <8751658+rthellend@users.noreply.github.com> --- tpm.go | 29 +++++++++++++++++++++++++++++ tpm_test.go | 9 +++++++++ 2 files changed, 38 insertions(+) diff --git a/tpm.go b/tpm.go index 5000ef2..55d5ec4 100644 --- a/tpm.go +++ b/tpm.go @@ -672,6 +672,35 @@ func (k *Key) Curve() elliptic.Curve { return k.curve } +// HMAC returns the HMAC signature of the message. +func (k *Key) HMAC(message []byte) ([]byte, error) { + k.t.mu.Lock() + defer k.t.mu.Unlock() + if err := k.loadLocked(); err != nil { + return nil, err + } + if k.keyType != TypeHMAC { + return nil, ErrWrongKeyType + } + resp, err := tpm2.Hmac{ + Handle: tpm2.AuthHandle{ + Handle: k.t.loadedHandle, + Name: tpm2.TPM2BName{ + Buffer: []byte(k.id), + }, + Auth: tpm2.PasswordAuth(k.t.objectAuth), + }, + Buffer: tpm2.TPM2BMaxBuffer{ + Buffer: message, + }, + HashAlg: tpm2.TPMAlgSHA256, + }.Execute(k.t.tpm) + if err != nil { + return nil, fmt.Errorf("TPM2_HMAC: %w", err) + } + return resp.OutHMAC.Buffer, nil +} + // Sign signs a digest with the key (RSA only). func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { k.t.mu.Lock() diff --git a/tpm_test.go b/tpm_test.go index 9244448..e240cb3 100644 --- a/tpm_test.go +++ b/tpm_test.go @@ -248,6 +248,15 @@ func TestHMAC(t *testing.T) { t.Fatal("HMAC verification failed: signatures do not match") } + // Test HMAC method + mac, err := key.HMAC(hashed[:]) + if err != nil { + t.Fatalf("key.HMAC: %v", err) + } + if !bytes.Equal(sig, mac) { + t.Fatal("HMAC verification failed: signatures do not match") + } + // Verify encryption/decryption fails if _, err := key.Encrypt([]byte(payload)); err == nil { t.Fatal("Encrypt should have failed") From 91f8c703a4bef43aed128637e9dea54b94a05b1d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:00:42 +0000 Subject: [PATCH 3/6] Add support for HMAC keys (SHA256, SHA384, SHA512) Refactored HMAC logic to `hmacLocked`. Updated `createLocked` to support multiple hash algorithms. Updated `getPublicLocked` to infer key size from hash algorithm. Updated documentation and tests. Co-authored-by: rthellend <8751658+rthellend@users.noreply.github.com> --- tpm.go | 81 ++++++++++++++++++++++++++++++-------------- tpm_test.go | 97 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 117 insertions(+), 61 deletions(-) diff --git a/tpm.go b/tpm.go index 55d5ec4..5e16248 100644 --- a/tpm.go +++ b/tpm.go @@ -364,16 +364,23 @@ func (t *TPM) createLocked(opts ...KeyOption) ([]byte, error) { } case TypeHMAC: - if opt.bits != 0 && opt.bits != 256 { - return nil, fmt.Errorf("HMAC key size %d not supported", opt.bits) + var hashAlg tpm2.TPMAlgID + switch opt.bits { + case 384: + hashAlg = tpm2.TPMAlgSHA384 + case 512: + hashAlg = tpm2.TPMAlgSHA512 + default: + hashAlg = tpm2.TPMAlgSHA256 } + unique := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, unique); err != nil { return nil, fmt.Errorf("rand: %w", err) } public = tpm2.TPMTPublic{ Type: tpm2.TPMAlgKeyedHash, - NameAlg: tpm2.TPMAlgSHA256, + NameAlg: hashAlg, ObjectAttributes: tpm2.TPMAObject{ FixedTPM: true, STClear: false, @@ -395,7 +402,7 @@ func (t *TPM) createLocked(opts ...KeyOption) ([]byte, error) { Details: tpm2.NewTPMUSchemeKeyedHash( tpm2.TPMAlgHMAC, &tpm2.TPMSSchemeHMAC{ - HashAlg: tpm2.TPMAlgSHA256, + HashAlg: hashAlg, }, ), }, @@ -618,9 +625,18 @@ func (k *Key) getPublicLocked() error { } if keyedHashParms.Scheme.Scheme == tpm2.TPMAlgHMAC { k.keyType = TypeHMAC - // The key size is not explicitly available in the public area for HMAC keys. - // However, since we default to SHA256, we can assume 256 bits. - k.bits = 256 + details, err := keyedHashParms.Scheme.Details.HMAC() + if err != nil { + return fmt.Errorf("TPM2_ReadPublic: %w", err) + } + switch details.HashAlg { + case tpm2.TPMAlgSHA384: + k.bits = 384 + case tpm2.TPMAlgSHA512: + k.bits = 512 + default: + k.bits = 256 + } } } return nil @@ -682,6 +698,32 @@ func (k *Key) HMAC(message []byte) ([]byte, error) { if k.keyType != TypeHMAC { return nil, ErrWrongKeyType } + // For HMAC, the hash algorithm is fixed at creation time. + // We use SHA256 as a default/placeholder if we don't know it, + // but ideally we should use the key's hash algorithm. + // Since we don't store it explicitly, let's assume the key's + // algorithm matches what we'd expect (e.g. SHA256 for 256 bits). + // But wait, the HMAC command takes a HashAlg. This is the algorithm + // used for the HMAC calculation. It should match the key's scheme. + // We can try to infer it or just use SHA256 if 256 bits? + // Actually, the TPM2_HMAC command documentation says "HashAlg: The hash algorithm to use". + // It's likely this should match the key's defined scheme if it's restricted. + // Let's rely on k.hmacLocked to pick the right one. + return k.hmacLocked(message, tpm2.TPMAlgNull) +} + +func (k *Key) hmacLocked(message []byte, hashAlg tpm2.TPMAlgID) ([]byte, error) { + if hashAlg == tpm2.TPMAlgNull { + switch k.bits { + case 384: + hashAlg = tpm2.TPMAlgSHA384 + case 512: + hashAlg = tpm2.TPMAlgSHA512 + default: + hashAlg = tpm2.TPMAlgSHA256 + } + } + resp, err := tpm2.Hmac{ Handle: tpm2.AuthHandle{ Handle: k.t.loadedHandle, @@ -693,7 +735,7 @@ func (k *Key) HMAC(message []byte) ([]byte, error) { Buffer: tpm2.TPM2BMaxBuffer{ Buffer: message, }, - HashAlg: tpm2.TPMAlgSHA256, + HashAlg: hashAlg, }.Execute(k.t.tpm) if err != nil { return nil, fmt.Errorf("TPM2_HMAC: %w", err) @@ -701,7 +743,10 @@ func (k *Key) HMAC(message []byte) ([]byte, error) { return resp.OutHMAC.Buffer, nil } -// Sign signs a digest with the key (RSA only). +// Sign signs a digest with the key (RSA and ECC) or computes the HMAC +// (HMAC keys). +// +// For HMAC keys, it is functionally equivalent to calling HMAC(). func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { k.t.mu.Lock() defer k.t.mu.Unlock() @@ -803,23 +848,7 @@ func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signatur return b.Bytes() case TypeHMAC: - resp, err := tpm2.Hmac{ - Handle: tpm2.AuthHandle{ - Handle: k.t.loadedHandle, - Name: tpm2.TPM2BName{ - Buffer: []byte(k.id), - }, - Auth: tpm2.PasswordAuth(k.t.objectAuth), - }, - Buffer: tpm2.TPM2BMaxBuffer{ - Buffer: digest, - }, - HashAlg: hashAlg.HashAlg, - }.Execute(k.t.tpm) - if err != nil { - return nil, fmt.Errorf("TPM2_HMAC: %w", err) - } - return resp.OutHMAC.Buffer, nil + return k.hmacLocked(digest, hashAlg.HashAlg) default: return nil, ErrWrongKeyType diff --git a/tpm_test.go b/tpm_test.go index e240cb3..7ff956d 100644 --- a/tpm_test.go +++ b/tpm_test.go @@ -219,55 +219,82 @@ func TestHMAC(t *testing.T) { } defer tpm.Close() + // Test HMAC SHA256, SHA384, SHA512 + tests := []struct { + bits int + hash crypto.Hash + }{ + {256, crypto.SHA256}, + {384, crypto.SHA384}, + {512, crypto.SHA512}, + } + // Default size (SHA256) key, err := tpm.CreateKey(WithHMAC(0)) if err != nil { t.Fatalf("tpm.CreateKey: %v", err) } - - if got, want := key.Type(), TypeHMAC; got != want { - t.Fatalf("key.Type() = %d, want %d", got, want) - } if got, want := key.Bits(), 256; got != want { t.Fatalf("key.Bits() = %d, want %d", got, want) } - hashed := sha256.Sum256([]byte(payload)) - sig, err := key.Sign(nil, hashed[:], crypto.SHA256) - if err != nil { - t.Fatalf("Sign(): %v", err) - } + for _, tc := range tests { + key, err := tpm.CreateKey(WithHMAC(tc.bits)) + if err != nil { + t.Fatalf("tpm.CreateKey(%d): %v", tc.bits, err) + } - // Verify by signing again - sig2, err := key.Sign(nil, hashed[:], crypto.SHA256) - if err != nil { - t.Fatalf("Sign() 2: %v", err) - } + if got, want := key.Type(), TypeHMAC; got != want { + t.Fatalf("key.Type() = %d, want %d", got, want) + } + if got, want := key.Bits(), tc.bits; got != want { + t.Fatalf("key.Bits() = %d, want %d", got, want) + } - if !bytes.Equal(sig, sig2) { - t.Fatal("HMAC verification failed: signatures do not match") - } + var hashed []byte + if tc.hash == crypto.SHA256 { + h := sha256.Sum256([]byte(payload)) + hashed = h[:] + } else { + // For simplicity in test, just use empty or simple data, + // but we should match the hash size to be realistic if needed. + // Actually Sign() takes a digest, so we should provide a digest. + h := tc.hash.New() + h.Write([]byte(payload)) + hashed = h.Sum(nil) + } - // Test HMAC method - mac, err := key.HMAC(hashed[:]) - if err != nil { - t.Fatalf("key.HMAC: %v", err) - } - if !bytes.Equal(sig, mac) { - t.Fatal("HMAC verification failed: signatures do not match") - } + sig, err := key.Sign(nil, hashed, tc.hash) + if err != nil { + t.Fatalf("Sign(): %v", err) + } - // Verify encryption/decryption fails - if _, err := key.Encrypt([]byte(payload)); err == nil { - t.Fatal("Encrypt should have failed") - } - if _, err := key.Decrypt(nil, []byte(payload), nil); err == nil { - t.Fatal("Decrypt should have failed") - } + // HMAC is deterministic. Verify that signing the same data twice produces the same signature. + sig2, err := key.Sign(nil, hashed, tc.hash) + if err != nil { + t.Fatalf("Sign() 2: %v", err) + } - // Test invalid size - if _, err := tpm.CreateKey(WithHMAC(512)); err == nil { - t.Fatal("tpm.CreateKey(512) should have failed") + if !bytes.Equal(sig, sig2) { + t.Fatal("HMAC signatures should be deterministic, but they do not match") + } + + // Test HMAC method + mac, err := key.HMAC(hashed) + if err != nil { + t.Fatalf("key.HMAC: %v", err) + } + if !bytes.Equal(sig, mac) { + t.Fatal("HMAC verification failed: signatures do not match") + } + + // Verify encryption/decryption fails + if _, err := key.Encrypt([]byte(payload)); err == nil { + t.Fatal("Encrypt should have failed") + } + if _, err := key.Decrypt(nil, []byte(payload), nil); err == nil { + t.Fatal("Decrypt should have failed") + } } } From b1ed28f2c778f5947fe6181b3e0eccde24fdc715 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:16:58 +0000 Subject: [PATCH 4/6] Add support for HMAC keys (SHA256, SHA384, SHA512) Refactored HMAC logic to `hmacLocked`. Updated `createLocked` to strictly validate key sizes. Updated `getPublicLocked` to strictly map hash algorithms to key bits. Updated `HMAC` and `Sign` methods to use `hmacLocked`. Updated tests to cover different key sizes and verify error conditions. Co-authored-by: rthellend <8751658+rthellend@users.noreply.github.com> --- tpm.go | 48 +++++++++++++++++++++--------------------------- tpm_test.go | 5 +++++ 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/tpm.go b/tpm.go index 5e16248..0a428ec 100644 --- a/tpm.go +++ b/tpm.go @@ -370,8 +370,10 @@ func (t *TPM) createLocked(opts ...KeyOption) ([]byte, error) { hashAlg = tpm2.TPMAlgSHA384 case 512: hashAlg = tpm2.TPMAlgSHA512 - default: + case 0, 256: hashAlg = tpm2.TPMAlgSHA256 + default: + return nil, fmt.Errorf("HMAC key size %d not supported", opt.bits) } unique := make([]byte, 32) @@ -634,8 +636,10 @@ func (k *Key) getPublicLocked() error { k.bits = 384 case tpm2.TPMAlgSHA512: k.bits = 512 - default: + case tpm2.TPMAlgSHA256: k.bits = 256 + default: + k.bits = -1 } } } @@ -698,30 +702,20 @@ func (k *Key) HMAC(message []byte) ([]byte, error) { if k.keyType != TypeHMAC { return nil, ErrWrongKeyType } - // For HMAC, the hash algorithm is fixed at creation time. - // We use SHA256 as a default/placeholder if we don't know it, - // but ideally we should use the key's hash algorithm. - // Since we don't store it explicitly, let's assume the key's - // algorithm matches what we'd expect (e.g. SHA256 for 256 bits). - // But wait, the HMAC command takes a HashAlg. This is the algorithm - // used for the HMAC calculation. It should match the key's scheme. - // We can try to infer it or just use SHA256 if 256 bits? - // Actually, the TPM2_HMAC command documentation says "HashAlg: The hash algorithm to use". - // It's likely this should match the key's defined scheme if it's restricted. - // Let's rely on k.hmacLocked to pick the right one. - return k.hmacLocked(message, tpm2.TPMAlgNull) -} - -func (k *Key) hmacLocked(message []byte, hashAlg tpm2.TPMAlgID) ([]byte, error) { - if hashAlg == tpm2.TPMAlgNull { - switch k.bits { - case 384: - hashAlg = tpm2.TPMAlgSHA384 - case 512: - hashAlg = tpm2.TPMAlgSHA512 - default: - hashAlg = tpm2.TPMAlgSHA256 - } + return k.hmacLocked(message) +} + +func (k *Key) hmacLocked(message []byte) ([]byte, error) { + var hashAlg tpm2.TPMAlgID + switch k.bits { + case 384: + hashAlg = tpm2.TPMAlgSHA384 + case 512: + hashAlg = tpm2.TPMAlgSHA512 + case 256: + hashAlg = tpm2.TPMAlgSHA256 + default: + return nil, ErrWrongKeyType } resp, err := tpm2.Hmac{ @@ -848,7 +842,7 @@ func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signatur return b.Bytes() case TypeHMAC: - return k.hmacLocked(digest, hashAlg.HashAlg) + return k.hmacLocked(digest) default: return nil, ErrWrongKeyType diff --git a/tpm_test.go b/tpm_test.go index 7ff956d..66199b3 100644 --- a/tpm_test.go +++ b/tpm_test.go @@ -296,6 +296,11 @@ func TestHMAC(t *testing.T) { t.Fatal("Decrypt should have failed") } } + + // Test invalid size + if _, err := tpm.CreateKey(WithHMAC(511)); err == nil { + t.Fatal("tpm.CreateKey(511) should have failed") + } } func TestMarshal(t *testing.T) { From ed55f587d4e4a33eecc95c046cf891def83f0c4f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:27:12 +0000 Subject: [PATCH 5/6] Add support for HMAC keys (SHA256, SHA384, SHA512) Refactored HMAC logic to `hmacLocked`. Updated `createLocked` to strictly validate key sizes. Updated `getPublicLocked` to strictly map hash algorithms to key bits. Updated `HMAC` and `Sign` methods to use `hmacLocked`. Updated `TestHMAC` to verify `HMAC` uses message payload and check for determinism. Co-authored-by: rthellend <8751658+rthellend@users.noreply.github.com> --- tpm_test.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tpm_test.go b/tpm_test.go index 66199b3..4b0a336 100644 --- a/tpm_test.go +++ b/tpm_test.go @@ -279,13 +279,24 @@ func TestHMAC(t *testing.T) { t.Fatal("HMAC signatures should be deterministic, but they do not match") } - // Test HMAC method - mac, err := key.HMAC(hashed) + // Test HMAC method on the original payload. + mac, err := key.HMAC([]byte(payload)) if err != nil { t.Fatalf("key.HMAC: %v", err) } - if !bytes.Equal(sig, mac) { - t.Fatal("HMAC verification failed: signatures do not match") + + // HMAC is deterministic. Verify that HMACing the same data twice produces the same MAC. + mac2, err := key.HMAC([]byte(payload)) + if err != nil { + t.Fatalf("key.HMAC 2: %v", err) + } + if !bytes.Equal(mac, mac2) { + t.Fatal("HMAC results should be deterministic, but they do not match") + } + + // The result of HMAC(message) should be different from Sign(hash(message)). + if bytes.Equal(sig, mac) { + t.Fatal("key.Sign(hash) and key.HMAC(message) should not produce the same result") } // Verify encryption/decryption fails From 01a2ffd6bf1d097a3f5b20acf63c720a740cc064 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:28:14 +0000 Subject: [PATCH 6/6] Add support for HMAC keys (SHA256, SHA384, SHA512) Refactored HMAC logic to `hmacLocked`. Updated `createLocked` to strictly validate key sizes. Updated `getPublicLocked` to strictly map hash algorithms to key bits. Updated `HMAC` and `Sign` methods to use `hmacLocked`. Updated `TestHMAC` to verify `HMAC` uses message payload and check for determinism. Removed potentially confusing comment from `Sign` documentation. Co-authored-by: rthellend <8751658+rthellend@users.noreply.github.com> --- tpm.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tpm.go b/tpm.go index 0a428ec..a711254 100644 --- a/tpm.go +++ b/tpm.go @@ -739,8 +739,6 @@ func (k *Key) hmacLocked(message []byte) ([]byte, error) { // Sign signs a digest with the key (RSA and ECC) or computes the HMAC // (HMAC keys). -// -// For HMAC keys, it is functionally equivalent to calling HMAC(). func (k *Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { k.t.mu.Lock() defer k.t.mu.Unlock()