Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VerifySignature returning TPM_RC_SIGNATURE #10

Open
hallyn opened this issue Oct 15, 2022 · 9 comments
Open

VerifySignature returning TPM_RC_SIGNATURE #10

hallyn opened this issue Oct 15, 2022 · 9 comments

Comments

@hallyn
Copy link

hallyn commented Oct 15, 2022

I'm doing basically the following code:

func (t *tpm2Trust) readSignature(nv NVIndex) ([]byte, error) {
	filename := "tpm_passwd.policy.signed"
        sPath := filepath.Join(dataDir, filename)
        bytes, err := os.ReadFile(sPath)
        if err != nil {
                return []byte{}, errors.Wrapf(err, "Error reading signed policy file %s", sPath)
        }
        return bytes, nil
}

...

        session, err := t.tpm.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256)
        if err != nil {
                return "", errors.Wrapf(err, "Failed starting auth session")
        }

        err = t.tpm.PolicyPCR(session, nil,
                tpm2.PCRSelectionList{{Hash: tpm2.HashAlgorithmSHA256, Select: []int{7}}})
        if err != nil {
                return "", errors.Wrapf(err, "PolicyPCR failed")
        }

        vb := []byte(PolicyVersion.String())
        nvV := tpm2.Operand(vb)

        pvIndex, err := t.tpm.CreateResourceContextFromTPM(tpm2.Handle(TPM2IndexEAVersion))
        if err != nil {
                return "", errors.Wrapf(err, "Error creating TPM resource for NVIndex")
        }
        err = t.tpm.PolicyNV(pvIndex, pvIndex, session, nvV, uint16(0), tpm2.OpEq, nil)
        if err != nil {
                return "", errors.Wrapf(err, "PolicyNV failed")
        }

        digest, err := t.tpm.PolicyGetDigest(session)
        if err != nil {
                return "", errors.Wrapf(err, "Failed getting policy digest")
        }

        s, err := t.readSignature(TPM2IndexAtxSecret)
        if err != nil {
                return "", errors.Wrapf(err, "Failed reading policy signature")
        }
        signature := tpm2.Signature{
                SigAlg:    tpm2.SigSchemeAlgRSASSA,
                Signature: &tpm2.SignatureU{RSASSA: &tpm2.SignatureRSASSA{Hash: tpm2.HashAlgorithmSHA256, Sig: s}}}
        ticket, err := t.tpm.VerifySignature(key, digest, &signature)
        if err != nil {
                return "", errors.Wrapf(err, "Failed verifying policy signature")
        }

and getting

reading luks keys failed with Failed verifying policy signature: TPM returned an error for parameter 2 whilst executing command TPM_CC_VerifySignature: TPM_RC_SIGNATURE (the signature is not valid)

I've written 'digest' to disk, and verified it's identical to the policy
I had pre-generated, as well as that the signature read by t.readSignature(),
using the public key loaded into key, verifies correctly with:

openssl dgst -sha256 -verify pubkey.pem -signature policy.signed policy

Given that the signature file being read from disk is the binary blob,
should I be parsing it somehow rather than simply reading it into a []byte
and casting that to tpm2.Digest ?

@hallyn
Copy link
Author

hallyn commented Oct 18, 2022

FWIW here is doing what I think should be the same steps using the tpm2-tools scripts by hand:

root@trust:/home/sergeh/sess2# tpm2_loadexternal -C o -G rsa -u /pcr7data/policy-2/pubkeys/luks-snakeoil.pem -c pubkey_ctx_a -n pubkey_name_a
name: 000bd2b51b26486303c16c297000a6256761e36ea9a579b8222b6df75a2ac5083764
root@trust:/home/sergeh/sess2# tpm2_startauthsession -S session --policy-session
root@trust:/home/sergeh/sess2# tpm2_policypcr -S session -l sha256:7
445871de5bab7161fd03479cd5fc36caac41d244204643fa16196d8e5a43e983
root@trust:/home/sergeh/sess2# echo -n 0001 | tpm2_policynv -S session -i- 0x1500020 eq -L tpm_unseal_a
01095ae9c11f3cd988e5dc284a74889264d31b3c5178f042207310ae685d4b26
root@trust:/home/sergeh/sess2# tpm2_verifysignature -c pubkey_ctx_a -f rsassa -g sha256 -m tpm_unseal_a -s /pcr7data/policy-2/e4/a3d2faaa3e925e33b60f660508f9e6c83a645a63a62037995dfeda417dca71/tpm_luks.policy.signed -t ticket
root@trust:/home/sergeh/sess2# tpm2_policyauthorize -S session -i tpm_unseal_a -n pubkey_name_a -t ticket 
56e6476b16d9833592ff236c6e35ae7b7991535dbc83cee6b30d404e246c29a6

@chrisccoulson
Copy link
Collaborator

chrisccoulson commented Oct 18, 2022

The main issue is that the digest that is signed for TPM2_PolicyAuthorize contains both the approved policy digest and the policy reference, so VerifySignature fails because it is verifying the wrong digest.

You can use https://pkg.go.dev/github.com/canonical/go-tpm2@v0.1.1-0.20220823192114-7a7993f0fa1f/util#ComputePolicyAuthorizeDigest to compute the digest that VerifySignature expects, which takes the approved policy digest (in this case, the current session digest read back with TPM2_PolicyGetDigest) and the policy reference used when creating the original policy (the one passed to https://pkg.go.dev/github.com/canonical/go-tpm2@v0.1.1-0.20220823192114-7a7993f0fa1f/util#TrialAuthPolicy.PolicyAuthorize if you're using that).

