Add support for generating TPM2.0 challenges using AttestedCertifyInfo

Fixes: issues/320.

Adds support for generating an activation challenge using
CertificationParameters.
Achieves symmetry with challenge-generation in
AttestationParameters, in order to provide a challenge to a
TPM to activate a TPM-certified key.

`attest.Activation` currently supports verifying and
generating a challenge given attestationData, an EK, an AK,
and a signature. In the attestationData, the CreationInfo
field is used to further validate and create the resulting
challenge.
In this change, `attest.Certification` will now support
generating a challenge given attestationData, an EK, a
TPM-certified public key, and a signature, in addition to
an AK used to verify the certification of the provided
public key we are generating an activation challenge for.
This commit is contained in:
juanvallejo 2023-05-30 18:00:02 -07:00 committed by Eric Chiang
parent 89884d0a74
commit 258084d04e
2 changed files with 202 additions and 0 deletions

View File

@ -17,12 +17,14 @@ package attest
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpm2/credactivation"
"github.com/google/go-tpm/tpmutil"
)
@ -62,6 +64,38 @@ type VerifyOpts struct {
Hash crypto.Hash
}
// ActivateOpts specifies options for the key certification's challenge generation.
type ActivateOpts struct {
// EK, the endorsement key, describes an asymmetric key whose
// private key is permanently bound to the TPM.
//
// Activation will verify that the provided EK is held on the same
// TPM as the key we're certifying. However, it is the caller's responsibility to
// ensure the EK they provide corresponds to the the device which
// they are trying to associate the certified key with.
EK crypto.PublicKey
// VerifierKeyNameDigest is the name digest of the public key we're using to
// verify the certification of the tpm-generated key being activated.
// The verifier key (usually the AK) that owns this digest should be the same
// key used in VerifyOpts.Public.
// Use tpm2.Public.Name() to produce the digest for a provided key.
VerifierKeyNameDigest *tpm2.HashValue
}
// NewActivateOpts creates options for use in generating an activation challenge for a certified key.
// The computed hash is the name digest of the public key used to verify the certification of our key.
func NewActivateOpts(verifierPubKey tpm2.Public, ek crypto.PublicKey) (*ActivateOpts, error) {
pubName, err := verifierPubKey.Name()
if err != nil {
return nil, fmt.Errorf("unable to resolve a tpm2.Public Name struct from the given public key struct: %v", err)
}
return &ActivateOpts{
EK: ek,
VerifierKeyNameDigest: pubName.Digest,
}, nil
}
// Verify verifies the TPM2-produced certification parameters checking whether:
// - the key length is secure
// - the attestation parameters matched the attested key
@ -157,6 +191,51 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error {
return nil
}
// Generate returns a credential activation challenge, which can be provided
// to the TPM to verify the AK parameters given are authentic & the AK
// is present on the same TPM as the EK.
//
// The caller is expected to verify the secret returned from the TPM as
// as result of calling ActivateCredential() matches the secret returned here.
// The caller should use subtle.ConstantTimeCompare to avoid potential
// timing attack vectors.
func (p *CertificationParameters) Generate(rnd io.Reader, verifyOpts VerifyOpts, activateOpts ActivateOpts) (secret []byte, ec *EncryptedCredential, err error) {
if err := p.Verify(verifyOpts); err != nil {
return nil, nil, err
}
if activateOpts.EK == nil {
return nil, nil, errors.New("no EK provided")
}
secret = make([]byte, activationSecretLen)
if rnd == nil {
rnd = rand.Reader
}
if _, err = io.ReadFull(rnd, secret); err != nil {
return nil, nil, fmt.Errorf("error generating activation secret: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.CreateAttestation)
if err != nil {
return nil, nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCertify {
return nil, nil, fmt.Errorf("attestation does not apply to certify data, got %x", att.Type)
}
cred, encSecret, err := credactivation.Generate(activateOpts.VerifierKeyNameDigest, activateOpts.EK, symBlockSize, secret)
if err != nil {
return nil, nil, fmt.Errorf("credactivation.Generate() failed: %v", err)
}
return secret, &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
// certify uses AK's handle and the passed signature scheme to certify the key
// with the `hnd` handle.
func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigScheme) (*CertificationParameters, error) {

View File

@ -20,6 +20,7 @@
package attest
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
@ -274,3 +275,125 @@ func testKeyCertification(t *testing.T, tpm *TPM) {
})
}
}
func TestKeyActivationTPM20(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("error creating a new AK using simulated TPM: %v", err)
}
akAttestParams := ak.AttestationParameters()
pub, err := tpm2.DecodePublic(akAttestParams.Public)
if err != nil {
t.Fatalf("unable to decode public struct from AK attestation params: %v", err)
}
if pub.Type != tpm2.AlgRSA {
t.Fatal("non-RSA verifying key")
}
eks, err := tpm.EKs()
if err != nil {
t.Fatalf("unexpected error retrieving EK from tpm: %v", err)
}
if len(eks) == 0 {
t.Fatal("expected at least one EK from the simulated TPM")
}
pk := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
hash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
t.Fatalf("unable to compute hash signature from verifying key's RSA parameters: %v", err)
}
verifyOpts := VerifyOpts{
Public: pk,
Hash: hash,
}
sk, err := tpm.NewKey(ak, nil)
if err != nil {
t.Fatalf("unable to create a new TPM-backed key to certify: %v", err)
}
skCertParams := sk.CertificationParameters()
activateOpts, err := NewActivateOpts(pub, eks[0].Public)
if err != nil {
t.Fatalf("unable to create new ActivateOpts: %v", err)
}
wrongPub, err := tpm2.DecodePublic(skCertParams.Public)
if err != nil {
t.Fatalf("unable to decode public struct from CertificationParameters: %v", err)
}
wrongActivateOpts, err := NewActivateOpts(wrongPub, eks[0].Public)
if err != nil {
t.Fatalf("unable to create wrong ActivateOpts: %v", err)
}
for _, test := range []struct {
name string
p *CertificationParameters
verifyOpts VerifyOpts
activateOpts ActivateOpts
generateErr error
activateErr error
}{
{
name: "OK",
p: &skCertParams,
verifyOpts: verifyOpts,
activateOpts: *activateOpts,
generateErr: nil,
activateErr: nil,
},
{
name: "invalid verify opts",
p: &skCertParams,
verifyOpts: VerifyOpts{},
activateOpts: *activateOpts,
generateErr: cmpopts.AnyError,
activateErr: nil,
},
{
name: "invalid activate opts",
p: &skCertParams,
verifyOpts: verifyOpts,
activateOpts: *wrongActivateOpts,
generateErr: nil,
activateErr: cmpopts.AnyError,
},
} {
t.Run(test.name, func(t *testing.T) {
expectedSecret, encryptedCredentials, err := test.p.Generate(rand.Reader, test.verifyOpts, test.activateOpts)
if test.generateErr != nil {
if got, want := err, test.generateErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
t.Errorf("p.Generate() err = %v, want = %v", got, want)
}
return
} else if err != nil {
t.Errorf("unexpected p.Generate() error: %v", err)
return
}
actualSecret, err := ak.ActivateCredential(tpm, *encryptedCredentials)
if test.activateErr != nil {
if got, want := err, test.activateErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
t.Errorf("p.ActivateCredential() err = %v, want = %v", got, want)
}
return
} else if err != nil {
t.Errorf("unexpected p.ActivateCredential() error: %v", err)
return
}
if !bytes.Equal(expectedSecret, actualSecret) {
t.Fatalf("Unexpected bytes decoded, expected %x, but got %x", expectedSecret, actualSecret)
}
})
}
}