mirror of
https://github.com/google/go-attestation.git
synced 2024-12-21 22:07:56 +00:00
339bdb245a
Found manually looking through the code. The activate credential could crash the client, the secureboot and challenge generation could crash the server.
277 lines
8.7 KiB
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 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 AK. However, it is the callers 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
|
|
}
|