go-attestation/verifier/quote.go

153 lines
4.2 KiB
Go

package verifier
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha1"
"fmt"
"sort"
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"
"github.com/google/go-tpm/tpmutil"
)
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)
}
}
// VerifyQuote returns information about the validity of a quote & signature.
func VerifyQuote(tpmVersion tpb.TpmVersion, public, attestationData, signature []byte, pcrs map[uint32][]byte, nonce []byte) (*pb.QuoteVerificationResults, error) {
var (
pcrDigestMatched bool
nonceMatched bool
verifyErr error
digest []byte
)
if len(signature) < 8 {
return nil, fmt.Errorf("signature is too short to be valid: only %d bytes", len(signature))
}
switch tpmVersion {
case tpb.TpmVersion_TPM_20:
var compositeHash crypto.Hash
var verifyHash crypto.Hash
pub, err := tpm2.DecodePublic(public)
if err != nil {
return nil, err
}
att, err := tpm2.DecodeAttestationData(attestationData)
if err != nil {
return nil, err
}
if att.Type != tpm2.TagAttestQuote {
return nil, fmt.Errorf("attestation is tagged %x, want TagAttestQuote", att.Type)
}
digest = att.AttestedQuoteInfo.PCRDigest
// Compute the digest of PCR values based on the provided individual PCR values.
compositeHash, err = cryptoHash(pub.RSAParameters.Sign.Hash)
if err != nil {
return nil, err
}
var compositeData []byte
for _, pcr := range att.AttestedQuoteInfo.PCRSelection.PCRs {
digest, ok := pcrs[uint32(pcr)]
if !ok {
return nil, fmt.Errorf("PCR %d missing but used to compute PCR digest", pcr)
}
compositeData = append(compositeData, digest...)
}
compositeDigest := compositeHash.New()
compositeDigest.Write(compositeData)
pcrDigestMatched = bytes.Equal(compositeDigest.Sum(nil), digest)
// 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
}
nonceMatched = bytes.Equal(att.ExtraData, nonce)
//TODO(jsonp): Decode to tpm2.Signature & use that, once PR to expose DecodeSignature() is in.
verifyErr = rsa.VerifyPKCS1v15(&p, verifyHash, hsh.Sum(nil), signature[6:])
case tpb.TpmVersion_TPM_12:
p, err := tpm1.UnmarshalPubRSAPublicKey(public)
if err != nil {
return nil, err
}
digest = attestationData
pcrNums := sortPCRs(pcrs)
compositeData := []byte{}
for _, pcr := range pcrNums {
compositeData = append(compositeData, pcrs[uint32(pcr)]...)
}
composite, err := tpmutil.Pack(&struct {
Mask tpmutil.U16Bytes
Data tpmutil.U32Bytes
}{
Mask: []byte{0xff, 0xff, 0xff},
Data: compositeData,
})
info := struct {
Version [4]byte
QUOT [4]byte
Digest [20]byte
Nonce [20]byte
}{}
if _, err = tpmutil.Unpack(attestationData, &info); err != nil {
return nil, err
}
pcrDigestMatched = sha1.Sum(composite) == info.Digest
nonceMatched = sha1.Sum(nonce) == info.Nonce
verifyErr = tpm1.VerifyQuote(p, nonce, signature, pcrNums, compositeData)
default:
return nil, fmt.Errorf("TPM version %d not supported", tpmVersion)
}
return &pb.QuoteVerificationResults{
SignatureMismatch: verifyErr != nil,
Succeeded: verifyErr == nil && pcrDigestMatched && nonceMatched,
PcrDigest: digest,
PcrDigestMismatch: !pcrDigestMatched,
NonceMismatch: !nonceMatched,
}, nil
}
func sortPCRs(pcrs map[uint32][]byte) []int {
pcrNums := []int{}
for pcr := range pcrs {
pcrNums = append(pcrNums, int(pcr))
}
sort.Slice(pcrNums, func(i int, j int) bool {
return pcrNums[i] < pcrNums[j]
})
return pcrNums
}