Repository: google/go-tpm
Pull request #365 (2024-08-05 13:50:51)

add TPM2_PolicyDuplicationSelect
Adds `TPM2_PolicyDuplicationSelect` as described in

[pg 247 23.15 TPM2_PolicyDuplicationSelect](https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf)

Specifically, this allows for direct control [where the duplication can occur](https://github.com/tpm2-software/tpm2-tools/blob/master/man/tpm2_duplicate.1.md#example-4-exporting-an-hmac-key-for-a-remote-tpm-and-restrict-it-from-further-exports)

the test cases included here are basic.  i can add an end-to-end gist where a key with this policy is created and imported on a second TPM (todo is  the -ve test where its on a 3rd unauthorized TPM )

@chrisfenner

salrashid123: @chrisfenner thanks; can someone trigger the tests?  (they do pass but it seems there was a timeout/resource issue with the test vm provisioning)
chrisfenner: Yeah I re-triggered them a while ago... maybe they need to be re-rerun. I'll override this check if it's failing and preventing checkin today.
chrisfenner: cirrus CI is having a bad day, will force merge this

Patch:
diff --git a/tpm2/tpm2.go b/tpm2/tpm2.go
index 5f14f57..97f6ee6 100644
--- a/tpm2/tpm2.go
+++ b/tpm2/tpm2.go
@@ -1272,6 +1272,40 @@ func (cmd PolicyAuthValue) Update(policy *PolicyCalculator) error {
 // PolicyAuthValueResponse is the response from TPM2_PolicyAuthValue.
 type PolicyAuthValueResponse struct{}
 
+// PolicyDuplicationSelect is the input to TPM2_PolicyDuplicationSelect.
+// See definition in Part 3, Commands, section 23.15.
+type PolicyDuplicationSelect struct {
+	// handle for the policy session being extended
+	PolicySession handle `gotpm:"handle"`
+	ObjectName    TPM2BName
+	NewParentName TPM2BName
+	IncludeObject TPMIYesNo
+}
+
+// Command implements the Command interface.
+func (PolicyDuplicationSelect) Command() TPMCC { return TPMCCPolicyDuplicationSelect }
+
+// Execute executes the command and returns the response.
+func (cmd PolicyDuplicationSelect) Execute(t transport.TPM, s ...Session) (*PolicyDuplicationSelectResponse, error) {
+	var rsp PolicyDuplicationSelectResponse
+	err := execute[PolicyDuplicationSelectResponse](t, cmd, &rsp, s...)
+	if err != nil {
+		return nil, err
+	}
+	return &rsp, nil
+}
+
+// Update implements the PolicyDuplicationSelect interface.
+func (cmd PolicyDuplicationSelect) Update(policy *PolicyCalculator) error {
+	if cmd.IncludeObject {
+		return policy.Update(TPMCCPolicyDuplicationSelect, cmd.ObjectName.Buffer, cmd.NewParentName.Buffer, cmd.IncludeObject)
+	}
+	return policy.Update(TPMCCPolicyDuplicationSelect, cmd.NewParentName.Buffer, cmd.IncludeObject)
+}
+
+// PolicyDuplicationSelectResponse is the response from TPM2_PolicyDuplicationSelect.
+type PolicyDuplicationSelectResponse struct{}
+
 // PolicyNV is the input to TPM2_PolicyNV.
 // See definition in Part 3, Commands, section 23.9.
 type PolicyNV struct {


Test patch:
diff --git a/tpm2/test/policy_test.go b/tpm2/test/policy_test.go
index 2a0d11a..b3fa3a4 100644
--- a/tpm2/test/policy_test.go
+++ b/tpm2/test/policy_test.go
@@ -187,6 +187,31 @@ func primaryRSASRK(t *testing.T, thetpm transport.TPM) (NamedHandle, func()) {
 	}, cleanup
 }
 
+func primaryRSAEK(t *testing.T, thetpm transport.TPM) (NamedHandle, func()) {
+	t.Helper()
+	createPrimary := CreatePrimary{
+		PrimaryHandle: TPMRHEndorsement,
+		InPublic:      New2B(RSAEKTemplate),
+	}
+	rsp, err := createPrimary.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("could not create primary key: %v", err)
+	}
+	cleanup := func() {
+		t.Helper()
+		flush := FlushContext{
+			FlushHandle: rsp.ObjectHandle,
+		}
+		if _, err := flush.Execute(thetpm); err != nil {
+			t.Errorf("could not flush primary key: %v", err)
+		}
+	}
+	return NamedHandle{
+		Handle: rsp.ObjectHandle,
+		Name:   rsp.Name,
+	}, cleanup
+}
+
 func TestPolicySignedUpdate(t *testing.T) {
 	thetpm, err := simulator.OpenSimulator()
 	if err != nil {
@@ -981,3 +1006,122 @@ func TestPolicyAuthValue(t *testing.T) {
 	}
 
 }
+
+func TestPolicyDuplicationSelectUpdate(t *testing.T) {
+	thetpm, err := simulator.OpenSimulator()
+	if err != nil {
+		t.Fatalf("could not connect to TPM simulator: %v", err)
+	}
+	defer thetpm.Close()
+
+	ek, ekcleanup := primaryRSAEK(t, thetpm)
+	defer ekcleanup()
+
+	pk, pkcleanup := primaryRSASRK(t, thetpm)
+	defer pkcleanup()
+
+	k, err := CreateLoaded{
+		ParentHandle: AuthHandle{
+			Handle: pk.Handle,
+			Name:   pk.Name,
+			Auth:   PasswordAuth(nil),
+		},
+		InPublic: New2BTemplate(&TPMTPublic{
+			Type:    TPMAlgRSA,
+			NameAlg: TPMAlgSHA256,
+			ObjectAttributes: TPMAObject{
+				FixedTPM:            false,
+				FixedParent:         false,
+				SensitiveDataOrigin: true,
+				UserWithAuth:        true,
+				SignEncrypt:         true,
+			},
+			Parameters: NewTPMUPublicParms(
+				TPMAlgRSA,
+				&TPMSRSAParms{
+					Scheme: TPMTRSAScheme{
+						Scheme: TPMAlgRSASSA,
+						Details: NewTPMUAsymScheme(
+							TPMAlgRSASSA,
+							&TPMSSigSchemeRSASSA{
+								HashAlg: TPMAlgSHA256,
+							},
+						),
+					},
+					KeyBits: 2048,
+				},
+			),
+		}),
+	}.Execute(thetpm)
+	if err != nil {
+		t.Fatalf("error creating key %v", err)
+	}
+	defer func() {
+		t.Helper()
+		_, err := FlushContext{
+			FlushHandle: k.ObjectHandle,
+		}.Execute(thetpm)
+		if err != nil {
+			t.Errorf("error cleaning up key: %v", err)
+		}
+	}()
+
+	tests := []struct {
+		name          string
+		objectName    TPM2BName
+		includeObject bool
+	}{
+		{"IncludeObjectFalse", TPM2BName{}, false},
+		{"IncludeObjectTrue", k.Name, true},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			// create a trial policy with PolicyDuplicationSelect
+			sess, cleanup1, err := PolicySession(thetpm, TPMAlgSHA256, 16, Trial())
+			if err != nil {
+				t.Fatalf("error setting up trial session: %v", err)
+			}
+			defer func() {
+				t.Helper()
+				if err := cleanup1(); err != nil {
+					t.Errorf("error cleaning up trial session: %v", err)
+				}
+			}()
+
+			pds := PolicyDuplicationSelect{
+				PolicySession: sess.Handle(),
+				NewParentName: ek.Name,
+				ObjectName:    tt.objectName,
+				IncludeObject: tt.includeObject,
+			}
+			_, err = pds.Execute(thetpm)
+			if err != nil {
+				t.Fatalf("error executing PolicyDuplicationSelect: %v", err)
+			}
+
+			pdr, err := PolicyGetDigest{
+				PolicySession: sess.Handle(),
+			}.Execute(thetpm)
+			if err != nil {
+				t.Fatalf("error executing PolicyGetDigest: %v", pdr)
+			}
+
+			// Use the policy helper to calculate the same policy
+			pol, err := NewPolicyCalculator(TPMAlgSHA256)
+			if err != nil {
+				t.Fatalf("creating policy calculator: %v", err)
+			}
+			err = pds.Update(pol)
+			if err != nil {
+				t.Fatalf("error updating policy calculator: %v", err)
+			}
+			got := pol.Hash()
+
+			if !bytes.Equal(got.Digest, pdr.PolicyDigest.Buffer) {
+				t.Errorf("PolicyAuthValue.Hash() = %x,\nwant %x", got.Digest, pdr.PolicyDigest.Buffer)
+			}
+		})
+	}
+}