package internal import ( "bytes" "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/subtle" "fmt" pb "github.com/google/go-tpm-tools/proto/tpm" "github.com/google/go-tpm/legacy/tpm2" ) // SignatureHashAlgs are the hash algorithms we support for Quote signatures, in // their preferred order of use. var SignatureHashAlgs = []tpm2.Algorithm{tpm2.AlgSHA512, tpm2.AlgSHA384, tpm2.AlgSHA256} // VerifyQuote performs the following checks to validate a Quote: // - the provided signature is generated by the trusted AK public key // - the signature signs the provided quote data // - the quote data starts with TPM_GENERATED_VALUE // - the quote data is a valid TPMS_QUOTE_INFO // - the quote data was taken over the provided PCRs // - the provided PCR values match the quote data internal digest // - the provided extraData matches that in the quote data // - the signature hash algorithm must be in HashAlgs // // Note that the caller must have already established trust in the provided // public key before validating the Quote. // // VerifyQuote supports ECDSA and RSASSA signature verification. func VerifyQuote(q *pb.Quote, trustedPub crypto.PublicKey, extraData []byte) error { sig, err := tpm2.DecodeSignature(bytes.NewBuffer(q.GetRawSig())) if err != nil { return fmt.Errorf("signature decoding failed: %v", err) } hash, err := verifyHashAlg(sig) if err != nil { return err } switch pub := trustedPub.(type) { case *ecdsa.PublicKey: if err = verifyECDSAQuoteSignature(pub, hash, q.GetQuote(), sig); err != nil { return err } case *rsa.PublicKey: if err = verifyRSASSAQuoteSignature(pub, hash, q.GetQuote(), sig); err != nil { return err } default: return fmt.Errorf("only RSA and ECC public keys are currently supported, received type: %T", pub) } // Decode and check for magic TPMS_GENERATED_VALUE. attestationData, err := tpm2.DecodeAttestationData(q.GetQuote()) if err != nil { return fmt.Errorf("decoding attestation data failed: %v", err) } if attestationData.Type != tpm2.TagAttestQuote { return fmt.Errorf("expected quote tag, got: %v", attestationData.Type) } attestedQuoteInfo := attestationData.AttestedQuoteInfo if attestedQuoteInfo == nil { return fmt.Errorf("attestation data does not contain quote info") } if subtle.ConstantTimeCompare(attestationData.ExtraData, extraData) == 0 { return fmt.Errorf("quote extraData %v did not match expected extraData %v", attestationData.ExtraData, extraData) } return validatePCRDigest(attestedQuoteInfo, q.GetPcrs(), hash) } // Get the cryptographic hash used for the signature and make sure we support it func verifyHashAlg(sig *tpm2.Signature) (crypto.Hash, error) { var hashAlg tpm2.Algorithm if sig.ECC != nil { hashAlg = sig.ECC.HashAlg } else if sig.RSA != nil { hashAlg = sig.RSA.HashAlg } else { return 0, fmt.Errorf("signature is missing hash algorithm") } // Convert from TPM2 hash algorithm to a Golang hash algorithm hash, err := hashAlg.Hash() if err != nil { return 0, err } for _, alg := range SignatureHashAlgs { if hashAlg == alg { return hash, nil } } return 0, fmt.Errorf("unsupported signature hash algorithm: %v", hash) } func verifyECDSAQuoteSignature(ecdsaPub *ecdsa.PublicKey, hash crypto.Hash, quoted []byte, sig *tpm2.Signature) error { if sig.Alg != tpm2.AlgECDSA { return fmt.Errorf("signature scheme 0x%x is not supported, only ECDSA is supported", sig.Alg) } hashConstructor := hash.New() hashConstructor.Write(quoted) if !ecdsa.Verify(ecdsaPub, hashConstructor.Sum(nil), sig.ECC.R, sig.ECC.S) { return fmt.Errorf("ECC signature verification failed") } return nil } func verifyRSASSAQuoteSignature(rsaPub *rsa.PublicKey, hash crypto.Hash, quoted []byte, sig *tpm2.Signature) error { if sig.Alg != tpm2.AlgRSASSA { return fmt.Errorf("signature scheme 0x%x is not supported, only RSASSA (PKCS#1 v1.5) is supported", sig.Alg) } hashConstructor := hash.New() hashConstructor.Write(quoted) if err := rsa.VerifyPKCS1v15(rsaPub, hash, hashConstructor.Sum(nil), sig.RSA.Signature); err != nil { return fmt.Errorf("RSASSA signature verification failed: %v", err) } return nil } func validatePCRDigest(quoteInfo *tpm2.QuoteInfo, pcrs *pb.PCRs, hash crypto.Hash) error { if !SamePCRSelection(pcrs, quoteInfo.PCRSelection) { return fmt.Errorf("given PCRs and Quote do not have the same PCR selection") } pcrDigest := PCRDigest(pcrs, hash) if subtle.ConstantTimeCompare(quoteInfo.PCRDigest, pcrDigest) == 0 { return fmt.Errorf("given PCRs digest not matching") } return nil }