2019-06-06 17:43:36 +00:00
|
|
|
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.
|
2019-08-16 14:33:28 +00:00
|
|
|
p := rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
|
2019-06-06 17:43:36 +00:00
|
|
|
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
|
|
|
|
}
|