mirror of
https://github.com/google/go-attestation.git
synced 2024-12-24 15:16:45 +00:00
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:
parent
89884d0a74
commit
258084d04e
@ -17,12 +17,14 @@ package attest
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/google/go-tpm/tpm2"
|
"github.com/google/go-tpm/tpm2"
|
||||||
|
"github.com/google/go-tpm/tpm2/credactivation"
|
||||||
"github.com/google/go-tpm/tpmutil"
|
"github.com/google/go-tpm/tpmutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,6 +64,38 @@ type VerifyOpts struct {
|
|||||||
Hash crypto.Hash
|
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:
|
// Verify verifies the TPM2-produced certification parameters checking whether:
|
||||||
// - the key length is secure
|
// - the key length is secure
|
||||||
// - the attestation parameters matched the attested key
|
// - the attestation parameters matched the attested key
|
||||||
@ -157,6 +191,51 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error {
|
|||||||
return nil
|
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
|
// certify uses AK's handle and the passed signature scheme to certify the key
|
||||||
// with the `hnd` handle.
|
// with the `hnd` handle.
|
||||||
func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigScheme) (*CertificationParameters, error) {
|
func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, scheme tpm2.SigScheme) (*CertificationParameters, error) {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
package attest
|
package attest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user