Repository: google/go-tpm
Pull request #384 (2024-12-20 17:43:18)

Improve TPMUSymKeyBits and TPMUSymMode


Patch:
diff --git a/tpm2/structures.go b/tpm2/structures.go
index fb4685c..503bdd8 100644
--- a/tpm2/structures.go
+++ b/tpm2/structures.go
@@ -1368,7 +1368,7 @@ type SymKeyBitsContents interface {
 // create implements the unmarshallableWithHint interface.
 func (u *TPMUSymKeyBits) create(hint int64) (reflect.Value, error) {
 	switch TPMAlgID(hint) {
-	case TPMAlgAES:
+	case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia:
 		var contents boxed[TPMKeyBits]
 		u.contents = &contents
 		u.selector = TPMAlgID(hint)
@@ -1388,7 +1388,7 @@ func (u TPMUSymKeyBits) get(hint int64) (reflect.Value, error) {
 		return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector)
 	}
 	switch TPMAlgID(hint) {
-	case TPMAlgAES:
+	case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia:
 		var contents boxed[TPMKeyBits]
 		if u.contents != nil {
 			contents = *u.contents.(*boxed[TPMKeyBits])
@@ -1413,6 +1413,18 @@ func NewTPMUSymKeyBits[C SymKeyBitsContents](selector TPMAlgID, contents C) TPMU
 	}
 }
 
+// Sym returns the 'sym' member of the union.
+func (u *TPMUSymKeyBits) Sym() (*TPMKeyBits, error) {
+
+	switch u.selector {
+	case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia:
+		value := u.contents.(*boxed[TPMKeyBits]).unbox()
+		return value, nil
+	default:
+		return nil, fmt.Errorf("did not contain sym (selector value was %v)", u.selector)
+	}
+}
+
 // AES returns the 'aes' member of the union.
 func (u *TPMUSymKeyBits) AES() (*TPMKeyBits, error) {
 	if u.selector == TPMAlgAES {
@@ -1422,6 +1434,37 @@ func (u *TPMUSymKeyBits) AES() (*TPMKeyBits, error) {
 	return nil, fmt.Errorf("did not contain aes (selector value was %v)", u.selector)
 }
 
+// TDES returns the 'tdes' member of the union.
+//
+// Deprecated: TDES exists for historical compatibility
+// and is not recommended anymore.
+// https://csrc.nist.gov/news/2023/nist-to-withdraw-sp-800-67-rev-2
+func (u *TPMUSymKeyBits) TDES() (*TPMKeyBits, error) {
+	if u.selector == TPMAlgTDES {
+		value := u.contents.(*boxed[TPMKeyBits]).unbox()
+		return value, nil
+	}
+	return nil, fmt.Errorf("did not contain tdes (selector value was %v)", u.selector)
+}
+
+// SM4 returns the 'sm4' member of the union.
+func (u *TPMUSymKeyBits) SM4() (*TPMKeyBits, error) {
+	if u.selector == TPMAlgSM4 {
+		value := u.contents.(*boxed[TPMKeyBits]).unbox()
+		return value, nil
+	}
+	return nil, fmt.Errorf("did not contain sm4 (selector value was %v)", u.selector)
+}
+
+// Camellia returns the 'camellia' member of the union.
+func (u *TPMUSymKeyBits) Camellia() (*TPMKeyBits, error) {
+	if u.selector == TPMAlgCamellia {
+		value := u.contents.(*boxed[TPMKeyBits]).unbox()
+		return value, nil
+	}
+	return nil, fmt.Errorf("did not contain camellia (selector value was %v)", u.selector)
+}
+
 // XOR returns the 'xor' member of the union.
 func (u *TPMUSymKeyBits) XOR() (*TPMAlgID, error) {
 	if u.selector == TPMAlgXOR {
@@ -1446,7 +1489,7 @@ type SymModeContents interface {
 // create implements the unmarshallableWithHint interface.
 func (u *TPMUSymMode) create(hint int64) (reflect.Value, error) {
 	switch TPMAlgID(hint) {
-	case TPMAlgAES:
+	case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia:
 		var contents boxed[TPMAlgID]
 		u.contents = &contents
 		u.selector = TPMAlgID(hint)
@@ -1466,7 +1509,7 @@ func (u TPMUSymMode) get(hint int64) (reflect.Value, error) {
 		return reflect.ValueOf(nil), fmt.Errorf("incorrect union tag %v, is %v", hint, u.selector)
 	}
 	switch TPMAlgID(hint) {
-	case TPMAlgAES:
+	case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia:
 		var contents boxed[TPMAlgID]
 		if u.contents != nil {
 			contents = *u.contents.(*boxed[TPMAlgID])
@@ -1491,6 +1534,17 @@ func NewTPMUSymMode[C SymModeContents](selector TPMAlgID, contents C) TPMUSymMod
 	}
 }
 
+// Sym returns the 'sym' member of the union.
+func (u *TPMUSymMode) Sym() (*TPMIAlgSymMode, error) {
+	switch u.selector {
+	case TPMAlgTDES, TPMAlgAES, TPMAlgSM4, TPMAlgCamellia:
+		value := u.contents.(*boxed[TPMIAlgSymMode]).unbox()
+		return value, nil
+	default:
+		return nil, fmt.Errorf("did not contain sym (selector value was %v)", u.selector)
+	}
+}
+
 // AES returns the 'aes' member of the union.
 func (u *TPMUSymMode) AES() (*TPMIAlgSymMode, error) {
 	if u.selector == TPMAlgAES {
@@ -1500,6 +1554,37 @@ func (u *TPMUSymMode) AES() (*TPMIAlgSymMode, error) {
 	return nil, fmt.Errorf("did not contain aes (selector value was %v)", u.selector)
 }
 
+// TDES returns the 'tdes' member of the union.
+//
+// Deprecated: TDES exists for historical compatibility
+// and is not recommended anymore.
+// https://csrc.nist.gov/news/2023/nist-to-withdraw-sp-800-67-rev-2
+func (u *TPMUSymMode) TDES() (*TPMIAlgSymMode, error) {
+	if u.selector == TPMAlgTDES {
+		value := u.contents.(*boxed[TPMIAlgSymMode]).unbox()
+		return value, nil
+	}
+	return nil, fmt.Errorf("did not contain tdes (selector value was %v)", u.selector)
+}
+
+// SM4 returns the 'sm4' member of the union.
+func (u *TPMUSymMode) SM4() (*TPMIAlgSymMode, error) {
+	if u.selector == TPMAlgSM4 {
+		value := u.contents.(*boxed[TPMIAlgSymMode]).unbox()
+		return value, nil
+	}
+	return nil, fmt.Errorf("did not contain sm4 (selector value was %v)", u.selector)
+}
+
+// Camellia returns the 'camellia' member of the union.
+func (u *TPMUSymMode) Camellia() (*TPMIAlgSymMode, error) {
+	if u.selector == TPMAlgCamellia {
+		value := u.contents.(*boxed[TPMIAlgSymMode]).unbox()
+		return value, nil
+	}
+	return nil, fmt.Errorf("did not contain camellia (selector value was %v)", u.selector)
+}
+
 // TPMUSymDetails represents a TPMU_SYM_DETAILS.
 // See definition in Part 2: Structures, section 11.1.5.
 type TPMUSymDetails struct {
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/crypto_test.go b/tpm2/crypto_test.go
index a72bf11..ac33bbd 100644
--- a/tpm2/crypto_test.go
+++ b/tpm2/crypto_test.go
@@ -15,6 +15,25 @@ import (
 	"github.com/google/go-tpm/tpm2/transport/simulator"
 )
 
+// Subtle: Some RSA crypto libraries compute D (the private exponent)
+// based on phi(N) (Euler's totient function). Phi(N) is the order of the
+// multiplicative group of integers modulo N.
+// Other libraries compute lambda(N), the Carmichael function: the
+// minimum order among all the elements of the multiplicative group of
+// integers modulo N. Lambda(N) divides phi(N).
+// This test isn't intended to ensure that the underlying crypto
+// implementation chooses one or the other, or that it agrees with the
+// implementation of the tpm2.Priv() function. So, we compare RSA private keys
+// in tests by checking that the public exponent, modulus, and primes are the
+// same. We also have tests to ensure that signatures produced by the TPM and
+// by the software implementation validate (which has the effect of confirming
+// that the private exponents are correct).
+func areRSAPrivateKeysEqual(a, b *rsa.PrivateKey) bool {
+	return a.E == b.E &&
+		a.N.Cmp(b.N) == 0 &&
+		reflect.DeepEqual(a.Primes, b.Primes)
+}
+
 func TestPriv(t *testing.T) {
 
 	t.Parallel()
@@ -186,7 +205,7 @@ func TestPriv(t *testing.T) {
 			if key != nil {
 				switch key := key.(type) {
 				case *rsa.PrivateKey:
-					if !reflect.DeepEqual(rsaKey, key) {
+					if !areRSAPrivateKeysEqual(rsaKey, key) {
 						t.Errorf("Not equal: \n"+
 							"expected: %v\n"+
 							"actual  : %v",
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)
+	}
+}