mirror of
https://github.com/google/go-attestation.git
synced 2024-12-19 21:17:58 +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 (
|
||||
"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) {
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user