package attest
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/google/go-tpm/legacy/tpm2"
// TODO(jsonp): Move activation generation code to internal package.
"github.com/google/go-tpm/legacy/tpm2/credactivation"
)
const (
// minRSABits is the minimum accepted bit size of an RSA key.
minRSABits = 2048
// minECCBits is the minimum accepted bit size of an ECC key.
minECCBits = 256
// activationSecretLen is the size in bytes of the generated secret
// which is generated for credential activation.
activationSecretLen = 32
// symBlockSize is the block size used for symmetric ciphers used
// when generating the credential activation challenge.
symBlockSize = 16
// generatedMagic is a magic tag when can only be present on a
// TPM structure if the structure was generated wholly by the TPM.
generatedMagic = 0xff544347
)
// ActivationParameters encapsulates the inputs for activating an AK.
type ActivationParameters struct {
// EK, the endorsement key, describes an asymmetric key whose
// private key is permanently bound to the TPM.
//
// Activation will verify that the provided EK is held on the same
// TPM as the AK. However, it is the caller's responsibility to
// ensure the EK they provide corresponds to the the device which
// they are trying to associate the AK with.
EK crypto.PublicKey
// AK, the Attestation Key, describes the properties of
// an asymmetric key (managed by the TPM) which signs attestation
// structures.
// The values from this structure can be obtained by calling
// Parameters() on an attest.AK.
AK AttestationParameters
// Rand is a source of randomness to generate a seed and secret for the
// challenge.
//
// If nil, this defaults to crypto.Rand.
Rand io.Reader
}
// CheckAKParameters examines properties of an AK and a creation
// attestation, to determine if it is suitable for use as an attestation key.
func (p *ActivationParameters) CheckAKParameters() error {
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature is too short to be valid: only %d bytes", len(p.AK.CreateSignature))
}
pub, err := tpm2.DecodePublic(p.AK.Public)
if err != nil {
return fmt.Errorf("DecodePublic() failed: %v", err)
}
_, err = tpm2.DecodeCreationData(p.AK.CreateData)
if err != nil {
return fmt.Errorf("DecodeCreationData() failed: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.AK.CreateAttestation)
if err != nil {
return fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCreation {
return fmt.Errorf("attestation does not apply to creation data, got tag %x", att.Type)
}
// TODO: Support ECC AKs.
switch pub.Type {
case tpm2.AlgRSA:
if pub.RSAParameters.KeyBits < minRSABits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits)
}
case tpm2.AlgECC:
if len(pub.ECCParameters.Point.XRaw)*8 < minECCBits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minECCBits, len(pub.ECCParameters.Point.XRaw)*8)
}
if len(pub.ECCParameters.Point.YRaw)*8 < minECCBits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minECCBits, len(pub.ECCParameters.Point.YRaw)*8)
}
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
// Compute & verify that the creation data matches the digest in the
// attestation structure.
nameHash, err := pub.NameAlg.Hash()
if err != nil {
return fmt.Errorf("HashConstructor() failed: %v", err)
}
h := nameHash.New()
h.Write(p.AK.CreateData)
if !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) {
return errors.New("attestation refers to different public key")
}
// Make sure the AK has sane key parameters (Attestation can be faked if an AK
// can be used for arbitrary signatures).
// We verify the following:
// - Key is TPM backed.
// - Key is TPM generated.
// - Key is a restricted key (means it cannot do arbitrary signing/decrypt ops).
// - Key cannot be duplicated.
// - Key was generated by a call to TPM_Create*.
if att.Magic != generatedMagic {
return errors.New("creation attestation was not produced by a TPM")
}
if (pub.Attributes & tpm2.FlagFixedTPM) == 0 {
return errors.New("AK is exportable")
}
if ((pub.Attributes & tpm2.FlagRestricted) == 0) || ((pub.Attributes & tpm2.FlagFixedParent) == 0) || ((pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0) {
return errors.New("provided key is not limited to attestation")
}
// Verify the attested creation name matches what is computed from
// the public key.
match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub)
if err != nil {
return err
}
if !match {
return errors.New("creation attestation refers to a different key")
}
// Check the signature over the attestation data verifies correctly.
switch pub.Type {
case tpm2.AlgRSA:
return verifyRSASignature(pub, p)
case tpm2.AlgECC:
return verifyECDSASignature(pub, p)
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
}
func verifyRSASignature(pub tpm2.Public, p *ActivationParameters) error {
pk := rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
signHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
return err
}
hsh := signHash.New()
hsh.Write(p.AK.CreateAttestation)
verifyHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
return err
}
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AK.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AK.CreateSignature))
if err != nil {
return fmt.Errorf("DecodeSignature() failed: %v", err)
}
if err := rsa.VerifyPKCS1v15(&pk, verifyHash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
return fmt.Errorf("could not verify attestation: %v", err)
}
return nil
}
func verifyECDSASignature(pub tpm2.Public, p *ActivationParameters) error {
key, err := pub.Key()
if err != nil {
return nil
}
pk, ok := key.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("expected *ecdsa.PublicKey, got %T", key)
}
signHash, err := pub.ECCParameters.Sign.Hash.Hash()
if err != nil {
return err
}
hsh := signHash.New()
_, err = hsh.Write(p.AK.CreateAttestation)
if err != nil {
return err
}
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AK.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AK.CreateSignature))
if err != nil {
return fmt.Errorf("DecodeSignature() failed: %v", err)
}
if !ecdsa.Verify(pk, hsh.Sum(nil), sig.ECC.R, sig.ECC.S) {
return fmt.Errorf("unable to verify attestation for ecdsa credential")
}
return nil
}
// Generate returns a credential activation challenge, which can be provided
// to the TPM to verify the AK parameters given are authentic & the AK
// is present on the same TPM as the EK.
//
// It will call CheckAKParameters() at the beginning and return the error if
// there is any.
//
// The caller is expected to verify the secret returned from the TPM as
// as result of calling ActivateCredential() matches the secret returned here.
// The caller should use subtle.ConstantTimeCompare to avoid potential
// timing attack vectors.
func (p *ActivationParameters) Generate() (secret []byte, ec *EncryptedCredential, err error) {
if err := p.CheckAKParameters(); err != nil {
return nil, nil, err
}
if p.EK == nil {
return nil, nil, errors.New("no EK provided")
}
rnd, secret := p.Rand, make([]byte, activationSecretLen)
if rnd == nil {
rnd = rand.Reader
}
if _, err = io.ReadFull(rnd, secret); err != nil {
return nil, nil, fmt.Errorf("error generating activation secret: %v", err)
}
ec, err = p.generateChallenge(secret)
if err != nil {
return nil, nil, err
}
return secret, ec, nil
}
func (p *ActivationParameters) generateChallenge(secret []byte) (*EncryptedCredential, error) {
att, err := tpm2.DecodeAttestationData(p.AK.CreateAttestation)
if err != nil {
return nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.AttestedCreationInfo == nil {
return nil, fmt.Errorf("attestation was not for a creation event")
}
if att.AttestedCreationInfo.Name.Digest == nil {
return nil, fmt.Errorf("attestation creation info name has no digest")
}
cred, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, p.EK, symBlockSize, secret)
if err != nil {
return nil, fmt.Errorf("credactivation.Generate() failed: %v", err)
}
return &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
// Copyright 2021 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 (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"fmt"
"io"
)
type key interface {
close(tpmBase) error
marshal() ([]byte, error)
certificationParameters() CertificationParameters
sign(tpmBase, []byte, crypto.PublicKey, crypto.SignerOpts) ([]byte, error)
decrypt(tpmBase, []byte) ([]byte, error)
blobs() ([]byte, []byte, error)
}
// Key represents a key which can be used for signing and decrypting
// outside-TPM objects.
type Key struct {
key key
pub crypto.PublicKey
tpm tpmBase
}
// signer implements crypto.Signer returned by Key.Private().
type signer struct {
key key
pub crypto.PublicKey
tpm tpmBase
}
// Sign signs digest with the TPM-stored private signing key.
func (s *signer) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
return s.key.sign(s.tpm, digest, s.pub, opts)
}
// Public returns the public key corresponding to the private signing key.
func (s *signer) Public() crypto.PublicKey {
return s.pub
}
// Algorithm indicates an asymmetric algorithm to be used.
type Algorithm string
// Algorithm types supported.
const (
ECDSA Algorithm = "ECDSA"
RSA Algorithm = "RSA"
// Windows specific ECDSA CNG algorithm identifiers.
// NOTE: Using ECDSA will default to P256.
// Ref: https://learn.microsoft.com/en-us/windows/win32/SecCNG/cng-algorithm-identifiers
P256 Algorithm = "ECDSA_P256"
P384 Algorithm = "ECDSA_P384"
P521 Algorithm = "ECDSA_P521"
)
// KeyConfig encapsulates parameters for minting keys.
type KeyConfig struct {
// Algorithm to be used, either RSA or ECDSA.
Algorithm Algorithm
// Size is used to specify the bit size of the key or elliptic curve. For
// example, '256' is used to specify curve P-256.
Size int
// Parent describes the Storage Root Key that will be used as a parent.
// If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed.
// Supported only by TPM 2.0 on Linux.
Parent *ParentKeyConfig
// QualifyingData is an optional data that will be included into
// a TPM-generated signature of the minted key.
// It may contain any data chosen by the caller.
QualifyingData []byte
}
// defaultConfig is used when no other configuration is specified.
var defaultConfig = &KeyConfig{
Algorithm: ECDSA,
Size: 256,
}
// Size returns the bit size associated with an algorithm.
func (a Algorithm) Size() int {
switch a {
case RSA:
return 2048
case ECDSA:
return 256
case P256:
return 256
case P384:
return 384
case P521:
return 521
default:
return 0
}
}
// Public returns the public key corresponding to the private key.
func (k *Key) Public() crypto.PublicKey {
return k.pub
}
// Private returns an object allowing to use the TPM-backed private key.
// For now it implements only crypto.Signer.
func (k *Key) Private(pub crypto.PublicKey) (crypto.PrivateKey, error) {
switch pub.(type) {
case *rsa.PublicKey:
if _, ok := k.pub.(*rsa.PublicKey); !ok {
return nil, fmt.Errorf("incompatible public key types: %T != %T", pub, k.pub)
}
case *ecdsa.PublicKey:
if _, ok := k.pub.(*ecdsa.PublicKey); !ok {
return nil, fmt.Errorf("incompatible public key types: %T != %T", pub, k.pub)
}
default:
return nil, fmt.Errorf("unsupported public key type: %T", pub)
}
return &signer{k.key, k.pub, k.tpm}, nil
}
// Close unloads the key from the system.
func (k *Key) Close() error {
return k.key.close(k.tpm)
}
// Marshal encodes the key in a format that can be loaded with tpm.LoadKey().
// This method exists to allow consumers to store the key persistently and load
// it as a later time. Users SHOULD NOT attempt to interpret or extract values
// from this blob.
func (k *Key) Marshal() ([]byte, error) {
return k.key.marshal()
}
// CertificationParameters returns information about the key required to
// verify key certification.
func (k *Key) CertificationParameters() CertificationParameters {
return k.key.certificationParameters()
}
// Blobs returns public and private blobs to be used by tpm2.Load().
func (k *Key) Blobs() (pub, priv []byte, err error) {
return k.key.blobs()
}
// 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 abstracts TPM attestation operations.
package attest
import (
"crypto"
"crypto/x509"
"errors"
"fmt"
"io"
"strings"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
)
// TPMInterface indicates how the client communicates
// with the TPM.
type TPMInterface uint8
// TPM interfaces
const (
TPMInterfaceDirect TPMInterface = iota
TPMInterfaceKernelManaged
TPMInterfaceDaemonManaged
TPMInterfaceCommandChannel
)
// CommandChannelTPM20 represents a pipe along which TPM 2.0 commands
// can be issued, and measurement logs read.
type CommandChannelTPM20 interface {
io.ReadWriteCloser
MeasurementLog() ([]byte, error)
}
// OpenConfig encapsulates settings passed to OpenTPM().
type OpenConfig struct {
// CommandChannel provides a TPM 2.0 command channel, which can be
// used in-lieu of any TPM present on the platform.
CommandChannel CommandChannelTPM20
}
// keyEncoding indicates how an exported TPM key is represented.
type keyEncoding uint8
func (e keyEncoding) String() string {
switch e {
case keyEncodingInvalid:
return "invalid"
case keyEncodingOSManaged:
return "os-managed"
case keyEncodingEncrypted:
return "encrypted"
case keyEncodingParameterized:
return "parameterized"
default:
return fmt.Sprintf("keyEncoding<%d>", int(e))
}
}
// Key encodings
const (
keyEncodingInvalid keyEncoding = iota
// Managed by the OS but loadable by name.
keyEncodingOSManaged
// Key fully represented but in encrypted form.
keyEncodingEncrypted
// Parameters stored, but key must be regenerated before use.
keyEncodingParameterized
)
// ParentKeyConfig describes the Storage Root Key that is used
// as a parent for new keys.
type ParentKeyConfig struct {
Algorithm Algorithm
Handle tpmutil.Handle
}
var defaultParentConfig = ParentKeyConfig{
Algorithm: RSA,
Handle: 0x81000001,
}
type ak interface {
close(tpmBase) error
marshal() ([]byte, error)
activateCredential(tpm tpmBase, in EncryptedCredential, ek *EK) ([]byte, error)
quote(t tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error)
attestationParameters() AttestationParameters
certify(tb tpmBase, handle any, opts CertifyOpts) (*CertificationParameters, error)
signMsg(tb tpmBase, msg []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error)
}
// AK represents a key which can be used for attestation.
type AK struct {
ak ak
pub crypto.PublicKey
}
// Public returns the public key for the AK. This is only supported for TPM 2.0
// on Linux currently.
func (k *AK) Public() crypto.PublicKey {
return k.pub
}
// Close unloads the AK from the system.
func (k *AK) Close(t *TPM) error {
return k.ak.close(t.tpm)
}
// Marshal encodes the AK in a format that can be reloaded with tpm.LoadAK().
// This method exists to allow consumers to store the key persistently and load
// it as a later time. Users SHOULD NOT attempt to interpret or extract values
// from this blob.
func (k *AK) Marshal() ([]byte, error) {
return k.ak.marshal()
}
// ActivateCredential decrypts the secret using the key to prove that the AK
// was generated on the same TPM as the EK. This method can be used with TPMs
// that have the default EK, i.e. RSA EK with handle 0x81010001.
//
// This operation is synonymous with TPM2_ActivateCredential.
func (k *AK) ActivateCredential(tpm *TPM, in EncryptedCredential) (secret []byte, err error) {
return k.ak.activateCredential(tpm.tpm, in, nil)
}
// ActivateCredentialWithEK decrypts the secret using the key to prove that the AK
// was generated on the same TPM as the EK. This method can be used with TPMs
// that have an ECC EK. The 'ek' argument must be one of EKs returned from
// TPM.EKs() or TPM.EKCertificates().
//
// This operation is synonymous with TPM2_ActivateCredential.
func (k *AK) ActivateCredentialWithEK(tpm *TPM, in EncryptedCredential, ek EK) (secret []byte, err error) {
return k.ak.activateCredential(tpm.tpm, in, &ek)
}
// Quote returns a quote over the platform state, signed by the AK.
//
// This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead.
func (k *AK) Quote(tpm *TPM, nonce []byte, alg HashAlg) (*Quote, error) {
pcrs := make([]int, 24)
for pcr := range pcrs {
pcrs[pcr] = pcr
}
return k.ak.quote(tpm.tpm, nonce, alg, pcrs)
}
// QuotePCRs is like Quote() but allows the caller to select a subset of the PCRs.
func (k *AK) QuotePCRs(tpm *TPM, nonce []byte, alg HashAlg, pcrs []int) (*Quote, error) {
return k.ak.quote(tpm.tpm, nonce, alg, pcrs)
}
// AttestationParameters returns information about the AK, typically used to
// generate a credential activation challenge.
func (k *AK) AttestationParameters() AttestationParameters {
return k.ak.attestationParameters()
}
// Certify uses the attestation key to certify the key with `handle`. It returns
// certification parameters which allow to verify the properties of the attested
// key. Depending on the actual instantiation it can accept different handle
// types (e.g., tpmutil.Handle on Linux or uintptr on Windows).
func (k *AK) Certify(tpm *TPM, handle any) (*CertificationParameters, error) {
return k.ak.certify(tpm.tpm, handle, CertifyOpts{})
}
// SignMsg signs the message (not the digest) with the AK. Note that AK is a
// restricted signing key, it cannot sign a digest directly.
func (k *AK) SignMsg(tpm *TPM, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
return k.ak.signMsg(tpm.tpm, msg, k.pub, opts)
}
// AKConfig encapsulates parameters for minting keys.
type AKConfig struct {
// Optionally set unique name for AK on Windows.
Name string
// Parent describes the Storage Root Key that will be used as a parent.
// If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed.
// Supported only by TPM 2.0 on Linux.
Parent *ParentKeyConfig
// If not specified, the default algorithm (RSA) is assumed.
Algorithm Algorithm
}
// EncryptedCredential represents encrypted parameters which must be activated
// against a key.
type EncryptedCredential struct {
Credential []byte
Secret []byte
}
// Quote encapsulates the results of a Quote operation against the TPM,
// using an attestation key.
type Quote struct {
Quote []byte
Signature []byte
}
// PCR encapsulates the value of a PCR at a point in time.
type PCR struct {
Index int
Digest []byte
DigestAlg crypto.Hash
// quoteVerified is true if the PCR was verified against a quote
// in a call to AKPublic.Verify or AKPublic.VerifyAll.
quoteVerified bool
}
// QuoteVerified returns true if the value of this PCR was previously
// verified against a Quote, in a call to AKPublic.Verify or AKPublic.VerifyAll.
func (p *PCR) QuoteVerified() bool {
return p.quoteVerified
}
// EK is a burned-in endorcement key bound to a TPM. This optionally contains
// a certificate that can chain to the TPM manufacturer.
type EK struct {
// Public key of the EK.
Public crypto.PublicKey
// Certificate is the EK certificate for TPMs that provide it.
Certificate *x509.Certificate
// For Intel TPMs, Intel hosts certificates at a public URL derived from the
// Public key. Clients or servers can perform an HTTP GET to this URL, and
// use ParseEKCertificate on the response body.
CertificateURL string
// The EK persistent handle.
handle tpmutil.Handle
}
// AttestationParameters describes information about a key which is necessary
// for verifying its properties remotely.
type AttestationParameters struct {
// Public represents the AK's canonical encoding. This blob includes the
// public key, as well as signing parameters such as the hash algorithm
// used to generate quotes.
//
// Use ParseAKPublic to access the key's data.
Public []byte
// For TPM 2.0 devices, Public is encoded as a TPMT_PUBLIC structure.
// For TPM 1.2 devices, Public is a TPM_PUBKEY structure, as defined in
// the TPM Part 2 Structures specification, available at
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011.pdf
// UseTCSDActivationFormat is set when tcsd (trousers daemon) is operating
// as an intermediary between this library and the TPM. A value of true
// indicates that activation challenges should use the TCSD-specific format.
UseTCSDActivationFormat bool
// Subsequent fields are only populated for AKs generated on a TPM
// implementing version 2.0 of the specification. The specific structures
// referenced for each field are defined in the TPM Revision 2, Part 2 -
// Structures specification, available here:
// https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
// CreateData represents the properties of a TPM 2.0 key. It is encoded
// as a TPMS_CREATION_DATA structure.
CreateData []byte
// CreateAttestation represents an assertion as to the details of the key.
// It is encoded as a TPMS_ATTEST structure.
CreateAttestation []byte
// CreateSignature represents a signature of the CreateAttestation structure.
// It is encoded as a TPMT_SIGNATURE structure.
CreateSignature []byte
}
// AKPublic holds structured information about an AK's public key.
type AKPublic struct {
// Public is the public part of the AK. This can either be an *rsa.PublicKey or
// and *ecdsa.PublicKey.
Public crypto.PublicKey
// Hash is the hashing algorithm the AK will use when signing quotes.
Hash crypto.Hash
}
// ParseAKPublic parses the Public blob from the AttestationParameters,
// returning the public key and signing parameters for the key.
func ParseAKPublic(public []byte) (*AKPublic, error) {
pub, err := tpm2.DecodePublic(public)
if err != nil {
return nil, fmt.Errorf("parsing TPM public key structure: %v", err)
}
switch {
case pub.RSAParameters == nil && pub.ECCParameters == nil:
return nil, errors.New("parsing public key: missing asymmetric parameters")
case pub.RSAParameters != nil && pub.RSAParameters.Sign == nil:
return nil, errors.New("parsing public key: missing rsa signature scheme")
case pub.ECCParameters != nil && pub.ECCParameters.Sign == nil:
return nil, errors.New("parsing public key: missing ecc signature scheme")
}
pubKey, err := pub.Key()
if err != nil {
return nil, fmt.Errorf("parsing public key: %v", err)
}
var h crypto.Hash
switch pub.Type {
case tpm2.AlgRSA:
h, err = pub.RSAParameters.Sign.Hash.Hash()
case tpm2.AlgECC:
h, err = pub.ECCParameters.Sign.Hash.Hash()
default:
return nil, fmt.Errorf("unsupported public key type 0x%x", pub.Type)
}
if err != nil {
return nil, fmt.Errorf("invalid public key hash: %v", err)
}
return &AKPublic{Public: pubKey, Hash: h}, nil
}
// Verify is used to prove authenticity of the PCR measurements. It ensures that
// the quote was signed by the AK, and that its contents matches the PCR and
// nonce combination. An error is returned if a provided PCR index was not part
// of the quote. QuoteVerified() will return true on PCRs which were verified
// by a quote.
//
// Do NOT use this method if you have multiple quotes to verify: Use VerifyAll
// instead.
//
// 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 *AKPublic) Verify(quote Quote, pcrs []PCR, nonce []byte) error {
return a.validateQuote(quote, pcrs, nonce)
}
// VerifyAll uses multiple quotes to verify the authenticity of all PCR
// measurements. See documentation on Verify() for semantics.
//
// An error is returned if any PCRs provided were not covered by a quote,
// or if no quote/nonce was provided.
func (a *AKPublic) VerifyAll(quotes []Quote, pcrs []PCR, nonce []byte) error {
if len(quotes) == 0 {
return errors.New("no quotes were provided")
}
if len(nonce) == 0 {
return errors.New("no nonce was provided")
}
for i, quote := range quotes {
if err := a.Verify(quote, pcrs, nonce); err != nil {
return fmt.Errorf("quote %d: %v", i, err)
}
}
var errPCRs []string
for _, p := range pcrs {
if !p.QuoteVerified() {
errPCRs = append(errPCRs, fmt.Sprintf("%d (%s)", p.Index, p.DigestAlg))
}
}
if len(errPCRs) > 0 {
return fmt.Errorf("some PCRs were not covered by a quote: %s", strings.Join(errPCRs, ", "))
}
return nil
}
// HashAlg identifies a hashing Algorithm.
type HashAlg uint8
// Known valid hash algorithms.
var (
HashSHA1 = HashAlg(tpm2.AlgSHA1)
HashSHA256 = HashAlg(tpm2.AlgSHA256)
HashSHA384 = HashAlg(tpm2.AlgSHA384)
HashSHA512 = HashAlg(tpm2.AlgSHA512)
)
func (a HashAlg) cryptoHash() (crypto.Hash, error) {
g := a.goTPMAlg()
h, err := g.Hash()
if err != nil {
return 0, fmt.Errorf("HashAlg %v (corresponding to TPM2.Algorithm %v) has no corresponding crypto.Hash", a, g)
}
return h, nil
}
func (a HashAlg) goTPMAlg() tpm2.Algorithm {
return tpm2.Algorithm(a)
}
// String returns a human-friendly representation of the hash algorithm.
func (a HashAlg) String() string {
return a.goTPMAlg().String()
}
// PlatformParameters encapsulates the set of information necessary to attest
// the booted state of the machine the TPM is attached to.
//
// The digests contained in the event log can be considered authentic if:
// - The AK public corresponds to the known AK for that platform.
// - All quotes are verified with AKPublic.Verify(), and return no errors.
// - The event log parsed successfully using ParseEventLog(), and a call
// to EventLog.Verify() with the full set of PCRs returned no error.
type PlatformParameters struct {
// The public blob of the AK which endorsed the platform state. This can
// be decoded to verify the adjacent quotes using ParseAKPublic().
Public []byte
// The set of quotes which endorse the state of the PCRs.
Quotes []Quote
// The set of expected PCR values, which are used in replaying the event log
// to verify digests were not tampered with.
PCRs []PCR
// The raw event log provided by the platform. This can be processed with
// ParseEventLog().
EventLog []byte
}
var (
defaultOpenConfig = &OpenConfig{}
// ErrTPMNotAvailable is returned in response to OpenTPM() when
// either no TPM is available, or a TPM of the requested version
// is not available (if TPMVersion was set in the provided config).
ErrTPMNotAvailable = errors.New("TPM device not available")
)
// TPMInfo contains information about the version & interface
// of an open TPM.
type TPMInfo struct {
Interface TPMInterface
VendorInfo string
Manufacturer TCGVendorID
// FirmwareVersionMajor and FirmwareVersionMinor describe
// the firmware version of the TPM, but are only available
// for TPM 2.0 devices.
FirmwareVersionMajor int
FirmwareVersionMinor int
}
// OpenTPM initializes access to the TPM based on the
// config provided.
func OpenTPM(config *OpenConfig) (*TPM, error) {
if config == nil {
config = defaultOpenConfig
}
// As a special case, if the user provided us with a command channel,
// we should use that.
if config.CommandChannel != nil {
return &TPM{&wrappedTPM20{
interf: TPMInterfaceCommandChannel,
rwc: config.CommandChannel,
}}, nil
}
candidateTPMs, err := probeSystemTPMs()
if err != nil {
return nil, err
}
for _, tpm := range candidateTPMs {
return openTPM(tpm)
}
return nil, ErrTPMNotAvailable
}
// AvailableTPMs returns information about available TPMs matching
// the given config, without opening the devices.
func AvailableTPMs(config *OpenConfig) ([]TPMInfo, error) {
candidateTPMs, err := probeSystemTPMs()
if err != nil {
return nil, err
}
var out []TPMInfo
for _, tpm := range candidateTPMs {
t, err := openTPM(tpm)
if err != nil {
return nil, err
}
defer t.Close()
i, err := t.Info()
if err != nil {
return nil, err
}
out = append(out, *i)
}
return out, nil
}
// 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.
//go:build gofuzz
// +build gofuzz
package attest
// FuzzParseAKPublic is an exported entrypoint for fuzzers to test
// ParseAKPublic. This method should not be used for any other purpose.
func FuzzParseAKPublic(data []byte) int {
_, err := ParseAKPublic(data)
if err != nil {
return 0
}
return 1
}
// FuzzParseEKCertificate is an exported entrypoint for fuzzers to test
// ParseEKCertificate. This method should not be used for any other purpose.
func FuzzParseEKCertificate(data []byte) int {
_, err := ParseEKCertificate(data)
if err != nil {
return 0
}
return 1
}
// Copyright 2021 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/ecdsa"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/legacy/tpm2/credactivation"
"github.com/google/go-tpm/tpmutil"
)
// secureCurves represents a set of secure elliptic curves. For now,
// the selection is based on the key size only.
var secureCurves = map[tpm2.EllipticCurve]bool{
tpm2.CurveNISTP256: true,
tpm2.CurveNISTP384: true,
tpm2.CurveNISTP521: true,
tpm2.CurveBNP256: true,
tpm2.CurveBNP638: true,
}
// CertificationParameters encapsulates the inputs for certifying an application key.
type CertificationParameters struct {
// Public represents the key's canonical encoding (a TPMT_PUBLIC structure).
// It includes the public key and signing parameters.
Public []byte
// CreateData represents the properties of a TPM 2.0 key. It is encoded
// as a TPMS_CREATION_DATA structure.
CreateData []byte
// CreateAttestation represents an assertion as to the details of the key.
// It is encoded as a TPMS_ATTEST structure.
CreateAttestation []byte
// CreateSignature represents a signature of the CreateAttestation structure.
// It is encoded as a TPMT_SIGNATURE structure.
CreateSignature []byte
}
// VerifyOpts specifies options for the key certification's verification.
type VerifyOpts struct {
// Public is the public key used to verify key ceritification.
Public crypto.PublicKey
// Hash is the hash function used for signature verification. It can be
// extracted from the properties of the certifying key.
Hash crypto.Hash
}
// ActivateOpts specifies options for the key certification's challenge generation.
type ActivateOpts struct {
// EK, the endorsement key, describes an asymmetric key whose
// private key is permanently bound to the TPM.
//
// Activation will verify that the provided EK is held on the same
// TPM as the key we're certifying. However, it is the caller's responsibility to
// ensure the EK they provide corresponds to the the device which
// they are trying to associate the certified key with.
EK crypto.PublicKey
// VerifierKeyNameDigest is the name digest of the public key we're using to
// verify the certification of the tpm-generated key being activated.
// The verifier key (usually the AK) that owns this digest should be the same
// key used in VerifyOpts.Public.
// Use tpm2.Public.Name() to produce the digest for a provided key.
VerifierKeyNameDigest *tpm2.HashValue
}
// CertifyOpts specifies options for the key's certification.
type CertifyOpts struct {
// QualifyingData is the user provided qualifying data.
QualifyingData []byte
}
// NewActivateOpts creates options for use in generating an activation challenge for a certified key.
// The computed hash is the name digest of the public key used to verify the certification of our key.
func NewActivateOpts(verifierPubKey tpm2.Public, ek crypto.PublicKey) (*ActivateOpts, error) {
pubName, err := verifierPubKey.Name()
if err != nil {
return nil, fmt.Errorf("unable to resolve a tpm2.Public Name struct from the given public key struct: %v", err)
}
return &ActivateOpts{
EK: ek,
VerifierKeyNameDigest: pubName.Digest,
}, nil
}
// Verify verifies the TPM2-produced certification parameters checking whether:
// - the key length is secure
// - the attestation parameters matched the attested key
// - the key was TPM-generated and resides within TPM
// - the key can sign/decrypt outside-TPM objects
// - the signature is successfuly verified against the passed public key
// For now, it accepts only RSA verification keys.
func (p *CertificationParameters) Verify(opts VerifyOpts) error {
pub, err := tpm2.DecodePublic(p.Public)
if err != nil {
return fmt.Errorf("DecodePublic() failed: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.CreateAttestation)
if err != nil {
return fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCertify {
return fmt.Errorf("attestation does not apply to certification data, got tag %x", att.Type)
}
switch pub.Type {
case tpm2.AlgRSA:
if pub.RSAParameters.KeyBits < minRSABits {
return fmt.Errorf("attested key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits)
}
case tpm2.AlgECC:
if !secureCurves[pub.ECCParameters.CurveID] {
return fmt.Errorf("attested key uses insecure curve")
}
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
// Make sure the key has sane parameters (e.g., attestation can be faked if an AK
// can be used for arbitrary signatures).
// We verify the following:
// - Key is TPM backed.
// - Key is TPM generated.
// - Key is not restricted (means it can do arbitrary signing/decrypt ops).
// - Key cannot be duplicated.
// - Key was generated by a call to TPM_Create*.
if att.Magic != generatedMagic {
return errors.New("creation attestation was not produced by a TPM")
}
if (pub.Attributes & tpm2.FlagFixedTPM) == 0 {
return errors.New("provided key is exportable")
}
if (pub.Attributes & tpm2.FlagRestricted) != 0 {
return errors.New("provided key is restricted")
}
if (pub.Attributes & tpm2.FlagFixedParent) == 0 {
return errors.New("provided key can be duplicated to a different parent")
}
if (pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0 {
return errors.New("provided key is not created by TPM")
}
// Verify the attested creation name matches what is computed from
// the public key.
match, err := att.AttestedCertifyInfo.Name.MatchesPublic(pub)
if err != nil {
return err
}
if !match {
return errors.New("certification refers to a different key")
}
// Check the signature over the attestation data verifies correctly.
if !opts.Hash.Available() {
return fmt.Errorf("hash function is unavailable")
}
hsh := opts.Hash.New()
hsh.Write(p.CreateAttestation)
if len(p.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.CreateSignature))
if err != nil {
return fmt.Errorf("DecodeSignature() failed: %v", err)
}
switch pk := opts.Public.(type) {
case *rsa.PublicKey:
if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
return fmt.Errorf("could not verify attestation: %v", err)
}
case *ecdsa.PublicKey:
if ok := ecdsa.Verify(pk, hsh.Sum(nil), sig.ECC.R, sig.ECC.S); !ok {
return fmt.Errorf("could not verify ECC attestation")
}
default:
return fmt.Errorf("unsupported public key type: %T", pub)
}
return nil
}
// Generate returns a credential activation challenge, which can be provided
// to the TPM to verify the AK parameters given are authentic & the AK
// is present on the same TPM as the EK.
//
// The caller is expected to verify the secret returned from the TPM as
// as result of calling ActivateCredential() matches the secret returned here.
// The caller should use subtle.ConstantTimeCompare to avoid potential
// timing attack vectors.
func (p *CertificationParameters) Generate(rnd io.Reader, verifyOpts VerifyOpts, activateOpts ActivateOpts) (secret []byte, ec *EncryptedCredential, err error) {
if err := p.Verify(verifyOpts); err != nil {
return nil, nil, err
}
if activateOpts.EK == nil {
return nil, nil, errors.New("no EK provided")
}
secret = make([]byte, activationSecretLen)
if rnd == nil {
rnd = rand.Reader
}
if _, err = io.ReadFull(rnd, secret); err != nil {
return nil, nil, fmt.Errorf("error generating activation secret: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.CreateAttestation)
if err != nil {
return nil, nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCertify {
return nil, nil, fmt.Errorf("attestation does not apply to certify data, got %x", att.Type)
}
cred, encSecret, err := credactivation.Generate(activateOpts.VerifierKeyNameDigest, activateOpts.EK, symBlockSize, secret)
if err != nil {
return nil, nil, fmt.Errorf("credactivation.Generate() failed: %v", err)
}
return secret, &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
// certify uses AK's handle, the passed user qualifying data, and the passed
// signature scheme to certify the key with the `hnd` handle.
func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, qualifyingData []byte, scheme tpm2.SigScheme) (*CertificationParameters, error) {
pub, _, _, err := tpm2.ReadPublic(tpm, hnd)
if err != nil {
return nil, fmt.Errorf("tpm2.ReadPublic() failed: %v", err)
}
public, err := pub.Encode()
if err != nil {
return nil, fmt.Errorf("could not encode public key: %v", err)
}
att, sig, err := tpm2.CertifyEx(tpm, "", "", hnd, akHnd, qualifyingData, scheme)
if err != nil {
return nil, fmt.Errorf("tpm2.Certify() failed: %v", err)
}
return &CertificationParameters{
Public: public,
CreateAttestation: att,
CreateSignature: sig,
}, nil
}
package attest
import (
"bytes"
"crypto/sha1"
"encoding/binary"
)
const (
ekBlobTag = 0x000c
ekBlobActivateTag = 0x002b
ekTypeActivate = 0x0001
algXOR = 0x0000000a
schemeESNone = 0x0001
)
type symKeyHeader struct {
Alg uint32
Scheme uint16
KeySize uint16
}
type activationBlobHeader struct {
Tag uint16
KeyHeader symKeyHeader
}
func makeEmptyPCRInfo() []byte {
var b bytes.Buffer
binary.Write(&b, binary.BigEndian, uint16(3)) // SIZE_OF_SELECT
b.Write([]byte{0x00, 0x00, 0x00}) // empty bitfield for 3 PCRs
b.Write([]byte{0x01}) // TPM_LOCALITY_SELECTION = TPM_LOC_ZERO
b.Write(bytes.Repeat([]byte{0}, sha1.Size)) // TPM_COMPOSITE_HASH
return b.Bytes()
}
func makeActivationBlob(symKey, akpub []byte) (blob []byte, err error) {
akHash := sha1.Sum(akpub)
var out bytes.Buffer
if err := binary.Write(&out, binary.BigEndian, activationBlobHeader{
Tag: ekBlobActivateTag,
KeyHeader: symKeyHeader{
Alg: algXOR,
Scheme: schemeESNone,
KeySize: uint16(len(symKey)),
},
}); err != nil {
return nil, err
}
out.Write(symKey)
out.Write(akHash[:])
out.Write(makeEmptyPCRInfo())
return out.Bytes(), nil
}
// 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"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"sort"
"strings"
"github.com/google/go-tpm/legacy/tpm2"
)
// ReplayError describes the parsed events that failed to verify against
// a particular PCR.
type ReplayError struct {
Events []Event
// InvalidPCRs reports the set of PCRs where the event log replay failed.
InvalidPCRs []int
}
func (e ReplayError) affected(pcr int) bool {
for _, p := range e.InvalidPCRs {
if p == pcr {
return true
}
}
return false
}
// Error returns a human-friendly description of replay failures.
func (e ReplayError) Error() string {
return fmt.Sprintf("event log failed to verify: the following registers failed to replay: %v", e.InvalidPCRs)
}
// EventType indicates what kind of data an event is reporting.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=103
type EventType uint32
var eventTypeStrings = map[uint32]string{
0x00000000: "EV_PREBOOT_CERT",
0x00000001: "EV_POST_CODE",
0x00000002: "EV_UNUSED",
0x00000003: "EV_NO_ACTION",
0x00000004: "EV_SEPARATOR",
0x00000005: "EV_ACTION",
0x00000006: "EV_EVENT_TAG",
0x00000007: "EV_S_CRTM_CONTENTS",
0x00000008: "EV_S_CRTM_VERSION",
0x00000009: "EV_CPU_MICROCODE",
0x0000000A: "EV_PLATFORM_CONFIG_FLAGS",
0x0000000B: "EV_TABLE_OF_DEVICES",
0x0000000C: "EV_COMPACT_HASH",
0x0000000D: "EV_IPL",
0x0000000E: "EV_IPL_PARTITION_DATA",
0x0000000F: "EV_NONHOST_CODE",
0x00000010: "EV_NONHOST_CONFIG",
0x00000011: "EV_NONHOST_INFO",
0x00000012: "EV_OMIT_BOOT_DEVICE_EVENTS",
0x80000000: "EV_EFI_EVENT_BASE",
0x80000001: "EV_EFI_VARIABLE_DRIVER_CONFIG",
0x80000002: "EV_EFI_VARIABLE_BOOT",
0x80000003: "EV_EFI_BOOT_SERVICES_APPLICATION",
0x80000004: "EV_EFI_BOOT_SERVICES_DRIVER",
0x80000005: "EV_EFI_RUNTIME_SERVICES_DRIVER",
0x80000006: "EV_EFI_GPT_EVENT",
0x80000007: "EV_EFI_ACTION",
0x80000008: "EV_EFI_PLATFORM_FIRMWARE_BLOB",
0x80000009: "EV_EFI_HANDOFF_TABLES",
0x80000010: "EV_EFI_HCRTM_EVENT",
0x800000E0: "EV_EFI_VARIABLE_AUTHORITY",
}
// String returns the Spec name of the EventType, for example "EV_ACTION". If
// unknown, it returns a formatted string of the EventType value.
func (e EventType) String() string {
if s, ok := eventTypeStrings[uint32(e)]; ok {
return s
}
// NOTE: 0x00000013-0x0000FFFF are reserverd. Should we include that
// information in the formatting?
return fmt.Sprintf("EventType(0x%08x)", uint32(e))
}
// Event is a single event from a TCG event log. This reports descrete items such
// as BIOS measurements or EFI states.
//
// There are many pitfalls for using event log events correctly to determine the
// state of a machine[1]. In general it's much safer to only rely on the raw PCR
// values and use the event log for debugging.
//
// [1] https://github.com/google/go-attestation/blob/master/docs/event-log-disclosure.md
type Event struct {
// order of the event in the event log.
sequence int
// Index of the PCR that this event was replayed against.
Index int
// Untrusted type of the event. This value is not verified by event log replays
// and can be tampered with. It should NOT be used without additional context,
// and unrecognized event types should result in errors.
Type EventType
// Data of the event. For certain kinds of events, this must match the event
// digest to be valid.
Data []byte
// Digest is the verified digest of the event data. While an event can have
// multiple for different hash values, this is the one that was matched to the
// PCR value.
Digest []byte
// TODO(ericchiang): Provide examples or links for which event types must
// match their data to their digest.
}
func (e *Event) digestEquals(b []byte) error {
if len(e.Digest) == 0 {
return errors.New("no digests present")
}
switch len(e.Digest) {
case crypto.SHA256.Size():
s := sha256.Sum256(b)
if bytes.Equal(s[:], e.Digest) {
return nil
}
case crypto.SHA1.Size():
s := sha1.Sum(b)
if bytes.Equal(s[:], e.Digest) {
return nil
}
default:
return fmt.Errorf("cannot compare hash of length %d", len(e.Digest))
}
return fmt.Errorf("digest (len %d) does not match", len(e.Digest))
}
// 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 {
// Algs holds the set of algorithms that the event log uses.
Algs []HashAlg
rawEvents []rawEvent
specIDEvent *specIDEvent
}
func (e *EventLog) clone() *EventLog {
out := EventLog{
Algs: make([]HashAlg, len(e.Algs)),
rawEvents: make([]rawEvent, len(e.rawEvents)),
}
copy(out.Algs, e.Algs)
copy(out.rawEvents, e.rawEvents)
if e.specIDEvent != nil {
dupe := *e.specIDEvent
out.specIDEvent = &dupe
}
return &out
}
// Events returns events that have not been replayed against the PCR values and
// are therefore unverified. The returned events contain the digest that matches
// the provided hash algorithm, or are empty if that event didn't contain a
// digest for that hash.
//
// This method is insecure and should only be used for debugging.
func (e *EventLog) Events(hash HashAlg) []Event {
var events []Event
for _, re := range e.rawEvents {
ev := Event{
Index: re.index,
Type: re.typ,
Data: re.data,
}
for _, digest := range re.digests {
if hash, err := hash.cryptoHash(); hash != digest.hash || err != nil {
continue
}
ev.Digest = digest.data
break
}
events = append(events, ev)
}
return events
}
// Verify replays the event log against a TPM's PCR values, returning the
// events which could be matched to a provided PCR value.
//
// PCRs provide no security guarantees unless they're attested to have been
// generated by a TPM. Verify does not perform these checks.
//
// An error is returned if the replayed digest for events with a given PCR
// index do not match any provided value for that PCR index.
func (e *EventLog) Verify(pcrs []PCR) ([]Event, error) {
events, err := e.verify(pcrs)
// If there were any issues replaying the PCRs, try each of the workarounds
// in turn.
// TODO(jsonp): Allow workarounds to be combined.
if rErr, isReplayErr := err.(ReplayError); isReplayErr {
for _, wkrd := range eventlogWorkarounds {
if !rErr.affected(wkrd.affectedPCR) {
continue
}
el := e.clone()
if err := wkrd.apply(el); err != nil {
return nil, fmt.Errorf("failed applying workaround %q: %v", wkrd.id, err)
}
if events, err := el.verify(pcrs); err == nil {
return events, nil
}
}
}
return events, err
}
func (e *EventLog) verify(pcrs []PCR) ([]Event, error) {
events, err := replayEvents(e.rawEvents, pcrs)
if err != nil {
if _, isReplayErr := err.(ReplayError); isReplayErr {
return nil, err
}
return nil, fmt.Errorf("pcrs failed to replay: %v", err)
}
return events, nil
}
func (a *AKPublic) validateQuote(quote Quote, pcrs []PCR, nonce []byte) error {
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(quote.Signature))
if err != nil {
return fmt.Errorf("parse quote signature: %v", err)
}
sigHash := a.Hash.New()
sigHash.Write(quote.Quote)
switch pub := a.Public.(type) {
case *rsa.PublicKey:
if sig.RSA == nil {
return fmt.Errorf("rsa public key provided for ec signature")
}
sigBytes := []byte(sig.RSA.Signature)
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 fmt.Errorf("unsupported public key type %T", pub)
}
att, err := tpm2.DecodeAttestationData(quote.Quote)
if err != nil {
return fmt.Errorf("parsing quote signature: %v", err)
}
if att.Type != tpm2.TagAttestQuote {
return fmt.Errorf("attestation isn't a quote, tag of type 0x%x", att.Type)
}
if !bytes.Equal([]byte(att.ExtraData), nonce) {
return fmt.Errorf("nonce = %#v, want %#v", []byte(att.ExtraData), nonce)
}
pcrByIndex := map[int][]byte{}
pcrDigestAlg, err := HashAlg(att.AttestedQuoteInfo.PCRSelection.Hash).cryptoHash()
if err != nil {
return fmt.Errorf("invalid PCR selection hash: %v", err)
}
for _, pcr := range pcrs {
if pcr.DigestAlg == pcrDigestAlg {
pcrByIndex[pcr.Index] = pcr.Digest
}
}
sigHash.Reset()
quotePCRs := make(map[int]struct{}, len(att.AttestedQuoteInfo.PCRSelection.PCRs))
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
digest, ok := pcrByIndex[index]
if !ok {
return fmt.Errorf("quote was over PCR %d which wasn't provided", index)
}
quotePCRs[index] = struct{}{}
sigHash.Write(digest)
}
for index := range pcrByIndex {
if _, exists := quotePCRs[index]; !exists {
return fmt.Errorf("provided PCR %d was not included in quote", index)
}
}
if !bytes.Equal(sigHash.Sum(nil), att.AttestedQuoteInfo.PCRDigest) {
return fmt.Errorf("quote digest didn't match pcrs provided")
}
// If we got this far, all included PCRs with a digest algorithm matching that
// of the quote are verified. As such, we set their quoteVerified bit.
for i, pcr := range pcrs {
if _, exists := quotePCRs[pcr.Index]; exists && pcr.DigestAlg == pcrDigestAlg {
pcrs[i].quoteVerified = true
}
}
return nil
}
func extend(pcr PCR, replay []byte, e rawEvent, locality byte) (pcrDigest []byte, eventDigest []byte, err error) {
h := pcr.DigestAlg
for _, digest := range e.digests {
if digest.hash != pcr.DigestAlg {
continue
}
if len(digest.data) != len(pcr.Digest) {
return nil, nil, fmt.Errorf("digest data length (%d) doesn't match PCR digest length (%d)", len(digest.data), len(pcr.Digest))
}
hash := h.New()
if len(replay) != 0 {
hash.Write(replay)
} else {
b := make([]byte, h.Size())
b[h.Size()-1] = locality
hash.Write(b)
}
hash.Write(digest.data)
return hash.Sum(nil), digest.data, nil
}
return nil, nil, fmt.Errorf("no event digest matches pcr algorithm: %v", pcr.DigestAlg)
}
// replayPCR replays the event log for a specific PCR, using pcr and
// event digests with the algorithm in pcr. An error is returned if the
// replayed values do not match the final PCR digest, or any event tagged
// with that PCR does not possess an event digest with the specified algorithm.
func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
var (
replay []byte
outEvents []Event
locality byte
)
for _, e := range rawEvents {
if e.index != pcr.Index {
continue
}
// If TXT is enabled then the first event for PCR0
// should be a StartupLocality event. The final byte
// of this event indicates the locality from which
// TPM2_Startup() was issued. The initial value of
// PCR0 is equal to the locality.
if e.typ == eventTypeNoAction {
if pcr.Index == 0 && len(e.data) == 17 && strings.HasPrefix(string(e.data), "StartupLocality") {
locality = e.data[len(e.data)-1]
}
continue
}
replayValue, digest, err := extend(pcr, replay, e, locality)
if err != nil {
return nil, false
}
replay = replayValue
outEvents = append(outEvents, Event{sequence: e.sequence, Data: e.data, Digest: digest, Index: pcr.Index, Type: e.typ})
}
if len(outEvents) > 0 && !bytes.Equal(replay, pcr.Digest) {
return nil, false
}
return outEvents, true
}
type pcrReplayResult struct {
events []Event
successful bool
}
func replayEvents(rawEvents []rawEvent, pcrs []PCR) ([]Event, error) {
var (
invalidReplays []int
verifiedEvents []Event
allPCRReplays = map[int][]pcrReplayResult{}
)
// Replay the event log for every PCR and digest algorithm combination.
for _, pcr := range pcrs {
events, ok := replayPCR(rawEvents, pcr)
allPCRReplays[pcr.Index] = append(allPCRReplays[pcr.Index], pcrReplayResult{events, ok})
}
// Record PCR indices which do not have any successful replay. Record the
// events for a successful replay.
pcrLoop:
for i, replaysForPCR := range allPCRReplays {
for _, replay := range replaysForPCR {
if replay.successful {
// We consider the PCR verified at this stage: The replay of values with
// one digest algorithm matched a provided value.
// As such, we save the PCR's events, and proceed to the next PCR.
verifiedEvents = append(verifiedEvents, replay.events...)
continue pcrLoop
}
}
invalidReplays = append(invalidReplays, i)
}
if len(invalidReplays) > 0 {
events := make([]Event, 0, len(rawEvents))
for _, e := range rawEvents {
events = append(events, Event{e.sequence, e.index, e.typ, e.data, nil})
}
sort.Ints(invalidReplays)
return nil, ReplayError{
Events: events,
InvalidPCRs: invalidReplays,
}
}
sort.Slice(verifiedEvents, func(i int, j int) bool {
return verifiedEvents[i].sequence < verifiedEvents[j].sequence
})
return verifiedEvents, nil
}
// EV_NO_ACTION is a special event type that indicates information to the parser
// instead of holding a measurement. For TPM 2.0, this event type is used to signal
// switching from SHA1 format to a variable length digest.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110
const eventTypeNoAction = 0x03
// ParseEventLog parses an unverified measurement log.
func ParseEventLog(measurementLog []byte) (*EventLog, error) {
var specID *specIDEvent
r := bytes.NewBuffer(measurementLog)
parseFn := parseRawEvent
var el EventLog
e, err := parseFn(r, specID)
if err != nil {
return nil, fmt.Errorf("parse first event: %v", err)
}
if e.typ == eventTypeNoAction && len(e.data) >= binary.Size(specIDEventHeader{}) {
specID, err = parseSpecIDEvent(e.data)
if err != nil {
return nil, fmt.Errorf("failed to parse spec ID event: %v", err)
}
for _, alg := range specID.algs {
el.Algs = append(el.Algs, HashAlg(alg.ID))
}
// Switch to parsing crypto agile events. Don't include this in the
// replayed events since it intentionally doesn't extend the PCRs.
//
// Note that this doesn't actually guarantee that events have SHA256
// digests.
parseFn = parseRawEvent2
el.specIDEvent = specID
} else {
el.Algs = []HashAlg{HashSHA1}
el.rawEvents = append(el.rawEvents, e)
}
sequence := 1
for r.Len() != 0 {
e, err := parseFn(r, specID)
if err != nil {
return nil, err
}
e.sequence = sequence
sequence++
el.rawEvents = append(el.rawEvents, e)
}
return &el, nil
}
type specIDEvent struct {
algs []specAlgSize
}
type specAlgSize struct {
ID uint16
Size uint16
}
// Expected values for various Spec ID Event fields.
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=19
var wantSignature = [16]byte{0x53, 0x70,
0x65, 0x63, 0x20, 0x49,
0x44, 0x20, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x30,
0x33, 0x00} // "Spec ID Event03\0"
const (
wantMajor = 2
wantMinor = 0
wantErrata = 0
)
type specIDEventHeader struct {
Signature [16]byte
PlatformClass uint32
VersionMinor uint8
VersionMajor uint8
Errata uint8
UintnSize uint8
NumAlgs uint32
}
// parseSpecIDEvent parses a TCG_EfiSpecIDEventStruct structure from the reader.
//
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=18
func parseSpecIDEvent(b []byte) (*specIDEvent, error) {
r := bytes.NewReader(b)
var header specIDEventHeader
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, fmt.Errorf("reading event header: %w: %X", err, b)
}
if header.Signature != wantSignature {
return nil, fmt.Errorf("invalid spec id signature: %x", header.Signature)
}
if header.VersionMajor != wantMajor {
return nil, fmt.Errorf("invalid spec major version, got %02x, wanted %02x",
header.VersionMajor, wantMajor)
}
if header.VersionMinor != wantMinor {
return nil, fmt.Errorf("invalid spec minor version, got %02x, wanted %02x",
header.VersionMajor, wantMinor)
}
// TODO(ericchiang): Check errata? Or do we expect that to change in ways
// we're okay with?
specAlg := specAlgSize{}
e := specIDEvent{}
for i := 0; i < int(header.NumAlgs); i++ {
if err := binary.Read(r, binary.LittleEndian, &specAlg); err != nil {
return nil, fmt.Errorf("reading algorithm: %v", err)
}
e.algs = append(e.algs, specAlg)
}
var vendorInfoSize uint8
if err := binary.Read(r, binary.LittleEndian, &vendorInfoSize); err != nil {
return nil, fmt.Errorf("reading vender info size: %v", err)
}
if r.Len() != int(vendorInfoSize) {
return nil, fmt.Errorf("reading vendor info, expected %d remaining bytes, got %d", vendorInfoSize, r.Len())
}
return &e, nil
}
type digest struct {
hash crypto.Hash
data []byte
}
type rawEvent struct {
sequence int
index int
typ EventType
data []byte
digests []digest
}
// TPM 1.2 event log format. See "5.1 SHA1 Event Log Entry Format"
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
type rawEventHeader struct {
PCRIndex uint32
Type uint32
Digest [20]byte
EventSize uint32
}
type eventSizeErr struct {
eventSize uint32
logSize int
}
func (e *eventSizeErr) Error() string {
return fmt.Sprintf("event data size (%d bytes) is greater than remaining measurement log (%d bytes)", e.eventSize, e.logSize)
}
func parseRawEvent(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
var h rawEventHeader
if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
return event, fmt.Errorf("header deserialization error: %w", err)
}
if h.EventSize > uint32(r.Len()) {
return event, &eventSizeErr{h.EventSize, r.Len()}
}
data := make([]byte, int(h.EventSize))
if _, err := io.ReadFull(r, data); err != nil {
return event, fmt.Errorf("reading data error: %w", err)
}
digests := []digest{{hash: crypto.SHA1, data: h.Digest[:]}}
return rawEvent{
typ: EventType(h.Type),
data: data,
index: int(h.PCRIndex),
digests: digests,
}, nil
}
// TPM 2.0 event log format. See "5.2 Crypto Agile Log Entry Format"
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
type rawEvent2Header struct {
PCRIndex uint32
Type uint32
}
func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
var h rawEvent2Header
if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
return event, err
}
event.typ = EventType(h.Type)
event.index = int(h.PCRIndex)
// parse the event digests
var numDigests uint32
if err := binary.Read(r, binary.LittleEndian, &numDigests); err != nil {
return event, err
}
for i := 0; i < int(numDigests); i++ {
var algID uint16
if err := binary.Read(r, binary.LittleEndian, &algID); err != nil {
return event, err
}
var digest digest
for _, alg := range specID.algs {
if alg.ID != algID {
continue
}
if r.Len() < int(alg.Size) {
return event, fmt.Errorf("reading digest: %v", io.ErrUnexpectedEOF)
}
digest.data = make([]byte, alg.Size)
digest.hash, err = HashAlg(alg.ID).cryptoHash()
if err != nil {
return event, fmt.Errorf("unknown algorithm ID %x: %v", algID, err)
}
}
if len(digest.data) == 0 {
return event, fmt.Errorf("unknown algorithm ID %x", algID)
}
if _, err := io.ReadFull(r, digest.data); err != nil {
return event, err
}
event.digests = append(event.digests, digest)
}
// parse event data
var eventSize uint32
if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
return event, err
}
if eventSize > uint32(r.Len()) {
return event, &eventSizeErr{eventSize, r.Len()}
}
event.data = make([]byte, int(eventSize))
if _, err := io.ReadFull(r, event.data); err != nil {
return event, err
}
return event, err
}
// AppendEvents takes a series of TPM 2.0 event logs and combines
// them into a single sequence of events with a single header.
//
// Additional logs must not use a digest algorithm which was not
// present in the original log.
func AppendEvents(base []byte, additional ...[]byte) ([]byte, error) {
baseLog, err := ParseEventLog(base)
if err != nil {
return nil, fmt.Errorf("base: %v", err)
}
if baseLog.specIDEvent == nil {
return nil, errors.New("tpm 1.2 event logs cannot be combined")
}
outBuff := make([]byte, len(base))
copy(outBuff, base)
out := bytes.NewBuffer(outBuff)
for i, l := range additional {
log, err := ParseEventLog(l)
if err != nil {
return nil, fmt.Errorf("log %d: %v", i, err)
}
if log.specIDEvent == nil {
return nil, fmt.Errorf("log %d: cannot use tpm 1.2 event log as a source", i)
}
algCheck:
for _, alg := range log.specIDEvent.algs {
for _, baseAlg := range baseLog.specIDEvent.algs {
if baseAlg == alg {
continue algCheck
}
}
return nil, fmt.Errorf("log %d: cannot use digest (%+v) not present in base log. Base log has digests: %+v", i, alg, baseLog.specIDEvent.algs)
}
for x, e := range log.rawEvents {
// Serialize header (PCR index, event type, number of digests)
binary.Write(out, binary.LittleEndian, rawEvent2Header{
PCRIndex: uint32(e.index),
Type: uint32(e.typ),
})
binary.Write(out, binary.LittleEndian, uint32(len(e.digests)))
// Serialize digests
for _, d := range e.digests {
var algID uint16
switch d.hash {
case crypto.SHA256:
algID = uint16(HashSHA256)
case crypto.SHA1:
algID = uint16(HashSHA1)
default:
return nil, fmt.Errorf("log %d: event %d: unhandled hash function %v", i, x, d.hash)
}
binary.Write(out, binary.LittleEndian, algID)
out.Write(d.data)
}
// Serialize event data
binary.Write(out, binary.LittleEndian, uint32(len(e.data)))
out.Write(e.data)
}
}
return out.Bytes(), nil
}
// 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.
//go:build gofuzz
// +build gofuzz
package attest
// FuzzParseEventLog is an exported entrypoint for fuzzers to test the eventlog
// parser. This method should not be used for any other purpose.
func FuzzParseEventLog(data []byte) int {
_, err := ParseEventLog(data)
if err != nil {
return 0
}
return 1
}
// Copyright 2020 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 "fmt"
type elWorkaround struct {
id string
affectedPCR int
apply func(e *EventLog) error
}
// inject3 appends two new events into the event log.
func inject3(e *EventLog, pcr int, data1, data2, data3 string) error {
if err := inject(e, pcr, data1); err != nil {
return err
}
if err := inject(e, pcr, data2); err != nil {
return err
}
return inject(e, pcr, data3)
}
// inject2 appends two new events into the event log.
func inject2(e *EventLog, pcr int, data1, data2 string) error {
if err := inject(e, pcr, data1); err != nil {
return err
}
return inject(e, pcr, data2)
}
// inject appends a new event into the event log.
func inject(e *EventLog, pcr int, data string) error {
evt := rawEvent{
data: []byte(data),
index: pcr,
sequence: e.rawEvents[len(e.rawEvents)-1].sequence + 1,
}
for _, alg := range e.Algs {
hash, err := alg.cryptoHash()
if err != nil {
return fmt.Errorf("unknown algorithm ID %x: %v", alg, err)
}
h := hash.New()
h.Write([]byte(data))
evt.digests = append(evt.digests, digest{hash: hash, data: h.Sum(nil)})
}
e.rawEvents = append(e.rawEvents, evt)
return nil
}
const (
ebsInvocation = "Exit Boot Services Invocation"
ebsSuccess = "Exit Boot Services Returned with Success"
ebsFailure = "Exit Boot Services Returned with Failure"
)
var eventlogWorkarounds = []elWorkaround{
{
id: "EBS Invocation + Success",
affectedPCR: 5,
apply: func(e *EventLog) error {
return inject2(e, 5, ebsInvocation, ebsSuccess)
},
},
{
id: "EBS Invocation + Failure",
affectedPCR: 5,
apply: func(e *EventLog) error {
return inject2(e, 5, ebsInvocation, ebsFailure)
},
},
{
id: "EBS Invocation + Failure + Success",
affectedPCR: 5,
apply: func(e *EventLog) error {
return inject3(e, 5, ebsInvocation, ebsFailure, ebsSuccess)
},
},
}
// Package internal contains internal structures and functions for parsing
// TCG event logs.
package internal
import (
"bytes"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"io"
"unicode/utf16"
)
const (
// maxNameLen is the maximum accepted byte length for a name field.
// This value should be larger than any reasonable value.
maxNameLen = 2048
// maxDataLen is the maximum size in bytes of a variable data field.
// This value should be larger than any reasonable value.
maxDataLen = 1024 * 1024 // 1 Megabyte.
)
// GUIDs representing the contents of an UEFI_SIGNATURE_LIST.
var (
hashSHA256SigGUID = efiGUID{0xc1c41626, 0x504c, 0x4092, [8]byte{0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28}}
hashSHA1SigGUID = efiGUID{0x826ca512, 0xcf10, 0x4ac9, [8]byte{0xb1, 0x87, 0xbe, 0x01, 0x49, 0x66, 0x31, 0xbd}}
hashSHA224SigGUID = efiGUID{0x0b6e5233, 0xa65c, 0x44c9, [8]byte{0x94, 0x07, 0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd}}
hashSHA384SigGUID = efiGUID{0xff3e5307, 0x9fd0, 0x48c9, [8]byte{0x85, 0xf1, 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x01}}
hashSHA512SigGUID = efiGUID{0x093e0fae, 0xa6c4, 0x4f50, [8]byte{0x9f, 0x1b, 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a}}
keyRSA2048SigGUID = efiGUID{0x3c5766e8, 0x269c, 0x4e34, [8]byte{0xaa, 0x14, 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6}}
certRSA2048SHA256SigGUID = efiGUID{0xe2b36190, 0x879b, 0x4a3d, [8]byte{0xad, 0x8d, 0xf2, 0xe7, 0xbb, 0xa3, 0x27, 0x84}}
certRSA2048SHA1SigGUID = efiGUID{0x67f8444f, 0x8743, 0x48f1, [8]byte{0xa3, 0x28, 0x1e, 0xaa, 0xb8, 0x73, 0x60, 0x80}}
certX509SigGUID = efiGUID{0xa5c059a1, 0x94e4, 0x4aa7, [8]byte{0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72}}
certHashSHA256SigGUID = efiGUID{0x3bd2a492, 0x96c0, 0x4079, [8]byte{0xb4, 0x20, 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed}}
certHashSHA384SigGUID = efiGUID{0x7076876e, 0x80c2, 0x4ee6, [8]byte{0xaa, 0xd2, 0x28, 0xb3, 0x49, 0xa6, 0x86, 0x5b}}
certHashSHA512SigGUID = efiGUID{0x446dbf63, 0x2502, 0x4cda, [8]byte{0xbc, 0xfa, 0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d}}
)
var (
// https://github.com/rhboot/shim/blob/20e4d9486fcae54ee44d2323ae342ffe68c920e6/lib/guid.c#L36
// GUID used by the shim.
shimLockGUID = efiGUID{0x605dab50, 0xe046, 0x4300, [8]byte{0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23}}
// "SbatLevel" encoded as UCS-2.
shimSbatVarName = []uint16{0x53, 0x62, 0x61, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c}
// "MokListTrusted" encoded as UCS-2.
shimMokListTrustedVarName = []uint16{0x4d, 0x6f, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64}
)
// EventType describes the type of event signalled in the event log.
type EventType uint32
// BIOS Events (TCG PC Client Specific Implementation Specification for Conventional BIOS 1.21)
const (
PrebootCert EventType = 0x00000000
PostCode EventType = 0x00000001
unused EventType = 0x00000002
NoAction EventType = 0x00000003
Separator EventType = 0x00000004
Action EventType = 0x00000005
EventTag EventType = 0x00000006
SCRTMContents EventType = 0x00000007
SCRTMVersion EventType = 0x00000008
CPUMicrocode EventType = 0x00000009
PlatformConfigFlags EventType = 0x0000000A
TableOfDevices EventType = 0x0000000B
CompactHash EventType = 0x0000000C
Ipl EventType = 0x0000000D
IplPartitionData EventType = 0x0000000E
NonhostCode EventType = 0x0000000F
NonhostConfig EventType = 0x00000010
NonhostInfo EventType = 0x00000011
OmitBootDeviceEvents EventType = 0x00000012
)
// EFI Events (TCG EFI Platform Specification Version 1.22)
const (
EFIEventBase EventType = 0x80000000
EFIVariableDriverConfig EventType = 0x80000001
EFIVariableBoot EventType = 0x80000002
EFIBootServicesApplication EventType = 0x80000003
EFIBootServicesDriver EventType = 0x80000004
EFIRuntimeServicesDriver EventType = 0x80000005
EFIGPTEvent EventType = 0x80000006
EFIAction EventType = 0x80000007
EFIPlatformFirmwareBlob EventType = 0x80000008
EFIHandoffTables EventType = 0x80000009
EFIHCRTMEvent EventType = 0x80000010
EFIVariableAuthority EventType = 0x800000e0
)
// EFIDeviceType describes the type of a device specified by a device path.
type EFIDeviceType uint8
// "Device Path Protocol" type values.
//
// Section 9.3.2 of the UEFI specification, accessible at:
// https://uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
const (
HardwareDevice EFIDeviceType = 0x01
ACPIDevice EFIDeviceType = 0x02
MessagingDevice EFIDeviceType = 0x03
MediaDevice EFIDeviceType = 0x04
BBSDevice EFIDeviceType = 0x05
EndDeviceArrayMarker EFIDeviceType = 0x7f
)
// ErrSigMissingGUID is returned if an EFI_SIGNATURE_DATA structure was parsed
// successfully, however was missing the SignatureOwner GUID. This case is
// handled specially as a workaround for a bug relating to authority events.
var ErrSigMissingGUID = errors.New("signature data was missing owner GUID")
var eventTypeNames = map[EventType]string{
PrebootCert: "Preboot Cert",
PostCode: "POST Code",
unused: "Unused",
NoAction: "No Action",
Separator: "Separator",
Action: "Action",
EventTag: "Event Tag",
SCRTMContents: "S-CRTM Contents",
SCRTMVersion: "S-CRTM Version",
CPUMicrocode: "CPU Microcode",
PlatformConfigFlags: "Platform Config Flags",
TableOfDevices: "Table of Devices",
CompactHash: "Compact Hash",
Ipl: "IPL",
IplPartitionData: "IPL Partition Data",
NonhostCode: "Non-Host Code",
NonhostConfig: "Non-HostConfig",
NonhostInfo: "Non-Host Info",
OmitBootDeviceEvents: "Omit Boot Device Events",
EFIEventBase: "EFI Event Base",
EFIVariableDriverConfig: "EFI Variable Driver Config",
EFIVariableBoot: "EFI Variable Boot",
EFIBootServicesApplication: "EFI Boot Services Application",
EFIBootServicesDriver: "EFI Boot Services Driver",
EFIRuntimeServicesDriver: "EFI Runtime Services Driver",
EFIGPTEvent: "EFI GPT Event",
EFIAction: "EFI Action",
EFIPlatformFirmwareBlob: "EFI Platform Firmware Blob",
EFIVariableAuthority: "EFI Variable Authority",
EFIHandoffTables: "EFI Handoff Tables",
EFIHCRTMEvent: "EFI H-CRTM Event",
}
// TaggedEventData represents the TCG_PCClientTaggedEventStruct structure,
// as defined by 11.3.2.1 in the "TCG PC Client Specific Implementation
// Specification for Conventional BIOS", version 1.21.
type TaggedEventData struct {
ID uint32
Data []byte
}
// ParseTaggedEventData parses a TCG_PCClientTaggedEventStruct structure.
func ParseTaggedEventData(d []byte) (*TaggedEventData, error) {
var (
r = bytes.NewReader(d)
header struct {
ID uint32
DataLen uint32
}
)
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, fmt.Errorf("reading header: %w", err)
}
if int(header.DataLen) > len(d) {
return nil, fmt.Errorf("tagged event len (%d bytes) larger than data length (%d bytes)", header.DataLen, len(d))
}
out := TaggedEventData{
ID: header.ID,
Data: make([]byte, header.DataLen),
}
return &out, binary.Read(r, binary.LittleEndian, &out.Data)
}
func (e EventType) String() string {
if s, ok := eventTypeNames[e]; ok {
return s
}
return fmt.Sprintf("EventType(0x%x)", uint32(e))
}
// UntrustedParseEventType returns the event type indicated by
// the provided value.
func UntrustedParseEventType(et uint32) (EventType, error) {
// "The value associated with a UEFI specific platform event type MUST be in
// the range between 0x80000000 and 0x800000FF, inclusive."
if (et < 0x80000000 && et > 0x800000FF) || (et <= 0x0 && et > 0x12) {
return EventType(0), fmt.Errorf("event type not between [0x0, 0x12] or [0x80000000, 0x800000FF]: got %#x", et)
}
if _, ok := eventTypeNames[EventType(et)]; !ok {
return EventType(0), fmt.Errorf("unknown event type %#x", et)
}
return EventType(et), nil
}
// efiGUID represents the EFI_GUID type.
// See section "2.3.1 Data Types" in the specification for more information.
// type efiGUID [16]byte
type efiGUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
func (d efiGUID) String() string {
var u [8]byte
binary.BigEndian.PutUint32(u[:4], d.Data1)
binary.BigEndian.PutUint16(u[4:6], d.Data2)
binary.BigEndian.PutUint16(u[6:8], d.Data3)
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], d.Data4[:2], d.Data4[2:])
}
// UEFIVariableDataHeader represents the leading fixed-size fields
// within UEFI_VARIABLE_DATA.
type UEFIVariableDataHeader struct {
VariableName efiGUID
UnicodeNameLength uint64 // uintN
VariableDataLength uint64 // uintN
}
// UEFIVariableData represents the UEFI_VARIABLE_DATA structure.
type UEFIVariableData struct {
Header UEFIVariableDataHeader
UnicodeName []uint16
VariableData []byte // []int8
}
// ParseUEFIVariableData parses the data section of an event structured as
// a UEFI variable.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100
func ParseUEFIVariableData(r io.Reader) (ret UEFIVariableData, err error) {
err = binary.Read(r, binary.LittleEndian, &ret.Header)
if err != nil {
return
}
if ret.Header.UnicodeNameLength > maxNameLen {
return UEFIVariableData{}, fmt.Errorf("unicode name too long: %d > %d", ret.Header.UnicodeNameLength, maxNameLen)
}
ret.UnicodeName = make([]uint16, ret.Header.UnicodeNameLength)
for i := 0; uint64(i) < ret.Header.UnicodeNameLength; i++ {
err = binary.Read(r, binary.LittleEndian, &ret.UnicodeName[i])
if err != nil {
return
}
}
if ret.Header.VariableDataLength > maxDataLen {
return UEFIVariableData{}, fmt.Errorf("variable data too long: %d > %d", ret.Header.VariableDataLength, maxDataLen)
}
ret.VariableData = make([]byte, ret.Header.VariableDataLength)
_, err = io.ReadFull(r, ret.VariableData)
return
}
// VarName returns the variable name from the UEFI variable data.
func (v *UEFIVariableData) VarName() string {
return string(utf16.Decode(v.UnicodeName))
}
// SignatureData returns the signature data from the UEFI variable data.
func (v *UEFIVariableData) SignatureData() (certs []x509.Certificate, hashes [][]byte, err error) {
return parseEfiSignatureList(v.VariableData)
}
// UEFIVariableAuthority describes the contents of a UEFI variable authority
// event.
type UEFIVariableAuthority struct {
Certs []x509.Certificate
}
// ParseUEFIVariableAuthority parses the data section of an event structured as
// a UEFI variable authority.
//
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789
func ParseUEFIVariableAuthority(v UEFIVariableData) (UEFIVariableAuthority, error) {
if v.Header.VariableName == shimLockGUID && (
// Skip parsing new SBAT section logged by shim.
// See https://github.com/rhboot/shim/blob/main/SBAT.md for more.
unicodeNameEquals(v, shimSbatVarName) || // https://github.com/rhboot/shim/blob/20e4d9486fcae54ee44d2323ae342ffe68c920e6/include/sbat.h#L9-L12
// Skip parsing new MokListTrusted section logged by shim.
// See https://github.com/rhboot/shim/blob/main/MokVars.txt for more.
unicodeNameEquals(v, shimMokListTrustedVarName)) { // https://github.com/rhboot/shim/blob/4e513405b4f1641710115780d19dcec130c5208f/mok.c#L169-L182
return UEFIVariableAuthority{}, nil
}
certs, err := parseEfiSignature(v.VariableData)
return UEFIVariableAuthority{Certs: certs}, err
}
func unicodeNameEquals(v UEFIVariableData, comp []uint16) bool {
if len(v.UnicodeName) != len(comp) {
return false
}
for i, v := range v.UnicodeName {
if v != comp[i] {
return false
}
}
return true
}
// efiSignatureData represents the EFI_SIGNATURE_DATA type.
// See section "31.4.1 Signature Database" in the specification for more information.
type efiSignatureData struct {
SignatureOwner efiGUID
SignatureData []byte // []int8
}
// efiSignatureList represents the EFI_SIGNATURE_LIST type.
// See section "31.4.1 Signature Database" in the specification for more information.
type efiSignatureListHeader struct {
SignatureType efiGUID
SignatureListSize uint32
SignatureHeaderSize uint32
SignatureSize uint32
}
type efiSignatureList struct {
Header efiSignatureListHeader
SignatureData []byte
Signatures []byte
}
// parseEfiSignatureList parses a EFI_SIGNATURE_LIST structure.
// The structure and related GUIDs are defined at:
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1790
func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
if len(b) < 28 {
// Being passed an empty signature list here appears to be valid
return nil, nil, nil
}
signatures := efiSignatureList{}
buf := bytes.NewReader(b)
var certificates []x509.Certificate
var hashes [][]byte
for buf.Len() > 0 {
err := binary.Read(buf, binary.LittleEndian, &signatures.Header)
if err != nil {
return nil, nil, err
}
if signatures.Header.SignatureHeaderSize > maxDataLen {
return nil, nil, fmt.Errorf("signature header too large: %d > %d", signatures.Header.SignatureHeaderSize, maxDataLen)
}
if signatures.Header.SignatureListSize > maxDataLen {
return nil, nil, fmt.Errorf("signature list too large: %d > %d", signatures.Header.SignatureListSize, maxDataLen)
}
signatureType := signatures.Header.SignatureType
switch signatureType {
case certX509SigGUID: // X509 certificate
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
signature := efiSignatureData{}
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
if err != nil {
return nil, nil, err
}
err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(signature.SignatureData)
if err != nil {
return nil, nil, err
}
sigOffset += int(signatures.Header.SignatureSize)
certificates = append(certificates, *cert)
}
case hashSHA256SigGUID: // SHA256
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
signature := efiSignatureData{}
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
if err != nil {
return nil, nil, err
}
err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
if err != nil {
return nil, nil, err
}
hashes = append(hashes, signature.SignatureData)
sigOffset += int(signatures.Header.SignatureSize)
}
case keyRSA2048SigGUID:
err = errors.New("unhandled RSA2048 key")
case certRSA2048SHA256SigGUID:
err = errors.New("unhandled RSA2048-SHA256 key")
case hashSHA1SigGUID:
err = errors.New("unhandled SHA1 hash")
case certRSA2048SHA1SigGUID:
err = errors.New("unhandled RSA2048-SHA1 key")
case hashSHA224SigGUID:
err = errors.New("unhandled SHA224 hash")
case hashSHA384SigGUID:
err = errors.New("unhandled SHA384 hash")
case hashSHA512SigGUID:
err = errors.New("unhandled SHA512 hash")
case certHashSHA256SigGUID:
err = errors.New("unhandled X509-SHA256 hash metadata")
case certHashSHA384SigGUID:
err = errors.New("unhandled X509-SHA384 hash metadata")
case certHashSHA512SigGUID:
err = errors.New("unhandled X509-SHA512 hash metadata")
default:
err = fmt.Errorf("unhandled signature type %s", signatureType)
}
if err != nil {
return nil, nil, err
}
}
return certificates, hashes, nil
}
// EFISignatureData represents the EFI_SIGNATURE_DATA type.
// See section "31.4.1 Signature Database" in the specification
// for more information.
type EFISignatureData struct {
SignatureOwner efiGUID
SignatureData []byte // []int8
}
func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
var certificates []x509.Certificate
if len(b) < 16 {
return nil, fmt.Errorf("invalid signature: buffer smaller than header (%d < %d)", len(b), 16)
}
buf := bytes.NewReader(b)
signature := EFISignatureData{}
signature.SignatureData = make([]byte, len(b)-16)
if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner); err != nil {
return certificates, err
}
if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureData); err != nil {
return certificates, err
}
cert, err := x509.ParseCertificate(signature.SignatureData)
if err == nil {
certificates = append(certificates, *cert)
} else {
// A bug in shim may cause an event to be missing the SignatureOwner GUID.
// We handle this, but signal back to the caller using ErrSigMissingGUID.
var err2 error
cert, err2 = x509.ParseCertificate(b)
if err2 == nil {
certificates = append(certificates, *cert)
err = ErrSigMissingGUID
}
}
return certificates, err
}
// EFIDevicePathElement represents an EFI_DEVICE_PATH_ELEMENT structure.
type EFIDevicePathElement struct {
Type EFIDeviceType
Subtype uint8
Data []byte
}
// EFIImageLoad describes an EFI_IMAGE_LOAD_EVENT structure.
type EFIImageLoad struct {
Header EFIImageLoadHeader
DevPathData []byte
}
// EFIImageLoadHeader represents the EFI_IMAGE_LOAD_EVENT structure.
type EFIImageLoadHeader struct {
LoadAddr uint64
Length uint64
LinkAddr uint64
DevicePathLen uint64
}
func parseDevicePathElement(r io.Reader) (EFIDevicePathElement, error) {
var (
out EFIDevicePathElement
dataLen uint16
)
if err := binary.Read(r, binary.LittleEndian, &out.Type); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading type: %v", err)
}
if err := binary.Read(r, binary.LittleEndian, &out.Subtype); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading subtype: %v", err)
}
if err := binary.Read(r, binary.LittleEndian, &dataLen); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading data len: %v", err)
}
if dataLen > maxNameLen {
return EFIDevicePathElement{}, fmt.Errorf("device path data too long: %d > %d", dataLen, maxNameLen)
}
if dataLen < 4 {
return EFIDevicePathElement{}, fmt.Errorf("device path data too short: %d < %d", dataLen, 4)
}
out.Data = make([]byte, dataLen-4)
if err := binary.Read(r, binary.LittleEndian, &out.Data); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading data: %v", err)
}
return out, nil
}
// DevicePath returns the device path elements from the EFI_IMAGE_LOAD_EVENT structure.
func (h *EFIImageLoad) DevicePath() ([]EFIDevicePathElement, error) {
var (
r = bytes.NewReader(h.DevPathData)
out []EFIDevicePathElement
)
for r.Len() > 0 {
e, err := parseDevicePathElement(r)
if err != nil {
return nil, err
}
if e.Type == EndDeviceArrayMarker {
return out, nil
}
out = append(out, e)
}
return out, nil
}
// ParseEFIImageLoad parses an EFI_IMAGE_LOAD_EVENT structure.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf#page=17
func ParseEFIImageLoad(r io.Reader) (ret EFIImageLoad, err error) {
err = binary.Read(r, binary.LittleEndian, &ret.Header)
if err != nil {
return
}
if ret.Header.DevicePathLen > maxNameLen {
return EFIImageLoad{}, fmt.Errorf("device path structure too long: %d > %d", ret.Header.DevicePathLen, maxNameLen)
}
ret.DevPathData = make([]byte, ret.Header.DevicePathLen)
err = binary.Read(r, binary.LittleEndian, &ret.DevPathData)
return
}
// Copyright 2020 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/x509"
"errors"
"fmt"
"github.com/google/go-attestation/attest/internal"
)
// SecurebootState describes the secure boot status of a machine, as determined
// by processing its event log.
type SecurebootState struct {
Enabled bool
// PlatformKeys enumerates keys which can sign a key exchange key.
PlatformKeys []x509.Certificate
// PlatformKeys enumerates key hashes which can sign a key exchange key.
PlatformKeyHashes [][]byte
// ExchangeKeys enumerates keys which can sign a database of permitted or
// forbidden keys.
ExchangeKeys []x509.Certificate
// ExchangeKeyHashes enumerates key hashes which can sign a database or
// permitted or forbidden keys.
ExchangeKeyHashes [][]byte
// PermittedKeys enumerates keys which may sign binaries to run.
PermittedKeys []x509.Certificate
// PermittedHashes enumerates hashes which permit binaries to run.
PermittedHashes [][]byte
// ForbiddenKeys enumerates keys which must not permit a binary to run.
ForbiddenKeys []x509.Certificate
// ForbiddenKeys enumerates hashes which must not permit a binary to run.
ForbiddenHashes [][]byte
// PreSeparatorAuthority describes the use of a secure-boot key to authorize
// the execution of a binary before the separator.
PreSeparatorAuthority []x509.Certificate
// PostSeparatorAuthority describes the use of a secure-boot key to authorize
// the execution of a binary after the separator.
PostSeparatorAuthority []x509.Certificate
// DriverLoadSourceHints describes the origin of boot services drivers.
// This data is not tamper-proof and must only be used as a hint.
DriverLoadSourceHints []DriverLoadSource
// DMAProtectionDisabled is true if the platform reports during boot that
// DMA protection is supported but disabled.
//
// See: https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-kernel-dma-protection
DMAProtectionDisabled bool
}
// DriverLoadSource describes the logical origin of a boot services driver.
type DriverLoadSource uint8
const (
// UnknownSource indicates that the driver load source is unknown.
UnknownSource DriverLoadSource = iota
// PciMmioSource indicates that the driver load source is PCI-MMIO.
PciMmioSource
)
// ParseSecurebootState parses a series of events to determine the
// configuration of secure boot on a device. An error is returned if
// the state cannot be determined, or if the event log is structured
// in such a way that it may have been tampered post-execution of
// platform firmware.
func ParseSecurebootState(events []Event) (*SecurebootState, error) {
// This algorithm verifies the following:
// - All events in PCR 7 have event types which are expected in PCR 7.
// - All events are parsable according to their event type.
// - All events have digests values corresponding to their data/event type.
// - No unverifiable events were present.
// - All variables are specified before the separator and never duplicated.
// - The SecureBoot variable has a value of 0 or 1.
// - If SecureBoot was 1 (enabled), authority events were present indicating
// keys were used to perform verification.
// - If SecureBoot was 1 (enabled), platform + exchange + database keys
// were specified.
// - No UEFI debugger was attached.
var (
out SecurebootState
seenSeparator7 bool
seenSeparator2 bool
seenAuthority bool
seenVars = map[string]bool{}
driverSources [][]internal.EFIDevicePathElement
)
for _, e := range events {
if e.Index != 7 && e.Index != 2 {
continue
}
et, err := internal.UntrustedParseEventType(uint32(e.Type))
if err != nil {
return nil, fmt.Errorf("unrecognised event type: %v", err)
}
digestVerify := e.digestEquals(e.Data)
switch e.Index {
case 7:
switch et {
case internal.Separator:
if seenSeparator7 {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
}
seenSeparator7 = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
case internal.EFIAction:
switch string(e.Data) {
case "UEFI Debug Mode":
return nil, errors.New("a UEFI debugger was present during boot")
case "DMA Protection Disabled":
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.sequence, digestVerify)
}
out.DMAProtectionDisabled = true
default:
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
}
case internal.EFIVariableDriverConfig:
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
}
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
}
seenVars[v.VarName()] = true
if seenSeparator7 {
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
}
switch v.VarName() {
case "SecureBoot":
if len(v.VariableData) != 1 {
return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
}
out.Enabled = v.VariableData[0] == 1
case "PK":
if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
}
case "KEK":
if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
}
case "db":
if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
}
case "dbx":
if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
}
}
case internal.EFIVariableAuthority:
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing UEFI variable data: %v", err)
}
a, err := internal.ParseUEFIVariableAuthority(v)
if err != nil {
// Workaround for: https://github.com/google/go-attestation/issues/157
if err == internal.ErrSigMissingGUID {
// Versions of shim which do not carry
// https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50
// have an erroneous additional byte in the event, which breaks digest
// verification. If verification failed, we try removing the last byte.
if digestVerify != nil && len(e.Data) > 0 {
digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
}
} else {
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
}
}
seenAuthority = true
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
}
if !seenSeparator7 {
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
} else {
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
}
default:
return nil, fmt.Errorf("unexpected event type in PCR7: %v", et)
}
case 2:
switch et {
case internal.Separator:
if seenSeparator2 {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
}
seenSeparator2 = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
case internal.EFIBootServicesDriver:
if !seenSeparator2 {
imgLoad, err := internal.ParseEFIImageLoad(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.sequence, err)
}
dp, err := imgLoad.DevicePath()
if err != nil {
return nil, fmt.Errorf("failed to parse device path for driver load event %d: %v", e.sequence, err)
}
driverSources = append(driverSources, dp)
}
}
}
}
// Compute driver source hints based on the EFI device path observed in
// EFI Boot-services driver-load events.
sourceLoop:
for _, source := range driverSources {
// We consider a driver to have originated from PCI-MMIO if any number
// of elements in the device path [1] were PCI devices, and are followed by
// an element representing a "relative offset range" read.
// In the wild, we have typically observed 4-tuple device paths for such
// devices: ACPI device -> PCI device -> PCI device -> relative offset.
//
// [1]: See section 9 of the UEFI specification v2.6 or greater.
var seenPCI bool
for _, e := range source {
// subtype 0x1 corresponds to a PCI device (See: 9.3.2.1)
if e.Type == internal.HardwareDevice && e.Subtype == 0x1 {
seenPCI = true
}
// subtype 0x8 corresponds to "relative offset range" (See: 9.3.6.8)
if seenPCI && e.Type == internal.MediaDevice && e.Subtype == 0x8 {
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, PciMmioSource)
continue sourceLoop
}
}
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, UnknownSource)
}
if !out.Enabled {
return &out, nil
}
if !seenAuthority {
return nil, errors.New("secure boot was enabled but no key was used")
}
if len(out.PlatformKeys) == 0 && len(out.PlatformKeyHashes) == 0 {
return nil, errors.New("secure boot was enabled but no platform keys were known")
}
if len(out.ExchangeKeys) == 0 && len(out.ExchangeKeyHashes) == 0 {
return nil, errors.New("secure boot was enabled but no key exchange keys were known")
}
if len(out.PermittedKeys) == 0 && len(out.PermittedHashes) == 0 {
return nil, errors.New("secure boot was enabled but no keys or hashes were permitted")
}
return &out, nil
}
// 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 (
"encoding/json"
"fmt"
)
// serializedKey represents a loadable, TPM-backed key.
type serializedKey struct {
// Encoding describes the strategy by which the key should be
// loaded/unloaded.
Encoding keyEncoding `json:"KeyEncoding"`
// Unused and retained for backwards compatibility with existing keys.
TPMVersion uint8
// Public represents the public key, in a TPM-specific format. This
// field is populated on all platforms and TPM versions.
Public []byte
// The following fields are only valid for TPM 2.0 hardware, holding
// information returned as the result to a TPM2_CertifyCreation command.
// These are stored alongside the key for later use, as the certification
// can only be obtained immediately after the key is generated.
CreateData []byte
CreateAttestation []byte
CreateSignature []byte
// Name is only valid for KeyEncodingOSManaged, which is only used
// on Windows.
Name string
// Blob represents the key material for KeyEncodingEncrypted keys. This
// is only used on Linux.
Blob []byte `json:"KeyBlob"`
}
// Serialize represents the key in a persistent format which may be
// loaded at a later time using deserializeKey().
func (k *serializedKey) Serialize() ([]byte, error) {
return json.Marshal(k)
}
func deserializeKey(b []byte) (*serializedKey, error) {
var k serializedKey
var err error
if err = json.Unmarshal(b, &k); err != nil {
return nil, fmt.Errorf("json.Unmarshal() failed: %v", err)
}
if k.TPMVersion != 2 {
return nil, fmt.Errorf("key for different TPM version: %v", k.TPMVersion)
}
return &k, nil
}
// 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/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
"net/url"
"strings"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
"go.uber.org/multierr"
)
const (
tpmPtManufacturer = 0x00000100 + 5 // PT_FIXED + offset of 5
tpmPtVendorString = 0x00000100 + 6 // PT_FIXED + offset of 6
tpmPtFwVersion1 = 0x00000100 + 11 // PT_FIXED + offset of 11
// Defined in "Registry of reserved TPM 2.0 handles and localities".
nvramRSACertIndex = 0x1c00002
nvramRSAEkNonceIndex = 0x1c00003
nvramECCCertIndex = 0x1c0000a
nvramECCEkNonceIndex = 0x1c0000b
// Defined in "Registry of reserved TPM 2.0 handles and localities", and checked on a glinux machine.
commonRSAEkEquivalentHandle = 0x81010001
commonECCEkEquivalentHandle = 0x81010002
)
var (
akTemplateRSA = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA,
RSAParameters: &tpm2.RSAParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: tpm2.AlgSHA256,
},
KeyBits: 2048,
},
}
akTemplateECC = tpm2.Public{
Type: tpm2.AlgECC,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA,
ECCParameters: &tpm2.ECCParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
Hash: tpm2.AlgSHA256,
},
CurveID: tpm2.CurveNISTP256,
Point: tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
},
},
}
defaultRSASRKTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA,
RSAParameters: &tpm2.RSAParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB,
},
ModulusRaw: make([]byte, 256),
KeyBits: 2048,
},
}
defaultECCSRKTemplate = tpm2.Public{
Type: tpm2.AlgECC,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA,
ECCParameters: &tpm2.ECCParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB,
},
CurveID: tpm2.CurveNISTP256,
Point: tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
},
},
}
// Default RSA and ECC EK templates defined in:
// https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
defaultRSAEKTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt,
AuthPolicy: []byte{
0x83, 0x71, 0x97, 0x67, 0x44, 0x84,
0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D,
0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52,
0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64,
0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14,
0x69, 0xAA,
},
RSAParameters: &tpm2.RSAParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB,
},
KeyBits: 2048,
ModulusRaw: make([]byte, 256),
},
}
defaultECCEKTemplate = tpm2.Public{
Type: tpm2.AlgECC,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt,
AuthPolicy: []byte{
0x83, 0x71, 0x97, 0x67, 0x44, 0x84,
0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D,
0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52,
0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64,
0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14,
0x69, 0xAA,
},
ECCParameters: &tpm2.ECCParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB,
},
CurveID: tpm2.CurveNISTP256,
Point: tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
},
},
}
// Basic template for an ECDSA key signing outside-TPM objects. Other
// fields are populated depending on the key creation options.
ecdsaKeyTemplate = tpm2.Public{
Type: tpm2.AlgECC,
Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted,
ECCParameters: &tpm2.ECCParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
},
},
}
// Basic template for an RSA key signing outside-TPM objects. Other
// fields are populated depending on the key creation options.
rsaKeyTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted | tpm2.FlagDecrypt,
RSAParameters: &tpm2.RSAParams{},
}
)
type tpmInfo struct {
vendor string
manufacturer TCGVendorID
fwMajor int
fwMinor int
}
func readVendorAttributes(tpm io.ReadWriter) (tpmInfo, error) {
var vendorInfo string
// The Vendor String is split up into 4 sections of 4 bytes,
// for a maximum length of 16 octets of ASCII text. We iterate
// through the 4 indexes to get all 16 bytes & construct vendorInfo.
// See: TPM_PT_VENDOR_STRING_1 in TPM 2.0 Structures reference.
for i := 0; i < 4; i++ {
caps, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityTPMProperties, 1, tpmPtVendorString+uint32(i))
if err != nil {
return tpmInfo{}, fmt.Errorf("tpm2.GetCapability(PT_VENDOR_STRING_%d) failed: %v", i+1, err)
}
subset, ok := caps[0].(tpm2.TaggedProperty)
if !ok {
return tpmInfo{}, fmt.Errorf("got capability of type %T, want tpm2.TaggedProperty", caps[0])
}
// Reconstruct the 4 ASCII octets from the uint32 value.
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, subset.Value)
vendorInfo += string(b)
}
caps, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityTPMProperties, 1, tpmPtManufacturer)
if err != nil {
return tpmInfo{}, fmt.Errorf("tpm2.GetCapability(PT_MANUFACTURER) failed: %v", err)
}
manu, ok := caps[0].(tpm2.TaggedProperty)
if !ok {
return tpmInfo{}, fmt.Errorf("got capability of type %T, want tpm2.TaggedProperty", caps[0])
}
caps, _, err = tpm2.GetCapability(tpm, tpm2.CapabilityTPMProperties, 1, tpmPtFwVersion1)
if err != nil {
return tpmInfo{}, fmt.Errorf("tpm2.GetCapability(PT_FIRMWARE_VERSION_1) failed: %v", err)
}
fw, ok := caps[0].(tpm2.TaggedProperty)
if !ok {
return tpmInfo{}, fmt.Errorf("got capability of type %T, want tpm2.TaggedProperty", caps[0])
}
return tpmInfo{
vendor: strings.Trim(vendorInfo, "\x00"),
manufacturer: TCGVendorID(manu.Value),
fwMajor: int((fw.Value & 0xffff0000) >> 16),
fwMinor: int(fw.Value & 0x0000ffff),
}, nil
}
// ParseEKCertificate parses a raw DER encoded EK certificate blob.
func ParseEKCertificate(ekCert []byte) (*x509.Certificate, error) {
var wasWrapped bool
// TCG PC Specific Implementation section 7.3.2 specifies
// a prefix when storing a certificate in NVRAM. We look
// for and unwrap the certificate if its present.
if len(ekCert) > 5 && bytes.Equal(ekCert[:3], []byte{0x10, 0x01, 0x00}) {
certLen := int(binary.BigEndian.Uint16(ekCert[3:5]))
if len(ekCert) < certLen+5 {
return nil, fmt.Errorf("parsing nvram header: ekCert size %d smaller than specified cert length %d", len(ekCert), certLen)
}
ekCert = ekCert[5 : 5+certLen]
wasWrapped = true
}
// If the cert parses fine without any changes, we are G2G.
if c, err := x509.ParseCertificate(ekCert); err == nil {
return c, nil
}
// There might be trailing nonsense in the cert, which Go
// does not parse correctly. As ASN1 data is TLV encoded, we should
// be able to just get the certificate, and then send that to Go's
// certificate parser.
var cert struct {
Raw asn1.RawContent
}
if _, err := asn1.UnmarshalWithParams(ekCert, &cert, "lax"); err != nil {
return nil, fmt.Errorf("asn1.Unmarshal() failed: %v, wasWrapped=%v", err, wasWrapped)
}
c, err := x509.ParseCertificate(cert.Raw)
if err != nil {
return nil, fmt.Errorf("x509.ParseCertificate() failed: %v", err)
}
return c, nil
}
const (
manufacturerIntel = "Intel"
intelEKCertServiceURL = "https://ekop.intel.com/ekcertservice/"
)
func intelEKURL(ekPub *rsa.PublicKey) string {
pubHash := sha256.New()
pubHash.Write(ekPub.N.Bytes())
pubHash.Write([]byte{0x1, 0x00, 0x01})
return intelEKCertServiceURL + url.QueryEscape(base64.URLEncoding.EncodeToString(pubHash.Sum(nil)))
}
const (
manufacturerAMD = "AMD"
amdEKCertServiceURL = "https://ftpm.amd.com/pki/aia/"
)
func amdEKURL(ekPub *rsa.PublicKey) string {
pubHash := sha256.New()
pubHash.Write([]byte{0x00, 0x00, 0x22, 0x22})
expBytes := make([]byte, 4)
binary.BigEndian.PutUint32(expBytes, uint32(ekPub.E))
pubHash.Write(expBytes)
pubHash.Write(ekPub.N.Bytes())
return amdEKCertServiceURL + url.QueryEscape(fmt.Sprintf("%X", pubHash.Sum(nil)[0:16]))
}
func ekCertURL(ekPub *rsa.PublicKey, manufacturer string) string {
var CertURL string
switch manufacturer {
case intelEKCertServiceURL:
CertURL = intelEKURL(ekPub)
case amdEKCertServiceURL:
CertURL = amdEKURL(ekPub)
}
return CertURL
}
func readEKCertFromNVRAM20(tpm io.ReadWriter, nvramCertIndex tpmutil.Handle) (*x509.Certificate, error) {
// By passing nvramCertIndex as our auth handle we're using the NV index
// itself as the auth hierarchy, which is the same approach
// tpm2_getekcertificate takes.
ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, nvramCertIndex, "", 0)
if err != nil {
return nil, fmt.Errorf("reading EK cert: %v", err)
}
return ParseEKCertificate(ekCert)
}
func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte, selectedPCRs []int) (*Quote, error) {
sel := tpm2.PCRSelection{Hash: hashAlg,
PCRs: selectedPCRs}
quote, sig, err := tpm2.Quote(tpm, akHandle, "", "", nonce, sel, tpm2.AlgNull)
if err != nil {
return nil, err
}
rawSig, err := tpmutil.Pack(sig.Alg, sig.RSA.HashAlg, sig.RSA.Signature)
return &Quote{
Quote: quote,
Signature: rawSig,
}, err
}
func pcrbanks(tpm io.ReadWriter) ([]HashAlg, error) {
vals, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityPCRs, 1024, 0)
if err != nil {
return nil, fmt.Errorf("failed to get TPM available PCR banks: %w", err)
}
var hAlgs []HashAlg
var errs error
for i, v := range vals {
pcrb, ok := v.(tpm2.PCRSelection)
if !ok {
errs = multierr.Append(errs, fmt.Errorf("failed to convert value %d to tpm2.PCRSelection: %v", i, v))
continue
}
if len(pcrb.PCRs) == 0 {
// ignore empty PCR banks.
continue
}
hAlgs = append(hAlgs, HashAlg(pcrb.Hash))
}
return hAlgs, errs
}
func readAllPCRs(tpm io.ReadWriter, alg tpm2.Algorithm) (map[uint32][]byte, error) {
numPCRs := 24
out := map[uint32][]byte{}
// The TPM 2.0 spec says that the TPM can partially fulfill the
// request. As such, we repeat the command up to 24 times to get all
// 24 PCRs.
for i := 0; i < numPCRs; i++ {
// Build a selection structure, specifying all PCRs we do
// not have the value for.
sel := tpm2.PCRSelection{Hash: alg}
for pcr := 0; pcr < numPCRs; pcr++ {
if _, present := out[uint32(pcr)]; !present {
sel.PCRs = append(sel.PCRs, pcr)
}
}
// Ask the TPM for those PCR values.
ret, err := tpm2.ReadPCRs(tpm, sel)
if err != nil {
return nil, fmt.Errorf("tpm2.ReadPCRs(%+v) failed with err: %v", sel, err)
}
// Keep track of the PCRs we were actually given.
for pcr, digest := range ret {
out[uint32(pcr)] = digest
}
if len(out) == numPCRs {
break
}
}
if len(out) != numPCRs {
return nil, fmt.Errorf("failed to read all PCRs, only read %d", len(out))
}
return out, nil
}
// tpmBase defines the implementation of a TPM invariant.
type tpmBase interface {
close() error
eks() ([]EK, error)
ekCertificates() ([]EK, error)
info() (*TPMInfo, error)
pcrbanks() ([]HashAlg, error)
loadAK(opaqueBlob []byte) (*AK, error)
loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error)
newAK(opts *AKConfig) (*AK, error)
loadKey(opaqueBlob []byte) (*Key, error)
loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error)
newKey(ak *AK, opts *KeyConfig) (*Key, error)
newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error)
pcrs(alg HashAlg) ([]PCR, error)
measurementLog() ([]byte, error)
}
// TPM interfaces with a TPM device on the system.
type TPM struct {
// tpm refers to a concrete implementation of TPM logic, based on the current
// platform and TPM version.
tpm tpmBase
}
// Close shuts down the connection to the TPM.
func (t *TPM) Close() error {
return t.tpm.close()
}
// EKs returns the endorsement keys burned-in to the platform.
// Note for Linux clients: for historical reasons, the method assumes that
// the TPM has a single EK, and the EK's type is RSA. If the EK's type is ECC
// and the TPM contains an ECC EK Certificate, the EKCertificates() method
// should be used to retrieve the EKs.
func (t *TPM) EKs() ([]EK, error) {
return t.tpm.eks()
}
// EKCertificates returns the endorsement key certificates burned-in to the platform.
// It is guaranteed that each EK.Certificate field will be populated.
func (t *TPM) EKCertificates() ([]EK, error) {
return t.tpm.ekCertificates()
}
// Info returns information about the TPM.
func (t *TPM) Info() (*TPMInfo, error) {
return t.tpm.info()
}
// LoadAK loads a previously-created ak into the TPM for use.
// A key loaded via this function needs to be closed with .Close().
// Only blobs generated by calling AK.Marshal() are valid parameters
// to this function.
func (t *TPM) LoadAK(opaqueBlob []byte) (*AK, error) {
return t.tpm.loadAK(opaqueBlob)
}
// LoadAKWithParent loads a previously-created ak into the TPM
// under the given parent for use.
func (t *TPM) LoadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error) {
return t.tpm.loadAKWithParent(opaqueBlob, parent)
}
// MeasurementLog returns the present value of the System Measurement Log.
//
// This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead.
func (t *TPM) MeasurementLog() ([]byte, error) {
el, err := t.tpm.measurementLog()
if err != nil {
return nil, err
}
// A valid event log contains at least one SpecID event header (28 bytes).
if minValidSize := 28; len(el) < minValidSize {
return nil, fmt.Errorf("event log too short: %d < %d", len(el), minValidSize)
}
return el, nil
}
// NewAK creates an attestation key.
func (t *TPM) NewAK(opts *AKConfig) (*AK, error) {
return t.tpm.newAK(opts)
}
// NewKey creates an application key certified by the attestation key. If opts is nil
// then DefaultConfig is used.
func (t *TPM) NewKey(ak *AK, opts *KeyConfig) (*Key, error) {
if opts == nil {
opts = defaultConfig
}
if opts.Algorithm == "" && opts.Size == 0 {
opts = defaultConfig
}
return t.tpm.newKey(ak, opts)
}
// NewKeyCertifiedByKey creates an application key certified by
// the attestation key. Unlike NewKey(), this method does not require
// an attest.AK object and only requires the AK handle and its algorithm.
// Thus it can be used in cases where the attestation key was not created
// by go-attestation library. If opts is nil then DefaultConfig is used.
func (t *TPM) NewKeyCertifiedByKey(akHandle tpmutil.Handle, akAlg Algorithm, opts *KeyConfig) (*Key, error) {
if opts == nil {
opts = defaultConfig
}
if opts.Algorithm == "" && opts.Size == 0 {
opts = defaultConfig
}
ck := certifyingKey{handle: akHandle, alg: akAlg}
return t.tpm.newKeyCertifiedByKey(ck, opts)
}
// LoadKey loads a previously-created application key into the TPM for use.
// A key loaded via this function needs to be closed with .Close().
// Only blobs generated by calling Key.Marshal() are valid parameters
// to this function.
func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) {
return t.tpm.loadKey(opaqueBlob)
}
// PCRs returns the present value of Platform Configuration Registers with
// the given digest algorithm.
//
// This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead.
func (t *TPM) PCRs(alg HashAlg) ([]PCR, error) {
return t.tpm.pcrs(alg)
}
// PCRBanks returns the list of supported PCR banks on the TPM.
//
// This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead.
func (t *TPM) PCRBanks() ([]HashAlg, error) {
return t.tpm.pcrbanks()
}
func (t *TPM) attestPCRs(ak *AK, nonce []byte, alg HashAlg) (*Quote, []PCR, error) {
pcrs, err := t.PCRs(alg)
if err != nil {
return nil, nil, fmt.Errorf("failed to read %v PCRs: %v", alg, err)
}
quote, err := ak.Quote(t, nonce, alg)
if err != nil {
return nil, nil, fmt.Errorf("failed to quote using %v: %v", alg, err)
}
// Make sure that the pcrs and quote values are consistent. See details in Section 17.6.2 of
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part1_Architecture_pub.pdf
pub, err := ParseAKPublic(ak.AttestationParameters().Public)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse AK public: %v", err)
}
if err := pub.Verify(*quote, pcrs, nonce); err != nil {
return nil, nil, fmt.Errorf("local quote verification failed: %v", err)
}
return quote, pcrs, nil
}
func (t *TPM) attestPlatform(ak *AK, nonce []byte, eventLog []byte) (*PlatformParameters, error) {
out := PlatformParameters{
Public: ak.AttestationParameters().Public,
EventLog: eventLog,
}
algs, err := t.PCRBanks()
if err != nil {
return nil, fmt.Errorf("failed to get PCR banks: %w", err)
}
var lastErr error
for _, alg := range algs {
quote, pcrs, err := t.attestPCRs(ak, nonce, alg)
if err != nil {
lastErr = err
continue
}
out.Quotes = append(out.Quotes, *quote)
out.PCRs = append(out.PCRs, pcrs...)
}
if len(out.Quotes) == 0 {
return nil, lastErr
}
return &out, nil
}
// PlatformAttestConfig configures how attestations are generated through
// tpm.AttestPlatform().
type PlatformAttestConfig struct {
// If non-nil, the raw event log will be read from EventLog
// instead of being obtained from the running system.
EventLog []byte
}
// AttestPlatform computes the set of information necessary to attest the
// state of the platform. For TPM 2.0 devices, AttestPlatform will attempt
// to read all supported PCR banks and quote all of them, so bugs in
// platform firmware which break replay for one PCR bank can be mitigated
// using any other.
// The provided config, if not nil, can be used to configure aspects of the
// platform attestation.
func (t *TPM) AttestPlatform(ak *AK, nonce []byte, config *PlatformAttestConfig) (*PlatformParameters, error) {
if config == nil {
config = &PlatformAttestConfig{}
}
var el []byte
if config.EventLog != nil {
el = config.EventLog
} else {
var err error
if el, err = t.MeasurementLog(); err != nil {
return nil, fmt.Errorf("failed to read event log: %v", err)
}
}
return t.attestPlatform(ak, nonce, el)
}
package attest
import (
"io"
)
var (
testLog = []byte{0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x21, 0x0, 0x0, 0x0, 0x53, 0x70, 0x65, 0x63, 0x20, 0x49, 0x44,
0x20, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x2, 0x0, 0x2, 0x1, 0x0, 0x0, 0x0, 0xb, 0x0, 0x20, 0x0, 0x0}
)
type fakeCmdChannel struct {
io.ReadWriteCloser
}
// MeasurementLog implements CommandChannelTPM20.
func (cc *fakeCmdChannel) MeasurementLog() ([]byte, error) {
return testLog, nil
}
// 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.
//go:build gofuzz || (!linux && !windows)
// +build gofuzz !linux,!windows
package attest
import (
"errors"
)
var errUnsupported = errors.New("tpm operations not supported from given build parameters")
func probeSystemTPMs() ([]string, error) {
return nil, errUnsupported
}
func openTPM(string) (*TPM, error) {
return nil, errUnsupported
}
// 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
// TCGVendorID represents a unique TCG manufacturer code.
// The canonical reference used is located at:
// https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-Vendor-ID-Registry-Version-1.01-Revision-1.00.pdf
type TCGVendorID uint32
var vendors = map[TCGVendorID]string{
1095582720: "AMD",
1096043852: "Atmel",
1112687437: "Broadcom",
1229081856: "IBM",
1213220096: "HPE",
1297303124: "Microsoft",
1229346816: "Infineon",
1229870147: "Intel",
1279610368: "Lenovo",
1314082080: "National Semiconductor",
1314150912: "Nationz",
1314145024: "Nuvoton Technology",
1363365709: "Qualcomm",
1397576515: "SMSC",
1398033696: "ST Microelectronics",
1397576526: "Samsung",
1397641984: "Sinosun",
1415073280: "Texas Instruments",
1464156928: "Winbond",
1380926275: "Fuzhou Rockchip",
1196379975: "Google",
}
func (id TCGVendorID) String() string {
return vendors[id]
}
// Copyright 2020 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"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
"unicode/utf16"
"github.com/google/go-attestation/attest/internal"
)
type windowsEvent uint32
// SIPA event types
const (
sipaTypeMask windowsEvent = 0x000f0000
sipaContainer windowsEvent = 0x00010000
sipaInformation windowsEvent = 0x00020000
sipaError windowsEvent = 0x00030000
sipaPreOsParameter windowsEvent = 0x00040000
sipaOSParameter windowsEvent = 0x00050000
sipaAuthority windowsEvent = 0x00060000
sipaLoadedModule windowsEvent = 0x00070000
sipaTrustPoint windowsEvent = 0x00080000
sipaELAM windowsEvent = 0x00090000
sipaVBS windowsEvent = 0x000a0000
trustBoundary windowsEvent = 0x40010001
elamAggregation windowsEvent = 0x40010002
loadedModuleAggregation windowsEvent = 0x40010003
trustpointAggregation windowsEvent = 0xC0010004
ksrAggregation windowsEvent = 0x40010005
ksrSignedMeasurementAggregation windowsEvent = 0x40010006
information windowsEvent = 0x00020001
bootCounter windowsEvent = 0x00020002
transferControl windowsEvent = 0x00020003
applicationReturn windowsEvent = 0x00020004
bitlockerUnlock windowsEvent = 0x00020005
eventCounter windowsEvent = 0x00020006
counterID windowsEvent = 0x00020007
morBitNotCancelable windowsEvent = 0x00020008
applicationSVN windowsEvent = 0x00020009
svnChainStatus windowsEvent = 0x0002000A
morBitAPIStatus windowsEvent = 0x0002000B
bootDebugging windowsEvent = 0x00040001
bootRevocationList windowsEvent = 0x00040002
osKernelDebug windowsEvent = 0x00050001
codeIntegrity windowsEvent = 0x00050002
testSigning windowsEvent = 0x00050003
dataExecutionPrevention windowsEvent = 0x00050004
safeMode windowsEvent = 0x00050005
winPE windowsEvent = 0x00050006
physicalAddressExtension windowsEvent = 0x00050007
osDevice windowsEvent = 0x00050008
systemRoot windowsEvent = 0x00050009
hypervisorLaunchType windowsEvent = 0x0005000A
hypervisorPath windowsEvent = 0x0005000B
hypervisorIOMMUPolicy windowsEvent = 0x0005000C
hypervisorDebug windowsEvent = 0x0005000D
driverLoadPolicy windowsEvent = 0x0005000E
siPolicy windowsEvent = 0x0005000F
hypervisorMMIONXPolicy windowsEvent = 0x00050010
hypervisorMSRFilterPolicy windowsEvent = 0x00050011
vsmLaunchType windowsEvent = 0x00050012
osRevocationList windowsEvent = 0x00050013
vsmIDKInfo windowsEvent = 0x00050020
flightSigning windowsEvent = 0x00050021
pagefileEncryptionEnabled windowsEvent = 0x00050022
vsmIDKSInfo windowsEvent = 0x00050023
hibernationDisabled windowsEvent = 0x00050024
dumpsDisabled windowsEvent = 0x00050025
dumpEncryptionEnabled windowsEvent = 0x00050026
dumpEncryptionKeyDigest windowsEvent = 0x00050027
lsaISOConfig windowsEvent = 0x00050028
noAuthority windowsEvent = 0x00060001
authorityPubKey windowsEvent = 0x00060002
filePath windowsEvent = 0x00070001
imageSize windowsEvent = 0x00070002
hashAlgorithmID windowsEvent = 0x00070003
authenticodeHash windowsEvent = 0x00070004
authorityIssuer windowsEvent = 0x00070005
authoritySerial windowsEvent = 0x00070006
imageBase windowsEvent = 0x00070007
authorityPublisher windowsEvent = 0x00070008
authoritySHA1Thumbprint windowsEvent = 0x00070009
imageValidated windowsEvent = 0x0007000A
moduleSVN windowsEvent = 0x0007000B
quote windowsEvent = 0x80080001
quoteSignature windowsEvent = 0x80080002
aikID windowsEvent = 0x80080003
aikPubDigest windowsEvent = 0x80080004
elamKeyname windowsEvent = 0x00090001
elamConfiguration windowsEvent = 0x00090002
elamPolicy windowsEvent = 0x00090003
elamMeasured windowsEvent = 0x00090004
vbsVSMRequired windowsEvent = 0x000A0001
vbsSecurebootRequired windowsEvent = 0x000A0002
vbsIOMMURequired windowsEvent = 0x000A0003
vbsNXRequired windowsEvent = 0x000A0004
vbsMSRFilteringRequired windowsEvent = 0x000A0005
vbsMandatoryEnforcement windowsEvent = 0x000A0006
vbsHVCIPolicy windowsEvent = 0x000A0007
vbsMicrosoftBootChainRequired windowsEvent = 0x000A0008
ksrSignature windowsEvent = 0x000B0001
)
// WinCSPAlg describes the hash algorithm used by a Windows CSP.
type WinCSPAlg uint32
// Valid CSP Algorithm IDs.
const (
WinAlgMD4 WinCSPAlg = 0x02
WinAlgMD5 WinCSPAlg = 0x03
WinAlgSHA1 WinCSPAlg = 0x04
WinAlgSHA256 WinCSPAlg = 0x0c
WinAlgSHA384 WinCSPAlg = 0x0d
WinAlgSHA512 WinCSPAlg = 0x0e
)
// BitlockerStatus describes the status of BitLocker on a Windows system.
type BitlockerStatus uint8
// Valid BitlockerStatus values.
const (
BitlockerStatusCached = 0x01
BitlockerStatusMedia = 0x02
BitlockerStatusTPM = 0x04
BitlockerStatusPin = 0x10
BitlockerStatusExternal = 0x20
BitlockerStatusRecovery = 0x40
)
// Ternary describes a boolean value that can additionally be unknown.
type Ternary uint8
// Valid Ternary values.
const (
TernaryUnknown Ternary = iota
TernaryTrue
TernaryFalse
)
// WinEvents describes information from the event log recorded during
// bootup of Microsoft Windows.
type WinEvents struct {
// ColdBoot is set to true if the system was not resuming from hibernation.
ColdBoot bool
// BootCount contains the value of the monotonic boot counter. This
// value is not set for TPM 1.2 devices and some TPMs with buggy
// implementations of monotonic counters.
BootCount uint64
// LoadedModules contains authenticode hashes for binaries which
// were loaded during boot.
LoadedModules map[string]WinModuleLoad
// ELAM describes the configuration of each Early Launch AntiMalware driver,
// for each AV Vendor key.
ELAM map[string]WinELAM
// BootDebuggingEnabled is true if boot debugging was ever reported
// as enabled.
BootDebuggingEnabled bool
// KernelDebugEnabled is true if kernel debugging was recorded as
// enabled at any point during boot.
KernelDebugEnabled bool
// DEPEnabled is true if NX (Data Execution Prevention) was consistently
// reported as enabled.
DEPEnabled Ternary
// CodeIntegrityEnabled is true if code integrity was consistently
// reported as enabled.
CodeIntegrityEnabled Ternary
// TestSigningEnabled is true if test-mode signature verification was
// ever reported as enabled.
TestSigningEnabled bool
// BitlockerUnlocks reports the bitlocker status for every instance of
// a disk unlock, where bitlocker was used to secure the disk.
BitlockerUnlocks []BitlockerStatus
}
// WinModuleLoad describes a module which was loaded while
// Windows booted.
type WinModuleLoad struct {
// FilePath represents the path from which the module was loaded. This
// information is not always present.
FilePath string
// AuthenticodeHash contains the authenticode hash of the binary
// blob which was loaded.
AuthenticodeHash []byte
// ImageBase describes all the addresses to which the the blob was loaded.
ImageBase []uint64
// ImageSize describes the size of the image in bytes. This information
// is not always present.
ImageSize uint64
// HashAlgorithm describes the hash algorithm used.
HashAlgorithm WinCSPAlg
// ImageValidated is set if the post-boot loader validated the image.
ImageValidated bool
// AuthorityIssuer identifies the issuer of the certificate which certifies
// the signature on this module.
AuthorityIssuer string
// AuthorityPublisher identifies the publisher of the certificate which
// certifies the signature on this module.
AuthorityPublisher string
// AuthoritySerial contains the serial of the certificate certifying this
// module.
AuthoritySerial []byte
// AuthoritySHA1 is the SHA1 hash of the certificate thumbprint.
AuthoritySHA1 []byte
}
// WinELAM describes the configuration of an Early Launch AntiMalware driver.
// These values represent the 3 measured registry values stored in the ELAM
// hive for the driver.
type WinELAM struct {
Measured []byte
Config []byte
Policy []byte
}
// ParseWinEvents parses a series of events to extract information about
// the bringup of Microsoft Windows. This information is not trustworthy
// unless the integrity of platform & bootloader events has already been
// established.
func ParseWinEvents(events []Event) (*WinEvents, error) {
var (
out = WinEvents{
LoadedModules: map[string]WinModuleLoad{},
ELAM: map[string]WinELAM{},
}
seenSeparator struct {
PCR12 bool
PCR13 bool
}
)
for _, e := range events {
if e.Index != 12 && e.Index != 13 {
continue
}
et, err := internal.UntrustedParseEventType(uint32(e.Type))
if err != nil {
return nil, fmt.Errorf("unrecognised event type: %v", err)
}
digestVerify := e.digestEquals(e.Data)
switch e.Index {
case 12: // 'early boot' events
switch et {
case internal.EventTag:
if seenSeparator.PCR12 {
continue
}
s, err := internal.ParseTaggedEventData(e.Data)
if err != nil {
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, digestVerify)
}
if err := out.readWinEventBlock(s, e.Index); err != nil {
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
}
case internal.Separator:
if seenSeparator.PCR12 {
return nil, fmt.Errorf("duplicate WBCL separator at event %d", e.sequence)
}
seenSeparator.PCR12 = true
if !bytes.Equal(e.Data, []byte("WBCL")) {
return nil, fmt.Errorf("invalid WBCL separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
default:
return nil, fmt.Errorf("unexpected (PCR12) event type: %v", et)
}
case 13: // Post 'early boot' events
switch et {
case internal.EventTag:
if seenSeparator.PCR13 {
continue
}
s, err := internal.ParseTaggedEventData(e.Data)
if err != nil {
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, digestVerify)
}
if err := out.readWinEventBlock(s, e.Index); err != nil {
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
}
case internal.Separator:
if seenSeparator.PCR13 {
return nil, fmt.Errorf("duplicate WBCL separator at event %d", e.sequence)
}
seenSeparator.PCR13 = true
if !bytes.Equal(e.Data, []byte("WBCL")) {
return nil, fmt.Errorf("invalid WBCL separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
default:
return nil, fmt.Errorf("unexpected (PCR13) event type: %v", et)
}
}
}
return &out, nil
}
type microsoftEventHeader struct {
Type windowsEvent
Size uint32
}
// unknownSIPAEvent is returned by parseSIPAEvent if the event type is
// not handled. Unlike other events in the TCG log, it is safe to skip
// unhandled SIPA events, as they are embedded within EventTag structures,
// and these structures should match the event digest.
var errUnknownSIPAEvent = errors.New("unknown event")
func (w *WinEvents) readBooleanInt64Event(header microsoftEventHeader, r *bytes.Reader) error {
if header.Size != 8 {
return fmt.Errorf("payload was %d bytes, want 8", header.Size)
}
var num uint64
if err := binary.Read(r, binary.LittleEndian, &num); err != nil {
return fmt.Errorf("reading u64: %w", err)
}
isSet := num != 0
switch header.Type {
// Boolean signals that latch off if the are ever false (ie: attributes
// that represent a stronger security state when set).
case dataExecutionPrevention:
if isSet && w.DEPEnabled == TernaryUnknown {
w.DEPEnabled = TernaryTrue
} else if !isSet {
w.DEPEnabled = TernaryFalse
}
}
return nil
}
func (w *WinEvents) readBooleanByteEvent(header microsoftEventHeader, r *bytes.Reader) error {
if header.Size != 1 {
return fmt.Errorf("payload was %d bytes, want 1", header.Size)
}
var b byte
if err := binary.Read(r, binary.LittleEndian, &b); err != nil {
return fmt.Errorf("reading byte: %w", err)
}
isSet := b != 0
switch header.Type {
// Boolean signals that latch on if they are ever true (ie: attributes
// that represent a weaker security state when set).
case osKernelDebug:
w.KernelDebugEnabled = w.KernelDebugEnabled || isSet
case bootDebugging:
w.BootDebuggingEnabled = w.BootDebuggingEnabled || isSet
case testSigning:
w.TestSigningEnabled = w.TestSigningEnabled || isSet
// Boolean signals that latch off if the are ever false (ie: attributes
// that represent a stronger security state when set).
case codeIntegrity:
if isSet && w.CodeIntegrityEnabled == TernaryUnknown {
w.CodeIntegrityEnabled = TernaryTrue
} else if !isSet {
w.CodeIntegrityEnabled = TernaryFalse
}
}
return nil
}
func (w *WinEvents) readUint32(header microsoftEventHeader, r io.Reader) (uint32, error) {
if header.Size != 4 {
return 0, fmt.Errorf("integer size not uint32 (%d bytes)", header.Size)
}
data := make([]uint8, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return 0, fmt.Errorf("reading u32: %w", err)
}
i := binary.LittleEndian.Uint32(data)
return i, nil
}
func (w *WinEvents) readUint64(header microsoftEventHeader, r io.Reader) (uint64, error) {
if header.Size != 8 {
return 0, fmt.Errorf("integer size not uint64 (%d bytes)", header.Size)
}
data := make([]uint8, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return 0, fmt.Errorf("reading u64: %w", err)
}
i := binary.LittleEndian.Uint64(data)
return i, nil
}
func (w *WinEvents) readBootCounter(header microsoftEventHeader, r *bytes.Reader) error {
i, err := w.readUint64(header, r)
if err != nil {
return fmt.Errorf("boot counter: %v", err)
}
if w.BootCount > 0 && w.BootCount != i {
return fmt.Errorf("conflicting values for boot counter: %d != %d", i, w.BootCount)
}
w.BootCount = i
return nil
}
func (w *WinEvents) readTransferControl(header microsoftEventHeader, r *bytes.Reader) error {
i, err := w.readUint32(header, r)
if err != nil {
return fmt.Errorf("transfer control: %v", err)
}
// A transferControl event with a value of 1 indicates that bootmngr
// launched WinLoad. A different (unknown) value is set if WinResume
// is launched.
w.ColdBoot = i == 0x1
return nil
}
func (w *WinEvents) readBitlockerUnlock(header microsoftEventHeader, r *bytes.Reader, pcr int) error {
if header.Size > 8 {
return fmt.Errorf("bitlocker data too large (%d bytes)", header.Size)
}
data := make([]uint8, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return fmt.Errorf("reading u%d: %w", header.Size<<8, err)
}
i, n := binary.Uvarint(data)
if n <= 0 {
return fmt.Errorf("reading u%d: invalid varint", header.Size<<8)
}
if pcr == 13 {
// The bitlocker status is duplicated across both PCRs. As such,
// we prefer the earlier one, and bail here to prevent duplicate
// records.
return nil
}
w.BitlockerUnlocks = append(w.BitlockerUnlocks, BitlockerStatus(i))
return nil
}
func (w *WinEvents) parseImageValidated(header microsoftEventHeader, r io.Reader) (bool, error) {
if header.Size != 1 {
return false, fmt.Errorf("payload was %d bytes, want 1", header.Size)
}
var num byte
if err := binary.Read(r, binary.LittleEndian, &num); err != nil {
return false, fmt.Errorf("reading u8: %w", err)
}
return num == 1, nil
}
func (w *WinEvents) parseHashAlgID(header microsoftEventHeader, r io.Reader) (WinCSPAlg, error) {
i, err := w.readUint32(header, r)
if err != nil {
return 0, fmt.Errorf("hash algorithm ID: %v", err)
}
switch alg := WinCSPAlg(i & 0xff); alg {
case WinAlgMD4, WinAlgMD5, WinAlgSHA1, WinAlgSHA256, WinAlgSHA384, WinAlgSHA512:
return alg, nil
default:
return 0, fmt.Errorf("unknown algorithm ID: %x", i)
}
}
func (w *WinEvents) parseAuthoritySerial(header microsoftEventHeader, r io.Reader) ([]byte, error) {
if header.Size > 128 {
return nil, fmt.Errorf("authority serial is too long (%d bytes)", header.Size)
}
data := make([]byte, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return nil, fmt.Errorf("reading bytes: %w", err)
}
return data, nil
}
func (w *WinEvents) parseAuthoritySHA1(header microsoftEventHeader, r io.Reader) ([]byte, error) {
if header.Size > 20 {
return nil, fmt.Errorf("authority thumbprint is too long (%d bytes)", header.Size)
}
data := make([]byte, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return nil, fmt.Errorf("reading bytes: %w", err)
}
return data, nil
}
func (w *WinEvents) parseImageBase(header microsoftEventHeader, r io.Reader) (uint64, error) {
if header.Size != 8 {
return 0, fmt.Errorf("payload was %d bytes, want 8", header.Size)
}
var num uint64
if err := binary.Read(r, binary.LittleEndian, &num); err != nil {
return 0, fmt.Errorf("reading u64: %w", err)
}
return num, nil
}
func (w *WinEvents) parseAuthenticodeHash(header microsoftEventHeader, r io.Reader) ([]byte, error) {
if header.Size > 32 {
return nil, fmt.Errorf("authenticode hash data exceeds the size of any valid hash (%d bytes)", header.Size)
}
data := make([]byte, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return nil, fmt.Errorf("reading bytes: %w", err)
}
return data, nil
}
func (w *WinEvents) readLoadedModuleAggregation(rdr *bytes.Reader, header microsoftEventHeader) error {
var (
r = &io.LimitedReader{R: rdr, N: int64(header.Size)}
codeHash []byte
imgBase, imgSize uint64
fPath string
algID WinCSPAlg
imgValidated bool
aIssuer, aPublisher string
aSerial, aSHA1 []byte
)
for r.N > 0 {
var h microsoftEventHeader
if err := binary.Read(r, binary.LittleEndian, &h); err != nil {
return fmt.Errorf("parsing LMA sub-event: %v", err)
}
if int64(h.Size) > r.N {
return fmt.Errorf("LMA sub-event is larger than available data: %d > %d", h.Size, r.N)
}
var err error
switch h.Type {
case imageBase:
if imgBase != 0 {
return errors.New("duplicate image base data in LMA event")
}
if imgBase, err = w.parseImageBase(h, r); err != nil {
return err
}
case authenticodeHash:
if codeHash != nil {
return errors.New("duplicate authenticode hash structure in LMA event")
}
if codeHash, err = w.parseAuthenticodeHash(h, r); err != nil {
return err
}
case filePath:
if fPath != "" {
return errors.New("duplicate file path in LMA event")
}
if fPath, err = w.parseUTF16(h, r); err != nil {
return err
}
case imageSize:
if imgSize != 0 {
return errors.New("duplicate image size in LMA event")
}
if imgSize, err = w.readUint64(h, r); err != nil {
return err
}
case hashAlgorithmID:
if algID != 0 {
return errors.New("duplicate hash algorithm ID in LMA event")
}
if algID, err = w.parseHashAlgID(h, r); err != nil {
return err
}
case imageValidated:
if imgValidated {
return errors.New("duplicate image validated field in LMA event")
}
if imgValidated, err = w.parseImageValidated(h, r); err != nil {
return err
}
case authorityIssuer:
if aIssuer != "" {
return errors.New("duplicate authority issuer in LMA event")
}
if aIssuer, err = w.parseUTF16(h, r); err != nil {
return err
}
case authorityPublisher:
if aPublisher != "" {
return errors.New("duplicate authority publisher in LMA event")
}
if aPublisher, err = w.parseUTF16(h, r); err != nil {
return err
}
case authoritySerial:
if aSerial != nil {
return errors.New("duplicate authority serial in LMA event")
}
if aSerial, err = w.parseAuthoritySerial(h, r); err != nil {
return err
}
case authoritySHA1Thumbprint:
if aSHA1 != nil {
return errors.New("duplicate authority SHA1 thumbprint in LMA event")
}
if aSHA1, err = w.parseAuthoritySHA1(h, r); err != nil {
return err
}
case moduleSVN:
// Ignore - consume value.
b := make([]byte, h.Size)
if err := binary.Read(r, binary.LittleEndian, &b); err != nil {
return err
}
default:
return fmt.Errorf("unknown event in LMA aggregation: %v", h.Type)
}
}
var iBase []uint64
if imgBase != 0 {
iBase = []uint64{imgBase}
}
l := WinModuleLoad{
FilePath: fPath,
AuthenticodeHash: codeHash,
ImageBase: iBase,
ImageSize: imgSize,
ImageValidated: imgValidated,
HashAlgorithm: algID,
AuthorityIssuer: aIssuer,
AuthorityPublisher: aPublisher,
AuthoritySerial: aSerial,
AuthoritySHA1: aSHA1,
}
hashHex := hex.EncodeToString(l.AuthenticodeHash)
l.ImageBase = append(l.ImageBase, w.LoadedModules[hashHex].ImageBase...)
w.LoadedModules[hashHex] = l
return nil
}
// parseUTF16 decodes data representing a UTF16 string. It is assumed the
// caller has validated that the data size is within allowable bounds.
func (w *WinEvents) parseUTF16(header microsoftEventHeader, r io.Reader) (string, error) {
data := make([]uint16, header.Size/2)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return "", err
}
return strings.TrimSuffix(string(utf16.Decode(data)), "\x00"), nil
}
func (w *WinEvents) readELAMAggregation(rdr io.Reader, header microsoftEventHeader) error {
var (
r = &io.LimitedReader{R: rdr, N: int64(header.Size)}
driverName string
measured []byte
policy []byte
config []byte
)
for r.N > 0 {
var h microsoftEventHeader
if err := binary.Read(r, binary.LittleEndian, &h); err != nil {
return fmt.Errorf("parsing ELAM aggregation sub-event: %v", err)
}
if int64(h.Size) > r.N {
return fmt.Errorf("ELAM aggregation sub-event is larger than available data: %d > %d", h.Size, r.N)
}
var err error
switch h.Type {
case elamAggregation:
w.readELAMAggregation(r, h)
if r.N == 0 {
return nil
}
case elamKeyname:
if driverName != "" {
return errors.New("duplicate driver name in ELAM aggregation event")
}
if driverName, err = w.parseUTF16(h, r); err != nil {
return fmt.Errorf("parsing ELAM driver name: %v", err)
}
case elamMeasured:
if measured != nil {
return errors.New("duplicate measured data in ELAM aggregation event")
}
measured = make([]byte, h.Size)
if err := binary.Read(r, binary.LittleEndian, &measured); err != nil {
return fmt.Errorf("reading ELAM measured value: %v", err)
}
case elamPolicy:
if policy != nil {
return errors.New("duplicate policy data in ELAM aggregation event")
}
policy = make([]byte, h.Size)
if err := binary.Read(r, binary.LittleEndian, &policy); err != nil {
return fmt.Errorf("reading ELAM policy value: %v", err)
}
case elamConfiguration:
if config != nil {
return errors.New("duplicate config data in ELAM aggregation event")
}
config = make([]byte, h.Size)
if err := binary.Read(r, binary.LittleEndian, &config); err != nil {
return fmt.Errorf("reading ELAM config value: %v", err)
}
default:
return fmt.Errorf("unknown event in LMA aggregation: %v", h.Type)
}
}
if driverName == "" {
return errors.New("ELAM driver name not specified")
}
w.ELAM[driverName] = WinELAM{
Measured: measured,
Config: config,
Policy: policy,
}
return nil
}
func (w *WinEvents) readSIPAEvent(r *bytes.Reader, pcr int) error {
var header microsoftEventHeader
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return err
}
switch header.Type {
case elamAggregation:
return w.readELAMAggregation(r, header)
case loadedModuleAggregation:
return w.readLoadedModuleAggregation(r, header)
case bootCounter:
return w.readBootCounter(header, r)
case bitlockerUnlock:
return w.readBitlockerUnlock(header, r, pcr)
case transferControl:
return w.readTransferControl(header, r)
case osKernelDebug, codeIntegrity, bootDebugging, testSigning: // Parse boolean values.
return w.readBooleanByteEvent(header, r)
case dataExecutionPrevention: // Parse booleans represented as uint64's.
return w.readBooleanInt64Event(header, r)
default:
// Event type was not handled, consume the data.
if int(header.Size) > r.Len() {
return fmt.Errorf("event data len (%d bytes) larger than event length (%d bytes)", header.Size, r.Len())
}
tmp := make([]byte, header.Size)
if err := binary.Read(r, binary.LittleEndian, &tmp); err != nil {
return fmt.Errorf("reading unknown data section of length %d: %w", header.Size, err)
}
return errUnknownSIPAEvent
}
}
// readWinEventBlock extracts boot configuration from SIPA events contained in
// the given tagged event.
func (w *WinEvents) readWinEventBlock(evt *internal.TaggedEventData, pcr int) error {
r := bytes.NewReader(evt.Data)
// All windows information should be sub events in an enclosing SIPA
// container event.
if (windowsEvent(evt.ID) & sipaTypeMask) != sipaContainer {
return fmt.Errorf("expected container event, got %v", windowsEvent(evt.ID))
}
for r.Len() > 0 {
if err := w.readSIPAEvent(r, pcr); err != nil {
if errors.Is(err, errUnknownSIPAEvent) {
// Unknown SIPA events are okay as all TCG events are verifiable.
continue
}
return err
}
}
return nil
}
// Copyright 2020 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/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/asn1"
"errors"
"fmt"
"io"
"math/big"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
)
// The size of TPM2B_MAX_BUFFER is TPM-dependent. The value here is what all
// TPMs support. See TPM 2.0 spec, part 2, section 10.4.8 TPM2B_MAX_BUFFER.
const tpm2BMaxBufferSize = 1024
// wrappedTPM20 interfaces with a TPM 2.0 command channel.
type wrappedTPM20 struct {
interf TPMInterface
rwc CommandChannelTPM20
tpmRSAEkTemplate *tpm2.Public
tpmECCEkTemplate *tpm2.Public
}
// certifyingKey contains details of a TPM key that could certify other keys.
type certifyingKey struct {
handle tpmutil.Handle
alg Algorithm
}
func (t *wrappedTPM20) rsaEkTemplate() tpm2.Public {
if t.tpmRSAEkTemplate != nil {
return *t.tpmRSAEkTemplate
}
nonce, err := tpm2.NVReadEx(t.rwc, nvramRSAEkNonceIndex, tpm2.HandleOwner, "", 0)
if err != nil {
t.tpmRSAEkTemplate = &defaultRSAEKTemplate // No nonce, use the default template
} else {
template := defaultRSAEKTemplate
copy(template.RSAParameters.ModulusRaw, nonce)
t.tpmRSAEkTemplate = &template
}
return *t.tpmRSAEkTemplate
}
func (t *wrappedTPM20) eccEkTemplate() tpm2.Public {
if t.tpmECCEkTemplate != nil {
return *t.tpmECCEkTemplate
}
nonce, err := tpm2.NVReadEx(t.rwc, nvramECCEkNonceIndex, tpm2.HandleOwner, "", 0)
if err != nil {
t.tpmECCEkTemplate = &defaultECCEKTemplate // No nonce, use the default template
} else {
template := defaultECCEKTemplate
copy(template.ECCParameters.Point.XRaw, nonce)
t.tpmECCEkTemplate = &template
}
return *t.tpmECCEkTemplate
}
func (t *wrappedTPM20) close() error {
return t.rwc.Close()
}
// Info returns information about the TPM.
func (t *wrappedTPM20) info() (*TPMInfo, error) {
var (
tInfo = TPMInfo{
Interface: t.interf,
}
t2Info tpmInfo
err error
)
if t2Info, err = readVendorAttributes(t.rwc); err != nil {
return nil, err
}
tInfo.Manufacturer = t2Info.manufacturer
tInfo.VendorInfo = t2Info.vendor
tInfo.FirmwareVersionMajor = t2Info.fwMajor
tInfo.FirmwareVersionMinor = t2Info.fwMinor
return &tInfo, nil
}
// Return value: handle, whether we generated a new one, error.
func (t *wrappedTPM20) getEndorsementKeyHandle(ek *EK) (tpmutil.Handle, bool, error) {
var ekHandle tpmutil.Handle
var ekTemplate tpm2.Public
if ek == nil {
// The default is RSA for backward compatibility.
ekHandle = commonRSAEkEquivalentHandle
ekTemplate = t.rsaEkTemplate()
} else {
ekHandle = ek.handle
if ekHandle == 0 {
// Assume RSA EK handle if it was not provided.
ekHandle = commonRSAEkEquivalentHandle
}
switch pub := ek.Public.(type) {
case *rsa.PublicKey:
ekTemplate = t.rsaEkTemplate()
case *ecdsa.PublicKey:
ekTemplate = t.eccEkTemplate()
default:
return 0, false, fmt.Errorf("unsupported public key type %T", pub)
}
}
_, _, _, err := tpm2.ReadPublic(t.rwc, ekHandle)
if err == nil {
// Found the persistent handle, assume it's the key we want.
return ekHandle, false, nil
}
rerr := err // Preserve this failure for later logging, if needed
keyHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", ekTemplate)
if err != nil {
return 0, false, fmt.Errorf("ReadPublic failed (%v), and then CreatePrimary failed: %v", rerr, err)
}
defer tpm2.FlushContext(t.rwc, keyHnd)
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, ekHandle)
if err != nil {
return 0, false, fmt.Errorf("EvictControl failed: %v", err)
}
return ekHandle, true, nil
}
// Return value: handle, whether we generated a new one, error
func (t *wrappedTPM20) getStorageRootKeyHandle(parent ParentKeyConfig) (tpmutil.Handle, bool, error) {
srkHandle := parent.Handle
_, _, _, err := tpm2.ReadPublic(t.rwc, srkHandle)
if err == nil {
// Found the persistent handle, assume it's the key we want.
return srkHandle, false, nil
}
rerr := err // Preserve this failure for later logging, if needed
var srkTemplate tpm2.Public
switch parent.Algorithm {
case RSA:
srkTemplate = defaultRSASRKTemplate
case ECDSA:
srkTemplate = defaultECCSRKTemplate
default:
return 0, false, fmt.Errorf("unsupported SRK algorithm: %v", parent.Algorithm)
}
keyHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", srkTemplate)
if err != nil {
return 0, false, fmt.Errorf("ReadPublic failed (%v), and then CreatePrimary failed: %v", rerr, err)
}
defer tpm2.FlushContext(t.rwc, keyHnd)
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, srkHandle)
if err != nil {
return 0, false, fmt.Errorf("EvictControl failed: %v", err)
}
return srkHandle, true, nil
}
func (t *wrappedTPM20) ekCertificates() ([]EK, error) {
var res []EK
if rsaCert, err := readEKCertFromNVRAM20(t.rwc, nvramRSACertIndex); err == nil {
res = append(res, EK{Public: crypto.PublicKey(rsaCert.PublicKey), Certificate: rsaCert, handle: commonRSAEkEquivalentHandle})
}
if eccCert, err := readEKCertFromNVRAM20(t.rwc, nvramECCCertIndex); err == nil {
res = append(res, EK{Public: crypto.PublicKey(eccCert.PublicKey), Certificate: eccCert, handle: commonECCEkEquivalentHandle})
}
return res, nil
}
func (t *wrappedTPM20) eks() ([]EK, error) {
if cert, err := readEKCertFromNVRAM20(t.rwc, nvramRSACertIndex); err == nil {
return []EK{
{Public: crypto.PublicKey(cert.PublicKey), Certificate: cert, handle: commonRSAEkEquivalentHandle},
}, nil
}
// Attempt to create an EK.
ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", t.rsaEkTemplate())
if err != nil {
return nil, fmt.Errorf("EK CreatePrimary failed: %v", err)
}
defer tpm2.FlushContext(t.rwc, ekHnd)
pub, _, _, err := tpm2.ReadPublic(t.rwc, ekHnd)
if err != nil {
return nil, fmt.Errorf("EK ReadPublic failed: %v", err)
}
if pub.RSAParameters == nil {
return nil, errors.New("ECC EK not yet supported")
}
i, err := t.info()
if err != nil {
return nil, fmt.Errorf("retrieving TPM info failed: %v", err)
}
ekPub := &rsa.PublicKey{
E: int(pub.RSAParameters.Exponent()),
N: pub.RSAParameters.Modulus(),
}
certificateURL := ekCertURL(ekPub, i.Manufacturer.String())
return []EK{
{
Public: ekPub,
CertificateURL: certificateURL,
handle: commonRSAEkEquivalentHandle,
},
}, nil
}
func (t *wrappedTPM20) newAK(opts *AKConfig) (*AK, error) {
var parent ParentKeyConfig
if opts != nil && opts.Parent != nil {
parent = *opts.Parent
} else {
parent = defaultParentConfig
}
srk, _, err := t.getStorageRootKeyHandle(parent)
if err != nil {
return nil, fmt.Errorf("failed to get SRK handle: %v", err)
}
var akTemplate tpm2.Public
var sigScheme *tpm2.SigScheme
// The default is RSA.
if opts != nil && opts.Algorithm == ECDSA {
akTemplate = akTemplateECC
sigScheme = akTemplateECC.ECCParameters.Sign
} else {
akTemplate = akTemplateRSA
sigScheme = akTemplateRSA.RSAParameters.Sign
}
blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", akTemplate)
if err != nil {
return nil, fmt.Errorf("CreateKeyEx() failed: %v", err)
}
keyHandle, _, err := tpm2.Load(t.rwc, srk, "", pub, blob)
if err != nil {
return nil, fmt.Errorf("Load() failed: %v", err)
}
// If any errors occur, free the AK's handle.
defer func() {
if err != nil {
tpm2.FlushContext(t.rwc, keyHandle)
}
}()
// We can only certify the creation immediately afterwards, so we cache the result.
attestation, sig, err := tpm2.CertifyCreation(t.rwc, "", keyHandle, keyHandle, nil, creationHash, *sigScheme, tix)
if err != nil {
return nil, fmt.Errorf("CertifyCreation failed: %v", err)
}
tpmPub, err := tpm2.DecodePublic(pub)
if err != nil {
return nil, fmt.Errorf("decode public key: %v", err)
}
pubKey, err := tpmPub.Key()
if err != nil {
return nil, fmt.Errorf("access public key: %v", err)
}
return &AK{
ak: newWrappedAK20(keyHandle, blob, pub, creationData, attestation, sig),
pub: pubKey,
}, nil
}
func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) {
k, ok := ak.ak.(*wrappedKey20)
if !ok {
return nil, fmt.Errorf("expected *wrappedKey20, got: %T", k)
}
kAlg, err := k.algorithm()
if err != nil {
return nil, fmt.Errorf("get algorithm: %v", err)
}
ck := certifyingKey{handle: k.hnd, alg: kAlg}
return t.newKeyCertifiedByKey(ck, opts)
}
func (t *wrappedTPM20) newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error) {
parent, blob, pub, creationData, err := createKey(t, opts)
if err != nil {
return nil, fmt.Errorf("cannot create key: %v", err)
}
keyHandle, _, err := tpm2.Load(t.rwc, parent, "", pub, blob)
if err != nil {
return nil, fmt.Errorf("Load() failed: %v", err)
}
// If any errors occur, free the handle.
defer func() {
if err != nil {
tpm2.FlushContext(t.rwc, keyHandle)
}
}()
// Certify application key by AK
certifyOpts := CertifyOpts{QualifyingData: opts.QualifyingData}
cp, err := certifyByKey(t, keyHandle, ck, certifyOpts)
if err != nil {
return nil, fmt.Errorf("certifyByKey() failed: %v", err)
}
if !bytes.Equal(pub, cp.Public) {
return nil, fmt.Errorf("certified incorrect key, expected: %v, certified: %v", pub, cp.Public)
}
// Pack the raw structure into a TPMU_SIGNATURE.
tpmPub, err := tpm2.DecodePublic(pub)
if err != nil {
return nil, fmt.Errorf("decode public key: %v", err)
}
pubKey, err := tpmPub.Key()
if err != nil {
return nil, fmt.Errorf("access public key: %v", err)
}
return &Key{key: newWrappedKey20(keyHandle, blob, pub, creationData, cp.CreateAttestation, cp.CreateSignature), pub: pubKey, tpm: t}, nil
}
func createKey(t *wrappedTPM20, opts *KeyConfig) (tpmutil.Handle, []byte, []byte, []byte, error) {
var parent ParentKeyConfig
if opts != nil && opts.Parent != nil {
parent = *opts.Parent
} else {
parent = defaultParentConfig
}
srk, _, err := t.getStorageRootKeyHandle(parent)
if err != nil {
return 0, nil, nil, nil, fmt.Errorf("failed to get SRK handle: %v", err)
}
tmpl, err := templateFromConfig(opts)
if err != nil {
return 0, nil, nil, nil, fmt.Errorf("incorrect key options: %v", err)
}
blob, pub, creationData, _, _, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", tmpl)
if err != nil {
return 0, nil, nil, nil, fmt.Errorf("CreateKey() failed: %v", err)
}
return srk, blob, pub, creationData, err
}
func templateFromConfig(opts *KeyConfig) (tpm2.Public, error) {
var tmpl tpm2.Public
switch opts.Algorithm {
case RSA:
tmpl = rsaKeyTemplate
if opts.Size < 0 || opts.Size > 65535 { // basic sanity check
return tmpl, fmt.Errorf("incorrect size parameter")
}
tmpl.RSAParameters.KeyBits = uint16(opts.Size)
case ECDSA:
tmpl = ecdsaKeyTemplate
switch opts.Size {
case 256:
tmpl.NameAlg = tpm2.AlgSHA256
tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA256
tmpl.ECCParameters.CurveID = tpm2.CurveNISTP256
tmpl.ECCParameters.Point = tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
}
case 384:
tmpl.NameAlg = tpm2.AlgSHA384
tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA384
tmpl.ECCParameters.CurveID = tpm2.CurveNISTP384
tmpl.ECCParameters.Point = tpm2.ECPoint{
XRaw: make([]byte, 48),
YRaw: make([]byte, 48),
}
case 521:
tmpl.NameAlg = tpm2.AlgSHA512
tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA512
tmpl.ECCParameters.CurveID = tpm2.CurveNISTP521
tmpl.ECCParameters.Point = tpm2.ECPoint{
XRaw: make([]byte, 65),
YRaw: make([]byte, 65),
}
default:
return tmpl, fmt.Errorf("unsupported key size: %v", opts.Size)
}
default:
return tmpl, fmt.Errorf("unsupported algorithm type: %q", opts.Algorithm)
}
return tmpl, nil
}
func (t *wrappedTPM20) deserializeAndLoad(opaqueBlob []byte, parent ParentKeyConfig) (tpmutil.Handle, *serializedKey, error) {
sKey, err := deserializeKey(opaqueBlob)
if err != nil {
return 0, nil, fmt.Errorf("deserializeKey() failed: %v", err)
}
if sKey.Encoding != keyEncodingEncrypted {
return 0, nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding)
}
srk, _, err := t.getStorageRootKeyHandle(parent)
if err != nil {
return 0, nil, fmt.Errorf("failed to get SRK handle: %v", err)
}
var hnd tpmutil.Handle
if hnd, _, err = tpm2.Load(t.rwc, srk, "", sKey.Public, sKey.Blob); err != nil {
return 0, nil, fmt.Errorf("Load() failed: %v", err)
}
return hnd, sKey, nil
}
func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) {
return t.loadAKWithParent(opaqueBlob, defaultParentConfig)
}
func (t *wrappedTPM20) loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error) {
hnd, sKey, err := t.deserializeAndLoad(opaqueBlob, parent)
if err != nil {
return nil, fmt.Errorf("cannot load attestation key: %v", err)
}
return &AK{ak: newWrappedAK20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil
}
func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) {
return t.loadKeyWithParent(opaqueBlob, defaultParentConfig)
}
func (t *wrappedTPM20) loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error) {
hnd, sKey, err := t.deserializeAndLoad(opaqueBlob, parent)
if err != nil {
return nil, fmt.Errorf("cannot load signing key: %v", err)
}
tpmPub, err := tpm2.DecodePublic(sKey.Public)
if err != nil {
return nil, fmt.Errorf("decode public blob: %v", err)
}
pub, err := tpmPub.Key()
if err != nil {
return nil, fmt.Errorf("access public key: %v", err)
}
return &Key{key: newWrappedKey20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature), pub: pub, tpm: t}, nil
}
func (t *wrappedTPM20) pcrbanks() ([]HashAlg, error) {
return pcrbanks(t.rwc)
}
func (t *wrappedTPM20) pcrs(alg HashAlg) ([]PCR, error) {
PCRs, err := readAllPCRs(t.rwc, alg.goTPMAlg())
if err != nil {
return nil, fmt.Errorf("failed to read PCRs: %v", err)
}
out := make([]PCR, len(PCRs))
for index, digest := range PCRs {
digestAlg, err := alg.cryptoHash()
if err != nil {
return nil, fmt.Errorf("unknown algorithm ID %x: %v", alg, err)
}
out[int(index)] = PCR{
Index: int(index),
Digest: digest,
DigestAlg: digestAlg,
}
}
return out, nil
}
func (t *wrappedTPM20) measurementLog() ([]byte, error) {
return t.rwc.MeasurementLog()
}
func tpmHash(rwc CommandChannelTPM20, msg []byte, h crypto.Hash) ([]byte, *tpm2.Ticket, error) {
alg, err := tpm2.HashToAlgorithm(h)
if err != nil {
return nil, nil, fmt.Errorf("failed to convert hash algorithm: %v", err)
}
hnd, err := tpm2.HashSequenceStart(rwc, "", alg)
if err != nil {
return nil, nil, fmt.Errorf("failed to start hash sequence: %v", err)
}
flushHandle := true
defer func() {
if flushHandle {
tpm2.FlushContext(rwc, hnd)
}
}()
i := 0
// Handle all but the last chunk.
for {
end := i + tpm2BMaxBufferSize
if end >= len(msg) {
break
}
if err := tpm2.SequenceUpdate(rwc, "", hnd, msg[i:end]); err != nil {
return nil, nil, fmt.Errorf("failed to update hash sequence: %v", err)
}
i = end
}
// Handle the last chunk.
//
// Hardcoding tpm2.HandleOwner here should be fine --- "The hierarchy
// parameter is not related to the signing key hierarchy". See TPM 2.0
// spec, Part 3, Section 17.6 TPM2_SequenceComplete.
digest, validation, err := tpm2.SequenceComplete(rwc, "", hnd, tpm2.HandleOwner, msg[i:])
if err != nil {
return nil, nil, fmt.Errorf("failed to complete hash sequence: %v", err)
}
// "If this command completes successfully, the sequenceHandle object will
// be flushed." --- we don't need to flush it ourselves.
flushHandle = false
if validation.Hierarchy == tpm2.HandleNull {
return nil, nil, fmt.Errorf("validation ticket has a null hierarchy. msg might be too short or starts with TPM_GENERATED_VALUE")
}
return digest, validation, nil
}
// wrappedKey20 represents a key manipulated through a *wrappedTPM20.
type wrappedKey20 struct {
hnd tpmutil.Handle
blob []byte
public []byte // used by both TPM1.2 and 2.0
createData []byte
createAttestation []byte
createSignature []byte
}
func newWrappedAK20(hnd tpmutil.Handle, blob, public, createData, createAttestation, createSig []byte) ak {
return &wrappedKey20{
hnd: hnd,
blob: blob,
public: public,
createData: createData,
createAttestation: createAttestation,
createSignature: createSig,
}
}
func newWrappedKey20(hnd tpmutil.Handle, blob, public, createData, createAttestation, createSig []byte) key {
return &wrappedKey20{
hnd: hnd,
blob: blob,
public: public,
createData: createData,
createAttestation: createAttestation,
createSignature: createSig,
}
}
func (k *wrappedKey20) marshal() ([]byte, error) {
return (&serializedKey{
Encoding: keyEncodingEncrypted,
TPMVersion: 2,
Blob: k.blob,
Public: k.public,
CreateData: k.createData,
CreateAttestation: k.createAttestation,
CreateSignature: k.createSignature,
}).Serialize()
}
func (k *wrappedKey20) close(t tpmBase) error {
tpm, ok := t.(*wrappedTPM20)
if !ok {
return fmt.Errorf("expected *wrappedTPM20, got %T", t)
}
return tpm2.FlushContext(tpm.rwc, k.hnd)
}
func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) {
t, ok := tb.(*wrappedTPM20)
if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
}
if len(in.Credential) < 2 {
return nil, fmt.Errorf("malformed credential blob")
}
credential := in.Credential[2:]
if len(in.Secret) < 2 {
return nil, fmt.Errorf("malformed encrypted secret")
}
secret := in.Secret[2:]
ekHnd, _, err := t.getEndorsementKeyHandle(ek)
if err != nil {
return nil, err
}
sessHandle, _, err := tpm2.StartAuthSession(
t.rwc,
tpm2.HandleNull, /*tpmKey*/
tpm2.HandleNull, /*bindKey*/
make([]byte, 16), /*nonceCaller*/
nil, /*secret*/
tpm2.SessionPolicy,
tpm2.AlgNull,
tpm2.AlgSHA256)
if err != nil {
return nil, fmt.Errorf("creating session: %v", err)
}
defer tpm2.FlushContext(t.rwc, sessHandle)
if _, _, err := tpm2.PolicySecret(t.rwc, tpm2.HandleEndorsement, tpm2.AuthCommand{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession}, sessHandle, nil, nil, nil, 0); err != nil {
return nil, fmt.Errorf("tpm2.PolicySecret() failed: %v", err)
}
return tpm2.ActivateCredentialUsingAuth(t.rwc, []tpm2.AuthCommand{
{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession},
{Session: sessHandle, Attributes: tpm2.AttrContinueSession},
}, k.hnd, ekHnd, credential, secret)
}
func sigSchemeFromAlgorithm(alg Algorithm) (tpm2.SigScheme, error) {
switch alg {
case RSA:
return tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: tpm2.AlgSHA256,
}, nil
case ECDSA:
return tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
Hash: tpm2.AlgSHA256,
}, nil
default:
return tpm2.SigScheme{}, fmt.Errorf("algorithm %v not supported", alg)
}
}
func (k *wrappedKey20) certify(tb tpmBase, handle any, opts CertifyOpts) (*CertificationParameters, error) {
kAlg, err := k.algorithm()
if err != nil {
return nil, fmt.Errorf("unknown algorithm: %v", err)
}
ck := certifyingKey{
handle: k.hnd,
alg: kAlg,
}
return certifyByKey(tb, handle, ck, opts)
}
func certifyByKey(tb tpmBase, handle any, ck certifyingKey, opts CertifyOpts) (*CertificationParameters, error) {
t, ok := tb.(*wrappedTPM20)
if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
}
hnd, ok := handle.(tpmutil.Handle)
if !ok {
return nil, fmt.Errorf("expected tpmutil.Handle, got %T", handle)
}
scheme, err := sigSchemeFromAlgorithm(ck.alg)
if err != nil {
return nil, fmt.Errorf("get signature scheme: %v", err)
}
return certify(t.rwc, hnd, ck.handle, opts.QualifyingData, scheme)
}
func (k *wrappedKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {
t, ok := tb.(*wrappedTPM20)
if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
}
return quote20(t.rwc, k.hnd, tpm2.Algorithm(alg), nonce, selectedPCRs)
}
func (k *wrappedKey20) attestationParameters() AttestationParameters {
return AttestationParameters{
Public: k.public,
CreateData: k.createData,
CreateAttestation: k.createAttestation,
CreateSignature: k.createSignature,
}
}
func (k *wrappedKey20) certificationParameters() CertificationParameters {
return CertificationParameters{
Public: k.public,
CreateAttestation: k.createAttestation,
CreateSignature: k.createSignature,
}
}
func (k *wrappedKey20) signMsg(tb tpmBase, msg []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error) {
t, ok := tb.(*wrappedTPM20)
if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
}
digest, validation, err := tpmHash(t.rwc, msg, opts.HashFunc())
if err != nil {
return nil, fmt.Errorf("failed to hash message: %v", err)
}
return k.signWithValidation(tb, digest, pub, opts, validation)
}
func (k *wrappedKey20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error) {
return k.signWithValidation(tb, digest, pub, opts, nil)
}
func (k *wrappedKey20) signWithValidation(tb tpmBase, digest []byte, pub crypto.PublicKey, opts crypto.SignerOpts, validation *tpm2.Ticket) ([]byte, error) {
t, ok := tb.(*wrappedTPM20)
if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
}
switch p := pub.(type) {
case *ecdsa.PublicKey:
return signECDSA(t.rwc, k.hnd, digest, p.Curve, validation)
case *rsa.PublicKey:
return signRSA(t.rwc, k.hnd, digest, opts, validation)
}
return nil, fmt.Errorf("unsupported signing key type: %T", pub)
}
func signECDSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, curve elliptic.Curve, validation *tpm2.Ticket) ([]byte, error) {
// https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/crypto/ecdsa/ecdsa.go;l=181
orderBits := curve.Params().N.BitLen()
orderBytes := (orderBits + 7) / 8
if len(digest) > orderBytes {
digest = digest[:orderBytes]
}
ret := new(big.Int).SetBytes(digest)
excess := len(digest)*8 - orderBits
if excess > 0 {
ret.Rsh(ret, uint(excess))
}
// call ret.FillBytes() here instead of ret.Bytes() to preserve leading zeroes
// that may have been dropped when converting the digest to an integer
digest = ret.FillBytes(digest)
sig, err := tpm2.Sign(rw, key, "", digest, validation, nil)
if err != nil {
return nil, fmt.Errorf("cannot sign: %v", err)
}
if sig.ECC == nil {
return nil, fmt.Errorf("expected ECDSA signature, got: %v", sig.Alg)
}
return asn1.Marshal(struct {
R *big.Int
S *big.Int
}{sig.ECC.R, sig.ECC.S})
}
func signRSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, opts crypto.SignerOpts, validation *tpm2.Ticket) ([]byte, error) {
h, err := tpm2.HashToAlgorithm(opts.HashFunc())
if err != nil {
return nil, fmt.Errorf("incorrect hash algorithm: %v", err)
}
scheme := &tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: h,
}
if pss, ok := opts.(*rsa.PSSOptions); ok {
if pss.SaltLength != rsa.PSSSaltLengthAuto && pss.SaltLength != len(digest) {
return nil, fmt.Errorf("PSS salt length %d is incorrect, expected rsa.PSSSaltLengthAuto or %d", pss.SaltLength, len(digest))
}
scheme.Alg = tpm2.AlgRSAPSS
}
sig, err := tpm2.Sign(rw, key, "", digest, validation, scheme)
if err != nil {
return nil, fmt.Errorf("cannot sign: %v", err)
}
if sig.RSA == nil {
return nil, fmt.Errorf("expected RSA signature, got: %v", sig.Alg)
}
return sig.RSA.Signature, nil
}
func (k *wrappedKey20) decrypt(tb tpmBase, ctxt []byte) ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}
func (k *wrappedKey20) blobs() ([]byte, []byte, error) {
return k.public, k.blob, nil
}
func (k *wrappedKey20) algorithm() (Algorithm, error) {
tpmPub, err := tpm2.DecodePublic(k.public)
if err != nil {
return "", fmt.Errorf("decode public key: %v", err)
}
switch tpmPub.Type {
case tpm2.AlgRSA:
return RSA, nil
case tpm2.AlgECC:
return ECDSA, nil
default:
return "", fmt.Errorf("unsupported key type: %v", tpmPub.Type)
}
}