mirror of
https://github.com/google/go-attestation.git
synced 2024-12-21 13:57:55 +00:00
parent
2ad969b54a
commit
4ef1479ae1
@ -253,6 +253,25 @@ func ParseAIKPublic(version TPMVersion, public []byte) (*AIKPublic, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify is used to prove authenticity of the PCR measurements. It ensures that
|
||||
// the quote was signed by the AIK, and that its contents matches the PCR and
|
||||
// nonce combination.
|
||||
//
|
||||
// The nonce is used to prevent replays of Quote and PCRs and is signed by the
|
||||
// quote. Some TPMs don't support nonces longer than 20 bytes, and if the
|
||||
// nonce is used to tie additional data to the quote, the additional data should be
|
||||
// hashed to construct the nonce.
|
||||
func (a *AIKPublic) Verify(quote Quote, pcrs []PCR, nonce []byte) error {
|
||||
switch quote.Version {
|
||||
case TPMVersion12:
|
||||
return a.validate12Quote(quote, pcrs, nonce)
|
||||
case TPMVersion20:
|
||||
return a.validate20Quote(quote, pcrs, nonce)
|
||||
default:
|
||||
return fmt.Errorf("quote used unknown tpm version 0x%x", quote.Version)
|
||||
}
|
||||
}
|
||||
|
||||
// HashAlg identifies a hashing Algorithm.
|
||||
type HashAlg uint8
|
||||
|
||||
|
@ -1,16 +1,30 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy of
|
||||
// the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
// Ensure hashes are available.
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
@ -48,49 +62,26 @@ type Event struct {
|
||||
// match their data to their digest.
|
||||
}
|
||||
|
||||
// EventLog contains the data required to parse and validate an event log.
|
||||
type EventLog struct {
|
||||
// AIKPublic is the activated public key that has been proven to be under the
|
||||
// control of the TPM.
|
||||
AIKPublic crypto.PublicKey
|
||||
// AIKHash is the hash used to generate the quote.
|
||||
AIKHash crypto.Hash
|
||||
|
||||
// Quote is a signature over the values of a PCR.
|
||||
Quote *Quote
|
||||
// PCRs are the hash values in a given number of registers.
|
||||
PCRs []PCR
|
||||
// Nonce is additional data used to validate the quote signature. It's used
|
||||
// by the server to prevent clients from re-playing quotes.
|
||||
Nonce []byte
|
||||
|
||||
// MeasurementLog contains the raw event log data, which is matched against
|
||||
// the PCRs for validation.
|
||||
MeasurementLog []byte
|
||||
}
|
||||
|
||||
// Validate verifies the signature of the quote agains the public key, that the
|
||||
// quote matches the PCRs, parses the measurement log, and replays the PCRs.
|
||||
//
|
||||
// Events for PCRs not in the quote are dropped.
|
||||
func (e *EventLog) Validate() (events []Event, err error) {
|
||||
var pcrs []PCR
|
||||
switch e.Quote.Version {
|
||||
case TPMVersion12:
|
||||
pcrs, err = e.validate12Quote()
|
||||
case TPMVersion20:
|
||||
pcrs, err = e.validate20Quote()
|
||||
default:
|
||||
return nil, fmt.Errorf("quote used unknown tpm version 0x%x", e.Quote.Version)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quote: %v", err)
|
||||
}
|
||||
rawEvents, err := parseEventLog(e.MeasurementLog)
|
||||
// ParseEventLog parses an unverified measurement log.
|
||||
func ParseEventLog(measurementLog []byte) (*EventLog, error) {
|
||||
rawEvents, err := parseEventLog(measurementLog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing measurement log: %v", err)
|
||||
}
|
||||
events, err = replayEvents(rawEvents, pcrs)
|
||||
return &EventLog{rawEvents}, nil
|
||||
}
|
||||
|
||||
// EventLog is a parsed measurement log. This contains unverified data representing
|
||||
// boot events that must be replayed against PCR values to determine authenticity.
|
||||
type EventLog struct {
|
||||
rawEvents []rawEvent
|
||||
}
|
||||
|
||||
// Verify replays the event log against a TPM's PCR values, returning events
|
||||
// from the event log, or an error if the replayed PCR values did not match the
|
||||
// provided PCR values.
|
||||
func (e *EventLog) Verify(pcrs []PCR) ([]Event, error) {
|
||||
events, err := replayEvents(e.rawEvents, pcrs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pcrs failed to replay: %v", err)
|
||||
}
|
||||
@ -114,113 +105,112 @@ type rawPCRComposite struct {
|
||||
Values tpmutil.U32Bytes
|
||||
}
|
||||
|
||||
func (e *EventLog) validate12Quote() (pcrs []PCR, err error) {
|
||||
pub, ok := e.AIKPublic.(*rsa.PublicKey)
|
||||
func (a *AIKPublic) validate12Quote(quote Quote, pcrs []PCR, nonce []byte) error {
|
||||
pub, ok := a.Public.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported public key type: %T", e.AIKPublic)
|
||||
return fmt.Errorf("unsupported public key type: %T", a.Public)
|
||||
}
|
||||
quote := sha1.Sum(e.Quote.Quote)
|
||||
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA1, quote[:], e.Quote.Signature); err != nil {
|
||||
return nil, fmt.Errorf("invalid quote signature: %v", err)
|
||||
qHash := sha1.Sum(quote.Quote)
|
||||
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA1, qHash[:], quote.Signature); err != nil {
|
||||
return fmt.Errorf("invalid quote signature: %v", err)
|
||||
}
|
||||
|
||||
var att rawAttestationData
|
||||
if _, err := tpmutil.Unpack(e.Quote.Quote, &att); err != nil {
|
||||
return nil, fmt.Errorf("parsing quote: %v", err)
|
||||
if _, err := tpmutil.Unpack(quote.Quote, &att); err != nil {
|
||||
return fmt.Errorf("parsing quote: %v", err)
|
||||
}
|
||||
// TODO(ericchiang): validate Version field.
|
||||
if att.Nonce != sha1.Sum(e.Nonce) {
|
||||
return nil, fmt.Errorf("invalid nonce")
|
||||
if att.Nonce != sha1.Sum(nonce) {
|
||||
return fmt.Errorf("invalid nonce")
|
||||
}
|
||||
if att.Fixed != fixedQuote {
|
||||
return nil, fmt.Errorf("quote wasn't a QUOT object: %x", att.Fixed)
|
||||
return fmt.Errorf("quote wasn't a QUOT object: %x", att.Fixed)
|
||||
}
|
||||
|
||||
// See 5.4.1 Creating a PCR composite hash
|
||||
sort.Slice(e.PCRs, func(i, j int) bool { return e.PCRs[i].Index < e.PCRs[j].Index })
|
||||
sort.Slice(pcrs, func(i, j int) bool { return pcrs[i].Index < pcrs[j].Index })
|
||||
var (
|
||||
pcrMask [3]byte // bitmap indicating which PCRs are active
|
||||
values []byte // appended values of all PCRs
|
||||
)
|
||||
for _, pcr := range e.PCRs {
|
||||
for _, pcr := range pcrs {
|
||||
if pcr.Index < 0 || pcr.Index >= 24 {
|
||||
return nil, fmt.Errorf("invalid PCR index: %d", pcr.Index)
|
||||
return fmt.Errorf("invalid PCR index: %d", pcr.Index)
|
||||
}
|
||||
pcrMask[pcr.Index/8] |= 1 << uint(pcr.Index%8)
|
||||
values = append(values, pcr.Digest...)
|
||||
}
|
||||
composite, err := tpmutil.Pack(rawPCRComposite{3, pcrMask, values})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling PCRss: %v", err)
|
||||
return fmt.Errorf("marshaling PCRs: %v", err)
|
||||
}
|
||||
if att.Digest != sha1.Sum(composite) {
|
||||
return nil, fmt.Errorf("PCRs passed didn't match quote: %v", err)
|
||||
return fmt.Errorf("PCRs passed didn't match quote: %v", err)
|
||||
}
|
||||
return e.PCRs, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EventLog) validate20Quote() (pcrs []PCR, err error) {
|
||||
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(e.Quote.Signature))
|
||||
func (a *AIKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error {
|
||||
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(quote.Signature))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse quote signature: %v", err)
|
||||
return fmt.Errorf("parse quote signature: %v", err)
|
||||
}
|
||||
|
||||
sigHash := e.AIKHash.New()
|
||||
sigHash.Write(e.Quote.Quote)
|
||||
sigHash := a.Hash.New()
|
||||
sigHash.Write(quote.Quote)
|
||||
|
||||
switch pub := e.AIKPublic.(type) {
|
||||
switch pub := a.Public.(type) {
|
||||
case *rsa.PublicKey:
|
||||
if sig.RSA == nil {
|
||||
return nil, fmt.Errorf("rsa public key provided for ec signature")
|
||||
return fmt.Errorf("rsa public key provided for ec signature")
|
||||
}
|
||||
sigBytes := []byte(sig.RSA.Signature)
|
||||
if err := rsa.VerifyPKCS1v15(pub, e.AIKHash, sigHash.Sum(nil), sigBytes); err != nil {
|
||||
return nil, fmt.Errorf("invalid quote signature: %v", err)
|
||||
if err := rsa.VerifyPKCS1v15(pub, a.Hash, sigHash.Sum(nil), sigBytes); err != nil {
|
||||
return fmt.Errorf("invalid quote signature: %v", err)
|
||||
}
|
||||
default:
|
||||
// TODO(ericchiang): support ecdsa
|
||||
return nil, fmt.Errorf("unsupported public key type %T", pub)
|
||||
return fmt.Errorf("unsupported public key type %T", pub)
|
||||
}
|
||||
|
||||
att, err := tpm2.DecodeAttestationData(e.Quote.Quote)
|
||||
att, err := tpm2.DecodeAttestationData(quote.Quote)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing quote signature: %v", err)
|
||||
return fmt.Errorf("parsing quote signature: %v", err)
|
||||
}
|
||||
if att.Type != tpm2.TagAttestQuote {
|
||||
return nil, fmt.Errorf("attestation isn't a quote, tag of type 0x%x", att.Type)
|
||||
return fmt.Errorf("attestation isn't a quote, tag of type 0x%x", att.Type)
|
||||
}
|
||||
if !bytes.Equal([]byte(att.ExtraData), e.Nonce) {
|
||||
return nil, fmt.Errorf("nonce didn't match: %v", err)
|
||||
if !bytes.Equal([]byte(att.ExtraData), nonce) {
|
||||
return fmt.Errorf("nonce didn't match: %v", err)
|
||||
}
|
||||
|
||||
pcrByIndex := map[int][]byte{}
|
||||
for _, pcr := range e.PCRs {
|
||||
for _, pcr := range pcrs {
|
||||
pcrByIndex[pcr.Index] = pcr.Digest
|
||||
}
|
||||
|
||||
n := len(att.AttestedQuoteInfo.PCRDigest)
|
||||
hash, ok := hashBySize[n]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("quote used unsupported hash algorithm length: %d", n)
|
||||
return fmt.Errorf("quote used unsupported hash algorithm length: %d", n)
|
||||
}
|
||||
var validatedPCRs []PCR
|
||||
|
||||
h := hash.New()
|
||||
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
|
||||
digest, ok := pcrByIndex[index]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("quote was over PCR %d which wasn't provided", index)
|
||||
return fmt.Errorf("quote was over PCR %d which wasn't provided", index)
|
||||
}
|
||||
if len(digest) != hash.Size() {
|
||||
return nil, fmt.Errorf("mismatch pcr and quote hash, pcr hash length=%d, quote hash length=%d", len(digest), hash.Size())
|
||||
return fmt.Errorf("mismatch pcr and quote hash, pcr hash length=%d, quote hash length=%d", len(digest), hash.Size())
|
||||
}
|
||||
h.Write(digest)
|
||||
validatedPCRs = append(validatedPCRs, PCR{Index: index, Digest: digest})
|
||||
}
|
||||
|
||||
if !bytes.Equal(h.Sum(nil), att.AttestedQuoteInfo.PCRDigest) {
|
||||
return nil, fmt.Errorf("quote digest didn't match pcrs provided")
|
||||
return fmt.Errorf("quote digest didn't match pcrs provided")
|
||||
}
|
||||
return validatedPCRs, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var hashBySize = map[int]crypto.Hash{
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2019 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy of
|
||||
// the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
@ -85,20 +99,19 @@ func testEventLog(t *testing.T, testdata string) {
|
||||
if err != nil {
|
||||
t.Fatalf("parsing AIK: %v", err)
|
||||
}
|
||||
|
||||
el := EventLog{
|
||||
AIKPublic: aik.Public,
|
||||
AIKHash: aik.Hash,
|
||||
Quote: &Quote{
|
||||
if err := aik.Verify(Quote{
|
||||
Version: dump.Static.TPMVersion,
|
||||
Quote: dump.Quote.Quote,
|
||||
Signature: dump.Quote.Signature,
|
||||
},
|
||||
Nonce: dump.Quote.Nonce,
|
||||
PCRs: dump.Log.PCRs,
|
||||
MeasurementLog: dump.Log.Raw,
|
||||
}, dump.Log.PCRs, dump.Quote.Nonce); err != nil {
|
||||
t.Fatalf("verifying quote: %v", err)
|
||||
}
|
||||
if _, err := el.Validate(); err != nil {
|
||||
|
||||
el, err := ParseEventLog(dump.Log.Raw)
|
||||
if err != nil {
|
||||
t.Fatalf("parsing event log: %v", err)
|
||||
}
|
||||
if _, err := el.Verify(dump.Log.PCRs); err != nil {
|
||||
t.Fatalf("validating event log: %v", err)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user