mirror of
https://github.com/google/go-attestation.git
synced 2024-12-21 22:07:56 +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.
|
// HashAlg identifies a hashing Algorithm.
|
||||||
type HashAlg uint8
|
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
|
package attest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
// Ensure hashes are available.
|
// Ensure hashes are available.
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
_ "crypto/sha256"
|
_ "crypto/sha256"
|
||||||
|
|
||||||
"github.com/google/go-tpm/tpm2"
|
"github.com/google/go-tpm/tpm2"
|
||||||
@ -48,49 +62,26 @@ type Event struct {
|
|||||||
// match their data to their digest.
|
// match their data to their digest.
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventLog contains the data required to parse and validate an event log.
|
// ParseEventLog parses an unverified measurement log.
|
||||||
type EventLog struct {
|
func ParseEventLog(measurementLog []byte) (*EventLog, error) {
|
||||||
// AIKPublic is the activated public key that has been proven to be under the
|
rawEvents, err := parseEventLog(measurementLog)
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing measurement log: %v", err)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("pcrs failed to replay: %v", err)
|
return nil, fmt.Errorf("pcrs failed to replay: %v", err)
|
||||||
}
|
}
|
||||||
@ -114,113 +105,112 @@ type rawPCRComposite struct {
|
|||||||
Values tpmutil.U32Bytes
|
Values tpmutil.U32Bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EventLog) validate12Quote() (pcrs []PCR, err error) {
|
func (a *AIKPublic) validate12Quote(quote Quote, pcrs []PCR, nonce []byte) error {
|
||||||
pub, ok := e.AIKPublic.(*rsa.PublicKey)
|
pub, ok := a.Public.(*rsa.PublicKey)
|
||||||
if !ok {
|
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)
|
qHash := sha1.Sum(quote.Quote)
|
||||||
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA1, quote[:], e.Quote.Signature); err != nil {
|
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA1, qHash[:], quote.Signature); err != nil {
|
||||||
return nil, fmt.Errorf("invalid quote signature: %v", err)
|
return fmt.Errorf("invalid quote signature: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var att rawAttestationData
|
var att rawAttestationData
|
||||||
if _, err := tpmutil.Unpack(e.Quote.Quote, &att); err != nil {
|
if _, err := tpmutil.Unpack(quote.Quote, &att); err != nil {
|
||||||
return nil, fmt.Errorf("parsing quote: %v", err)
|
return fmt.Errorf("parsing quote: %v", err)
|
||||||
}
|
}
|
||||||
// TODO(ericchiang): validate Version field.
|
// TODO(ericchiang): validate Version field.
|
||||||
if att.Nonce != sha1.Sum(e.Nonce) {
|
if att.Nonce != sha1.Sum(nonce) {
|
||||||
return nil, fmt.Errorf("invalid nonce")
|
return fmt.Errorf("invalid nonce")
|
||||||
}
|
}
|
||||||
if att.Fixed != fixedQuote {
|
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
|
// 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 (
|
var (
|
||||||
pcrMask [3]byte // bitmap indicating which PCRs are active
|
pcrMask [3]byte // bitmap indicating which PCRs are active
|
||||||
values []byte // appended values of all PCRs
|
values []byte // appended values of all PCRs
|
||||||
)
|
)
|
||||||
for _, pcr := range e.PCRs {
|
for _, pcr := range pcrs {
|
||||||
if pcr.Index < 0 || pcr.Index >= 24 {
|
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)
|
pcrMask[pcr.Index/8] |= 1 << uint(pcr.Index%8)
|
||||||
values = append(values, pcr.Digest...)
|
values = append(values, pcr.Digest...)
|
||||||
}
|
}
|
||||||
composite, err := tpmutil.Pack(rawPCRComposite{3, pcrMask, values})
|
composite, err := tpmutil.Pack(rawPCRComposite{3, pcrMask, values})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshaling PCRss: %v", err)
|
return fmt.Errorf("marshaling PCRs: %v", err)
|
||||||
}
|
}
|
||||||
if att.Digest != sha1.Sum(composite) {
|
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) {
|
func (a *AIKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error {
|
||||||
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(e.Quote.Signature))
|
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(quote.Signature))
|
||||||
if err != nil {
|
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 := a.Hash.New()
|
||||||
sigHash.Write(e.Quote.Quote)
|
sigHash.Write(quote.Quote)
|
||||||
|
|
||||||
switch pub := e.AIKPublic.(type) {
|
switch pub := a.Public.(type) {
|
||||||
case *rsa.PublicKey:
|
case *rsa.PublicKey:
|
||||||
if sig.RSA == nil {
|
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)
|
sigBytes := []byte(sig.RSA.Signature)
|
||||||
if err := rsa.VerifyPKCS1v15(pub, e.AIKHash, sigHash.Sum(nil), sigBytes); err != nil {
|
if err := rsa.VerifyPKCS1v15(pub, a.Hash, sigHash.Sum(nil), sigBytes); err != nil {
|
||||||
return nil, fmt.Errorf("invalid quote signature: %v", err)
|
return fmt.Errorf("invalid quote signature: %v", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// TODO(ericchiang): support ecdsa
|
// 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 {
|
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 {
|
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) {
|
if !bytes.Equal([]byte(att.ExtraData), nonce) {
|
||||||
return nil, fmt.Errorf("nonce didn't match: %v", err)
|
return fmt.Errorf("nonce didn't match: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pcrByIndex := map[int][]byte{}
|
pcrByIndex := map[int][]byte{}
|
||||||
for _, pcr := range e.PCRs {
|
for _, pcr := range pcrs {
|
||||||
pcrByIndex[pcr.Index] = pcr.Digest
|
pcrByIndex[pcr.Index] = pcr.Digest
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(att.AttestedQuoteInfo.PCRDigest)
|
n := len(att.AttestedQuoteInfo.PCRDigest)
|
||||||
hash, ok := hashBySize[n]
|
hash, ok := hashBySize[n]
|
||||||
if !ok {
|
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()
|
h := hash.New()
|
||||||
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
|
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
|
||||||
digest, ok := pcrByIndex[index]
|
digest, ok := pcrByIndex[index]
|
||||||
if !ok {
|
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() {
|
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)
|
h.Write(digest)
|
||||||
validatedPCRs = append(validatedPCRs, PCR{Index: index, Digest: digest})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(h.Sum(nil), att.AttestedQuoteInfo.PCRDigest) {
|
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{
|
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
|
package attest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -85,20 +99,19 @@ func testEventLog(t *testing.T, testdata string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parsing AIK: %v", err)
|
t.Fatalf("parsing AIK: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := aik.Verify(Quote{
|
||||||
el := EventLog{
|
|
||||||
AIKPublic: aik.Public,
|
|
||||||
AIKHash: aik.Hash,
|
|
||||||
Quote: &Quote{
|
|
||||||
Version: dump.Static.TPMVersion,
|
Version: dump.Static.TPMVersion,
|
||||||
Quote: dump.Quote.Quote,
|
Quote: dump.Quote.Quote,
|
||||||
Signature: dump.Quote.Signature,
|
Signature: dump.Quote.Signature,
|
||||||
},
|
}, dump.Log.PCRs, dump.Quote.Nonce); err != nil {
|
||||||
Nonce: dump.Quote.Nonce,
|
t.Fatalf("verifying quote: %v", err)
|
||||||
PCRs: dump.Log.PCRs,
|
|
||||||
MeasurementLog: dump.Log.Raw,
|
|
||||||
}
|
}
|
||||||
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)
|
t.Fatalf("validating event log: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user