go-attestation/attest/activation.go
Eric Chiang 9b6caf1273 attest: use provided randomness source when generating challenges (#80)
Currently the activation challenge lets a caller supply a source of
randomness other than crypto/rand, but it's not used in some places.
Plumb the source through the call chain.
2019-08-21 10:28:19 -07:00

281 lines
8.6 KiB
Go

package attest
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
tpm1 "github.com/google/go-tpm/tpm"
"github.com/google/go-tpm/tpm2"
// TODO(jsonp): Move activation generation code to internal package.
"github.com/google/go-tpm/tpm2/credactivation"
"github.com/google/go-tspi/verification"
)
const (
// minRSABits is the minimum accepted bit size of an RSA key.
minRSABits = 2048
// activationSecretLen is the size in bytes of the generated secret
// which is generated for credential activation.
activationSecretLen = 32
// symBlockSize is the block size used for symmetric ciphers used
// when generating the credential activation challenge.
symBlockSize = 16
// tpm20GeneratedMagic is a magic tag when can only be present on a
// TPM structure if the structure was generated wholly by the TPM.
tpm20GeneratedMagic = 0xff544347
)
func cryptoHash(h tpm2.Algorithm) (crypto.Hash, error) {
switch h {
case tpm2.AlgSHA1:
return crypto.SHA1, nil
case tpm2.AlgSHA256:
return crypto.SHA256, nil
case tpm2.AlgSHA384:
return crypto.SHA384, nil
case tpm2.AlgSHA512:
return crypto.SHA512, nil
default:
return crypto.Hash(0), fmt.Errorf("unsupported signature digest: %v", h)
}
}
// ActivationParameters encapsulates the inputs for activating an AIK.
type ActivationParameters struct {
// TPMVersion holds the version of the TPM, either 1.2 or 2.0.
TPMVersion TPMVersion
// EK, the endorsement key, describes an asymmetric key who's
// private key is permenantly bound to the TPM.
//
// Activation will verify that the provided EK is held on the same
// TPM as the AIK. However, it is the callers responsibility to
// ensure the EK they provide corresponds to the the device which
// they are trying to associate the AIK with.
EK crypto.PublicKey
// AIK, the Attestation Identity Key, describes the properties of
// an asymmetric key (managed by the TPM) which signs attestation
// structures.
// The values from this structure can be obtained by calling
// Parameters() on an attest.AIK.
AIK AttestationParameters
// Rand is a source of randomness to generate a seed and secret for the
// challenge.
//
// If nil, this defaults to crypto.Rand.
Rand io.Reader
}
// checkAIKParameters examines properties of an AIK and a creation
// attestation, to determine if it is suitable for use as an attestation key.
func (p *ActivationParameters) checkAIKParameters() error {
switch p.TPMVersion {
case TPMVersion12:
return p.checkTPM12AIKParameters()
case TPMVersion20:
return p.checkTPM20AIKParameters()
default:
return fmt.Errorf("TPM version %d not supported", p.TPMVersion)
}
}
func (p *ActivationParameters) checkTPM12AIKParameters() error {
// TODO(jsonp): Implement helper to parse public blobs, ie:
// func ParsePublic(publicBlob []byte) (crypto.Public, error)
pub, err := tpm1.UnmarshalPubRSAPublicKey(p.AIK.Public)
if err != nil {
return fmt.Errorf("unmarshalling public key: %v", err)
}
if bits := pub.Size() * 8; bits < minRSABits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, bits)
}
return nil
}
func (p *ActivationParameters) checkTPM20AIKParameters() error {
if len(p.AIK.CreateSignature) < 8 {
return fmt.Errorf("signature is too short to be valid: only %d bytes", len(p.AIK.CreateSignature))
}
pub, err := tpm2.DecodePublic(p.AIK.Public)
if err != nil {
return fmt.Errorf("DecodePublic() failed: %v", err)
}
_, err = tpm2.DecodeCreationData(p.AIK.CreateData)
if err != nil {
return fmt.Errorf("DecodeCreationData() failed: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.AIK.CreateAttestation)
if err != nil {
return fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCreation {
return fmt.Errorf("attestation does not apply to creation data, got tag %x", att.Type)
}
// TODO: Support ECC AIKs.
switch pub.Type {
case tpm2.AlgRSA:
if pub.RSAParameters.KeyBits < minRSABits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits)
}
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
// Compute & verify that the creation data matches the digest in the
// attestation structure.
nameHashConstructor, err := pub.NameAlg.HashConstructor()
if err != nil {
return fmt.Errorf("HashConstructor() failed: %v", err)
}
h := nameHashConstructor()
h.Write(p.AIK.CreateData)
if !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) {
return errors.New("attestation refers to different public key")
}
// Make sure the AIK has sane key parameters (Attestation can be faked if an AIK
// can be used for arbitrary signatures).
// We verify the following:
// - Key is TPM backed.
// - Key is TPM generated.
// - Key is a restricted key (means it cannot do arbitrary signing/decrypt ops).
// - Key cannot be duplicated.
// - Key was generated by a call to TPM_Create*.
if att.Magic != tpm20GeneratedMagic {
return errors.New("creation attestation was not produced by a TPM")
}
if (pub.Attributes & tpm2.FlagFixedTPM) == 0 {
return errors.New("AIK is exportable")
}
if ((pub.Attributes & tpm2.FlagRestricted) == 0) || ((pub.Attributes & tpm2.FlagFixedParent) == 0) || ((pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0) {
return errors.New("provided key is not limited to attestation")
}
// Verify the attested creation name matches what is computed from
// the public key.
match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub)
if err != nil {
return err
}
if !match {
return errors.New("creation attestation refers to a different key")
}
// Check the signature over the attestation data verifies correctly.
pk := rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
signHashConstructor, err := pub.RSAParameters.Sign.Hash.HashConstructor()
if err != nil {
return err
}
hsh := signHashConstructor()
hsh.Write(p.AIK.CreateAttestation)
verifyHash, err := cryptoHash(pub.RSAParameters.Sign.Hash)
if err != nil {
return err
}
if len(p.AIK.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AIK.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AIK.CreateSignature))
if err != nil {
return fmt.Errorf("DecodeSignature() failed: %v", err)
}
if err := rsa.VerifyPKCS1v15(&pk, verifyHash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
return fmt.Errorf("could not verify attestation: %v", err)
}
return nil
}
// Generate returns a credential activation challenge, which can be provided
// to the TPM to verify the AIK parameters given are authentic & the AIK
// is present on the same TPM as the EK.
func (p *ActivationParameters) Generate() (secret []byte, ec *EncryptedCredential, err error) {
if err := p.checkAIKParameters(); err != nil {
return nil, nil, err
}
if p.EK == nil {
return nil, nil, errors.New("no EK provided")
}
rnd, secret := p.Rand, 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)
}
switch p.TPMVersion {
case TPMVersion12:
ec, err = p.generateChallengeTPM12(rnd, secret)
case TPMVersion20:
ec, err = p.generateChallengeTPM20(secret)
default:
return nil, nil, fmt.Errorf("unrecognised TPM version: %v", p.TPMVersion)
}
if err != nil {
return nil, nil, err
}
return secret, ec, nil
}
func (p *ActivationParameters) generateChallengeTPM20(secret []byte) (*EncryptedCredential, error) {
att, err := tpm2.DecodeAttestationData(p.AIK.CreateAttestation)
if err != nil {
return nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
cred, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, p.EK, symBlockSize, secret)
if err != nil {
return nil, fmt.Errorf("credactivation.Generate() failed: %v", err)
}
return &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
func (p *ActivationParameters) generateChallengeTPM12(rand io.Reader, secret []byte) (*EncryptedCredential, error) {
pk, ok := p.EK.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("got EK of type %T, want an RSA key", p.EK)
}
var (
cred, encSecret []byte
err error
)
if p.AIK.UseTCSDActivationFormat {
cred, encSecret, err = verification.GenerateChallengeEx(pk, p.AIK.Public, secret)
} else {
cred, encSecret, err = generateChallenge12(rand, pk, p.AIK.Public, secret)
}
if err != nil {
return nil, fmt.Errorf("challenge generation failed: %v", err)
}
return &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}