Repository: google/go-tpm
Pull request #360 (2024-06-25 16:43:14)

add EncryptDecrypt2 support
* added encryptdecrypt support
* test case covers chained blocks as an example


Patch:
diff --git a/tpm2/structures.go b/tpm2/structures.go
index 92dfa47..dd35543 100644
--- a/tpm2/structures.go
+++ b/tpm2/structures.go
@@ -549,6 +549,10 @@ type TPM2BMaxBuffer TPM2BData
 // See definition in Part 2: Structures, section 10.4.9.
 type TPM2BMaxNVBuffer TPM2BData
 
+// TPM2BIV represents a TPM2B_IV.
+// See definition in Part 2: Structures, section 10.4.11.
+type TPM2BIV TPM2BData
+
 // TPM2BName represents a TPM2B_NAME.
 // See definition in Part 2: Structures, section 10.5.3.
 // NOTE: This structure does not contain a TPMUName, because that union
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index 1d1799c..5f14f57 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -452,6 +452,34 @@ type CreateLoadedResponse struct {
 	Name TPM2BName
 }
 
+// EncryptDecrypt2 is the input to TPM2_EncryptDecrypt2
+type EncryptDecrypt2 struct {
+	// reference to public portion of symmetric key to use for encryption
+	KeyHandle handle `gotpm:"handle,auth"`
+	Message   TPM2BMaxBuffer
+	Decrypt   TPMIYesNo
+	Mode      TPMIAlgSymMode `gotpm:"nullable"`
+	IV        TPM2BIV
+}
+
+// Command implements the Command interface.
+func (EncryptDecrypt2) Command() TPMCC { return TPMCCEncryptDecrypt2 }
+
+// Execute executes the command and returns the response.
+func (cmd EncryptDecrypt2) Execute(t transport.TPM, s ...Session) (*EncryptDecrypt2Response, error) {
+	var rsp EncryptDecrypt2Response
+	err := execute[EncryptDecrypt2Response](t, cmd, &rsp, s...)
+	if err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+type EncryptDecrypt2Response struct {
+	OutData TPM2BMaxBuffer
+	IV      TPM2BIV
+}
+
 // RSAEncrypt is the input to TPM2_RSA_Encrypt
 // See definition in Part 3, Commands, section 14.2.
 type RSAEncrypt struct {


Test patch:
diff --git a/tpm2/test/symmetric_encryption_test.go b/tpm2/test/symmetric_encryption_test.go
new file mode 100644
index 0000000..c76f158
--- /dev/null
+++ b/tpm2/test/symmetric_encryption_test.go
@@ -0,0 +1,229 @@
+package tpm2test
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/rand"
+	"io"
+	"testing"
+
+	. "github.com/google/go-tpm/tpm2"
+	"github.com/google/go-tpm/tpm2/transport"
+	"github.com/google/go-tpm/tpm2/transport/simulator"
+)
+
+const maxDigestBuffer = 1024
+
+func TestAESEncryption(t *testing.T) {
+	theTpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	t.Cleanup(func() {
+		if err := theTpm.Close(); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	primary, err := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic: New2B(TPMTPublic{
+			Type:    TPMAlgSymCipher,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				FixedTPM:            true,
+				FixedParent:         true,
+				UserWithAuth:        true,
+				SensitiveDataOrigin: true,
+				Decrypt:             true,
+				SignEncrypt:         true,
+			},
+			Parameters: NewTPMUPublicParms(
+				TPMAlgSymCipher,
+				&TPMSSymCipherParms{
+					Sym: TPMTSymDefObject{
+						Algorithm: TPMAlgAES,
+						Mode:      NewTPMUSymMode(TPMAlgAES, TPMAlgCFB),
+						KeyBits: NewTPMUSymKeyBits(
+							TPMAlgAES,
+							TPMKeyBits(128),
+						),
+					},
+				},
+			),
+		}),
+	}.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Cleanup(func() {
+		flushContextCmd := FlushContext{FlushHandle: primary.ObjectHandle}
+		if _, err := flushContextCmd.Execute(theTpm); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	message := []byte("secret")
+
+	iv := make([]byte, aes.BlockSize)
+	_, err = io.ReadFull(rand.Reader, iv)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	keyAuth := AuthHandle{
+		Handle: primary.ObjectHandle,
+		Name:   primary.Name,
+		Auth:   PasswordAuth(nil),
+	}
+
+	// test encryption
+	encryptRsp, err := EncryptDecrypt2{
+		KeyHandle: keyAuth,
+		Message: TPM2BMaxBuffer{
+			Buffer: message,
+		},
+		Mode:    TPMAlgCFB,
+		Decrypt: false,
+		IV: TPM2BIV{
+			Buffer: iv,
+		},
+	}.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// test decryption
+	decryptRsp, err := EncryptDecrypt2{
+		KeyHandle: keyAuth,
+		Message: TPM2BMaxBuffer{
+			Buffer: encryptRsp.OutData.Buffer,
+		},
+		Mode:    TPMAlgCFB,
+		Decrypt: true,
+		IV: TPM2BIV{
+			Buffer: iv,
+		},
+	}.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	if !bytes.Equal(message, decryptRsp.OutData.Buffer) {
+		t.Errorf("want %x got %x", message, decryptRsp.OutData.Buffer)
+	}
+}
+
+func TestAESEncryptionBlock(t *testing.T) {
+	theTpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	t.Cleanup(func() {
+		if err := theTpm.Close(); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	primary, err := CreatePrimary{
+		PrimaryHandle: TPMRHOwner,
+		InPublic: New2B(TPMTPublic{
+			Type:    TPMAlgSymCipher,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				FixedTPM:            true,
+				FixedParent:         true,
+				UserWithAuth:        true,
+				SensitiveDataOrigin: true,
+				Decrypt:             true,
+				SignEncrypt:         true,
+			},
+			Parameters: NewTPMUPublicParms(
+				TPMAlgSymCipher,
+				&TPMSSymCipherParms{
+					Sym: TPMTSymDefObject{
+						Algorithm: TPMAlgAES,
+						Mode:      NewTPMUSymMode(TPMAlgAES, TPMAlgCFB),
+						KeyBits: NewTPMUSymKeyBits(
+							TPMAlgAES,
+							TPMKeyBits(128),
+						),
+					},
+				},
+			),
+		}),
+	}.Execute(theTpm)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Cleanup(func() {
+		flushContextCmd := FlushContext{FlushHandle: primary.ObjectHandle}
+		if _, err := flushContextCmd.Execute(theTpm); err != nil {
+			t.Errorf("%v", err)
+		}
+	})
+
+	message := make([]byte, 2048)
+	_, err = rand.Read(message)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	iv := make([]byte, aes.BlockSize)
+	_, err = io.ReadFull(rand.Reader, iv)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	keyAuth := AuthHandle{
+		Handle: primary.ObjectHandle,
+		Name:   primary.Name,
+		Auth:   PasswordAuth(nil),
+	}
+
+	// test encryption
+
+	encrypted, err := encryptDecryptSymmetric(theTpm, keyAuth, iv, message, TPMAlgCFB, false)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	decrypted, err := encryptDecryptSymmetric(theTpm, keyAuth, iv, encrypted, TPMAlgCFB, true)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+
+	if !bytes.Equal(message, decrypted) {
+		t.Errorf("want %x got %x", message, decrypted)
+	}
+}
+
+func encryptDecryptSymmetric(rwr transport.TPM, keyAuth AuthHandle, iv, data []byte, mode TPMAlgID, decrypt bool) ([]byte, error) {
+	var out, block []byte
+
+	for rest := data; len(rest) > 0; {
+		if len(rest) > maxDigestBuffer {
+			block, rest = rest[:maxDigestBuffer], rest[maxDigestBuffer:]
+		} else {
+			block, rest = rest, nil
+		}
+		r, err := EncryptDecrypt2{
+			KeyHandle: keyAuth,
+			Message: TPM2BMaxBuffer{
+				Buffer: block,
+			},
+			Mode:    mode,
+			Decrypt: decrypt,
+			IV: TPM2BIV{
+				Buffer: iv,
+			},
+		}.Execute(rwr)
+		if err != nil {
+			return nil, err
+		}
+		block = r.OutData.Buffer
+		iv = r.IV.Buffer
+		out = append(out, block...)
+	}
+	return out, nil
+}