mirror of
https://github.com/google/go-attestation.git
synced 2024-12-23 23:02:30 +00:00
131 lines
4.2 KiB
Go
131 lines
4.2 KiB
Go
|
// Package verifier implements high-level logic to check information
|
||
|
// provided by TPMs in client devices.
|
||
|
package verifier
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/rsa"
|
||
|
"fmt"
|
||
|
|
||
|
tpb "github.com/google/go-attestation/proto"
|
||
|
pb "github.com/google/go-attestation/verifier/proto"
|
||
|
|
||
|
tpm1 "github.com/google/go-tpm/tpm"
|
||
|
"github.com/google/go-tpm/tpm2"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
tpmGeneratedMagic = 0xff544347
|
||
|
)
|
||
|
|
||
|
// VerifyAIK examines properties of an AIK and a creation attestation, to determine
|
||
|
// if it is suitable for use as an attestation key.
|
||
|
func VerifyAIK(tpmVersion tpb.TpmVersion, aik *tpb.AikInfo) (*pb.AikVerificationResults, error) {
|
||
|
switch tpmVersion {
|
||
|
case tpb.TpmVersion_TPM_12:
|
||
|
aik12 := aik.GetTpm12()
|
||
|
return verifyAIK12(aik12.GetPublicBlob())
|
||
|
|
||
|
case tpb.TpmVersion_TPM_20:
|
||
|
aik20 := aik.GetTpm20()
|
||
|
return verifyAIK20(aik20.GetPublicBlob(), aik20.GetCreationData(), aik20.GetAttestationData(), aik20.GetSignatureData())
|
||
|
|
||
|
default:
|
||
|
return nil, fmt.Errorf("TPM version %d not supported", tpmVersion)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func verifyAIK12(public []byte) (*pb.AikVerificationResults, error) {
|
||
|
pub, err := tpm1.UnmarshalPubRSAPublicKey(public)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var out pb.AikVerificationResults
|
||
|
out.RocaVulnerableKey = ROCAVulnerableKey(pub)
|
||
|
out.Succeeded = !out.RocaVulnerableKey
|
||
|
return &out, nil
|
||
|
}
|
||
|
|
||
|
func verifyAIK20(public, creationData, attestationData, signature []byte) (*pb.AikVerificationResults, error) {
|
||
|
if len(signature) < 8 {
|
||
|
return nil, fmt.Errorf("signature is too short to be valid: only %d bytes", len(signature))
|
||
|
}
|
||
|
|
||
|
pub, err := tpm2.DecodePublic(public)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
_, err = tpm2.DecodeCreationData(creationData)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
att, err := tpm2.DecodeAttestationData(attestationData)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if att.Type != tpm2.TagAttestCreation {
|
||
|
return nil, fmt.Errorf("attestation does apply to creation data, got tag %x", att.Type)
|
||
|
}
|
||
|
var out pb.AikVerificationResults
|
||
|
|
||
|
switch pub.Type {
|
||
|
case tpm2.AlgRSA:
|
||
|
if pub.RSAParameters.KeyBits < 2048 {
|
||
|
out.KeyTooSmall = true
|
||
|
}
|
||
|
out.RocaVulnerableKey = ROCAVulnerableKey(&rsa.PublicKey{N: pub.RSAParameters.Modulus})
|
||
|
default:
|
||
|
return nil, 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 nil, err
|
||
|
}
|
||
|
h := nameHashConstructor()
|
||
|
h.Write(creationData)
|
||
|
out.CreationAttestationMismatch = !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil))
|
||
|
|
||
|
// 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*.
|
||
|
out.KeyNotTpmBound = (att.Magic != tpmGeneratedMagic) || ((pub.Attributes & tpm2.FlagFixedTPM) == 0)
|
||
|
out.KeyUsageOverlyBroad = ((pub.Attributes & tpm2.FlagRestricted) == 0) || ((pub.Attributes & tpm2.FlagFixedParent) == 0) || ((pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0)
|
||
|
|
||
|
// Verify the attested creation name matches what is computed from
|
||
|
// the public key.
|
||
|
match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
out.NameAttestationMismatch = !match
|
||
|
|
||
|
// Check the signature over the attestation data verifies correctly.
|
||
|
p := rsa.PublicKey{E: int(pub.RSAParameters.Exponent), N: pub.RSAParameters.Modulus}
|
||
|
signHashConstructor, err := pub.RSAParameters.Sign.Hash.HashConstructor()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
hsh := signHashConstructor()
|
||
|
hsh.Write(attestationData)
|
||
|
|
||
|
verifyHash, err := cryptoHash(pub.RSAParameters.Sign.Hash)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
//TODO(jsonp): Decode to tpm2.Signature & use that, once PR to expose DecodeSignature() is in.
|
||
|
out.SignatureMismatch = rsa.VerifyPKCS1v15(&p, verifyHash, hsh.Sum(nil), signature[6:]) != nil
|
||
|
|
||
|
out.Succeeded = !(out.CreationAttestationMismatch || out.KeyTooSmall || out.KeyNotTpmBound || out.KeyUsageOverlyBroad || out.NameAttestationMismatch || out.SignatureMismatch)
|
||
|
return &out, nil
|
||
|
}
|