go-attestation/attest/activation.go

277 lines
8.7 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
)
// ActivationParameters encapsulates the inputs for activating an AK.
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 whose
// private key is permanently bound to the TPM.
//
// Activation will verify that the provided EK is held on the same
// TPM as the AK. 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 AK with.
EK crypto.PublicKey
// AK, the Attestation 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.AK.
AK 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
}
// checkAKParameters examines properties of an AK and a creation
// attestation, to determine if it is suitable for use as an attestation key.
func (p *ActivationParameters) checkAKParameters() error {
switch p.TPMVersion {
case TPMVersion12:
return p.checkTPM12AKParameters()
case TPMVersion20:
return p.checkTPM20AKParameters()
default:
return fmt.Errorf("TPM version %d not supported", p.TPMVersion)
}
}
func (p *ActivationParameters) checkTPM12AKParameters() error {
// TODO(jsonp): Implement helper to parse public blobs, ie:
// func ParsePublic(publicBlob []byte) (crypto.Public, error)
pub, err := tpm1.UnmarshalPubRSAPublicKey(p.AK.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) checkTPM20AKParameters() error {
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature is too short to be valid: only %d bytes", len(p.AK.CreateSignature))
}
pub, err := tpm2.DecodePublic(p.AK.Public)
if err != nil {
return fmt.Errorf("DecodePublic() failed: %v", err)
}
_, err = tpm2.DecodeCreationData(p.AK.CreateData)
if err != nil {
return fmt.Errorf("DecodeCreationData() failed: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.AK.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 AKs.
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.
nameHash, err := pub.NameAlg.Hash()
if err != nil {
return fmt.Errorf("HashConstructor() failed: %v", err)
}
h := nameHash.New()
h.Write(p.AK.CreateData)
if !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) {
return errors.New("attestation refers to different public key")
}
// Make sure the AK has sane key parameters (Attestation can be faked if an AK
// 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("AK 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()}
signHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
return err
}
hsh := signHash.New()
hsh.Write(p.AK.CreateAttestation)
verifyHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
return err
}
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AK.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AK.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 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 *ActivationParameters) Generate() (secret []byte, ec *EncryptedCredential, err error) {
if err := p.checkAKParameters(); 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.AK.CreateAttestation)
if err != nil {
return nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.AttestedCreationInfo == nil {
return nil, fmt.Errorf("attestation was not for a creation event")
}
if att.AttestedCreationInfo.Name.Digest == nil {
return nil, fmt.Errorf("attestation creation info name has no digest")
}
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.AK.UseTCSDActivationFormat {
cred, encSecret, err = verification.GenerateChallengeEx(pk, p.AK.Public, secret)
} else {
cred, encSecret, err = generateChallenge12(rand, pk, p.AK.Public, secret)
}
if err != nil {
return nil, fmt.Errorf("challenge generation failed: %v", err)
}
return &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}