Also, you can use https://pkg.go.dev/github.com/canonical/go-tpm2@v0.1.1-0.20220823192114-7a7993f0fa1f/mu to serialize and unserialize any TPM type (even types defined outside of this package) - so rather than reading in some bytes and then manually constructing the tpm2.Signature, you can use https://pkg.go.dev/github.com/canonical/go-tpm2@v0.1.1-0.20220823192114-7a7993f0fa1f/mu#MarshalToWriter to save the entire structure, and then https://pkg.go.dev/github.com/canonical/go-tpm2@v0.1.1-0.20220823192114-7a7993f0fa1f/mu#UnmarshalFromReader to read it back again.

@hallyn
Copy link
Author

hallyn commented Oct 18, 2022

Sorry, I obviously have a wrong mental model on how some of this is implemented...

What is "the policy reference"? Is that actually a reference to the policy calculated thus far on the tpm itself?

Since this is not a trial policy, I need the TPM to perform the policypcr and policynv actions "for real", naturally. Then I need to pass the policy signature verification pubkey, and the policy signature, to the TPM, so it can verify this is a legit policy. Are you saying I should use util/ComputePolicyAuthorizeDigest in place of tpm.PolicyGetDigest and pass that result to tpm.VerifySignature? If so, what should the policyRef Nonce be?

@hallyn
Copy link
Author

hallyn commented Oct 18, 2022

I tried simply doing

+       digest, err = tutil.ComputePolicyAuthorizeDigest(tpm2.HashAlgorithmSHA256,
+                       digest, session.NonceTPM())

After calculating digest, err := t.tpm.PolicyGetDigest(session), then passing this new digest on to VerifySignature, but that must not be what you meant...

@hallyn
Copy link
Author

hallyn commented Oct 18, 2022

Oh - I think you might be saying that I should change the signature that I calculated in the first place? That would be unfortunate, but I can try that...

Currently we do these steps: https://github.com/puzzleos/tpm_eapol_scripts/blob/master/read/tpm-read-secret.sh and this works, so the concatting of the 'policyref' is not strictly required by the TPM, at least.

@hallyn
Copy link
Author

hallyn commented Oct 18, 2022

So just to be sure this is clear, at https://github.com/project-machine/trust/blob/master/lib/tpm2.go#L542 I have written out the digest to a file, and verified it is identical to the policy file which I signed, which signature is then (assigned to 's' and) passed into tpm2.VerifySignature.

@chrisccoulson
Copy link
Collaborator

The policy reference can be used to reduce the scope of an authorized policy in the scenario where the person signing them might sign digests that have different uses. It's a static value that is chosen when the authorization policy associated with an object is created, and can be empty. Eg:

trial := util.ComputeAuthPolicy(tpm2.HashAlgorithmSHA256)
trial.PolicyAuthorize([]byte("PCR-POLICY"), key.Name())

policy := trial.GetDigest()

This creates an authorization policy that allows an entity with the private key to authorize policies with the policy reference "PCR-POLICY". The value of the policy reference is incorporated into the computed policy digest.

tpm2_policyauthorize calls this --qualification.

The signed policy is a signature over a digest that includes both the approved policy and the policy reference (this digest is computed by H(approvedPolicy|policyRef)) - it's not a signature over just the approved policy digest. To create a signed policy from an approved policy digest, you can do:

digest, signature, err := util.PolicyAuthorize(privKey, scheme, approvedPolicy, []byte("PCR-POLICY"))

The returned digest is the actual digest that was signed, and the one that should be passed to VerifySignature. You can either save this digest, or construct it again later on before calling VerifySignature:

digest, err := util.ComputePolicyAuthorizeDigest(tpm2.HashAlgorithmSHA256, approvedPolicy, []byte("PCR-POLICY))
ticket, err := tpm.VerifySignature(key, digest, signature)

You'll need to do this even for an empty policy reference because this is what TPM2_PolicyAuthorize does when verifying that the ticket matches the supplied parameters. In the case where the reference is empty, the digest is just becomes H(approvedPolicy).

The reason this works with https://github.com/puzzleos/tpm_eapol_scripts/blob/master/read/tpm-read-secret.sh is because tpm2_verifysignature accepts a message rather than a digest, so the supplied policy digest gets hashed internally before the signature verification. This ends up working ok because the policy reference in this case is empty (so it's verifying the digest computed by H(approvedPolicy)). The VerifySignature API in go-tpm2 is different to this because it accepts a digest, not a message. The digest has to be computed by the caller, which I think is what might be catching you out.

@hallyn
Copy link
Author

hallyn commented Nov 17, 2022

Thanks, and sorry for taking your time.

Just to be clear, passing []byte("") for policyref should be the same as not having a policy ref at all? I do believe I tried that, but it sounds like I may be getting tripped up needing to do one more digest. I think the most promising approach for me will be to start with a minimal standalone test program until I get that right.

@chrisccoulson
Copy link
Collaborator

Sorry, I missed this reply. You don't have to pass []byte("") for an empty policy ref - you can just pass nil and it will work fine as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants