mirror of
https://github.com/google/go-attestation.git
synced 2024-12-21 05:53:25 +00:00
549 lines
17 KiB
Go
549 lines
17 KiB
Go
// 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/tpm"
|
|
"github.com/google/go-tpm/tpmutil"
|
|
)
|
|
|
|
// TPMVersion is used to configure a preference in
|
|
// which TPM to use, if multiple are available.
|
|
type TPMVersion uint8
|
|
|
|
// TPM versions
|
|
const (
|
|
TPMVersionAgnostic TPMVersion = iota
|
|
TPMVersion12
|
|
TPMVersion20
|
|
)
|
|
|
|
// 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 {
|
|
// TPMVersion indicates which TPM version the library should
|
|
// attempt to use. If the specified version is not available,
|
|
// ErrTPMNotAvailable is returned. Defaults to TPMVersionAgnostic.
|
|
TPMVersion TPMVersion
|
|
|
|
// 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
|
|
)
|
|
|
|
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 interface{}) (*CertificationParameters, error)
|
|
}
|
|
|
|
// AK represents a key which can be used for attestation.
|
|
type AK struct {
|
|
ak ak
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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 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 interface{}) (*CertificationParameters, error) {
|
|
return k.ak.certify(tpm.tpm, handle)
|
|
}
|
|
|
|
// AKConfig encapsulates parameters for minting keys. This type is defined
|
|
// now (despite being empty) for future interface compatibility.
|
|
type AKConfig struct {
|
|
}
|
|
|
|
// 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 {
|
|
Version TPMVersion
|
|
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(version TPMVersion, public []byte) (*AKPublic, error) {
|
|
switch version {
|
|
case TPMVersion12:
|
|
rsaPub, err := tpm.UnmarshalPubRSAPublicKey(public)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing public key: %v", err)
|
|
}
|
|
return &AKPublic{Public: rsaPub, Hash: crypto.SHA1}, nil
|
|
case TPMVersion20:
|
|
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
|
|
default:
|
|
return nil, fmt.Errorf("unknown tpm version 0x%x", version)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
switch quote.Version {
|
|
case TPMVersion12:
|
|
return a.validate12Quote(quote, pcrs, nonce)
|
|
case TPMVersion20:
|
|
return a.validate20Quote(quote, pcrs, nonce)
|
|
default:
|
|
return fmt.Errorf("quote used unknown tpm version 0x%x", quote.Version)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
// Valid hash algorithms.
|
|
var (
|
|
HashSHA1 = HashAlg(tpm2.AlgSHA1)
|
|
HashSHA256 = HashAlg(tpm2.AlgSHA256)
|
|
)
|
|
|
|
func (a HashAlg) cryptoHash() crypto.Hash {
|
|
switch a {
|
|
case HashSHA1:
|
|
return crypto.SHA1
|
|
case HashSHA256:
|
|
return crypto.SHA256
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (a HashAlg) goTPMAlg() tpm2.Algorithm {
|
|
switch a {
|
|
case HashSHA1:
|
|
return tpm2.AlgSHA1
|
|
case HashSHA256:
|
|
return tpm2.AlgSHA256
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// String returns a human-friendly representation of the hash algorithm.
|
|
func (a HashAlg) String() string {
|
|
switch a {
|
|
case HashSHA1:
|
|
return "SHA1"
|
|
case HashSHA256:
|
|
return "SHA256"
|
|
}
|
|
return fmt.Sprintf("HashAlg<%d>", int(a))
|
|
}
|
|
|
|
// 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 version of the TPM which generated this attestation.
|
|
TPMVersion TPMVersion
|
|
// 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")
|
|
// ErrTPM12NotImplemented is returned in response to methods which
|
|
// need to interact with the TPM1.2 device in ways that have not
|
|
// yet been implemented.
|
|
ErrTPM12NotImplemented = errors.New("TPM 1.2 support not yet implemented")
|
|
)
|
|
|
|
// TPMInfo contains information about the version & interface
|
|
// of an open TPM.
|
|
type TPMInfo struct {
|
|
Version TPMVersion
|
|
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
|
|
}
|
|
|
|
// probedTPM identifies a TPM device on the system, which
|
|
// is a candidate for being used.
|
|
type probedTPM struct {
|
|
Version TPMVersion
|
|
Path string
|
|
}
|
|
|
|
// MatchesConfig returns true if the TPM satisfies the constraints
|
|
// specified by the given config.
|
|
func (t *probedTPM) MatchesConfig(config OpenConfig) bool {
|
|
return config.TPMVersion == TPMVersionAgnostic || t.Version == config.TPMVersion
|
|
}
|
|
|
|
// 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 {
|
|
if config.TPMVersion > TPMVersionAgnostic && config.TPMVersion != TPMVersion20 {
|
|
return nil, errors.New("command channel can only be used as a TPM 2.0 device")
|
|
}
|
|
return &TPM{&wrappedTPM20{
|
|
interf: TPMInterfaceCommandChannel,
|
|
rwc: config.CommandChannel,
|
|
}}, nil
|
|
}
|
|
|
|
candidateTPMs, err := probeSystemTPMs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, tpm := range candidateTPMs {
|
|
if tpm.MatchesConfig(*config) {
|
|
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) {
|
|
if config == nil {
|
|
config = defaultOpenConfig
|
|
}
|
|
|
|
candidateTPMs, err := probeSystemTPMs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var out []TPMInfo
|
|
|
|
for _, tpm := range candidateTPMs {
|
|
if tpm.MatchesConfig(*config) {
|
|
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
|
|
}
|