mirror of
https://github.com/google/go-attestation.git
synced 2025-06-18 23:18:19 +00:00
Initial commit.
This commit is contained in:
520
attest/tpm_linux.go
Normal file
520
attest/tpm_linux.go
Normal file
@ -0,0 +1,520 @@
|
||||
// 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.
|
||||
// +build linux
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
|
||||
"github.com/google/go-tspi/tspi" //for tpm12 support
|
||||
"github.com/google/go-tspi/tspiconst"
|
||||
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
"github.com/google/go-tspi/attestation"
|
||||
)
|
||||
|
||||
const (
|
||||
tpmRoot = "/sys/class/tpm"
|
||||
)
|
||||
|
||||
// TPM interfaces with a TPM device on the system.
|
||||
type TPM struct {
|
||||
version TPMVersion
|
||||
interf TPMInterface
|
||||
|
||||
sysPath string
|
||||
rwc io.ReadWriteCloser
|
||||
ctx *tspi.Context
|
||||
}
|
||||
|
||||
func probeSystemTPMs() ([]probedTPM, error) {
|
||||
var tpms []probedTPM
|
||||
|
||||
tpmDevs, err := ioutil.ReadDir(tpmRoot)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if err == nil {
|
||||
for _, tpmDev := range tpmDevs {
|
||||
if strings.HasPrefix(tpmDev.Name(), "tpm") {
|
||||
tpm := probedTPM{
|
||||
Path: path.Join(tpmRoot, tpmDev.Name()),
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(tpm.Path, "caps")); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
tpm.Version = TPMVersion20
|
||||
} else {
|
||||
tpm.Version = TPMVersion12
|
||||
}
|
||||
tpms = append(tpms, tpm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tpms, nil
|
||||
}
|
||||
|
||||
func openTPM(tpm probedTPM) (*TPM, error) {
|
||||
interf := TPMInterfaceDirect
|
||||
var rwc io.ReadWriteCloser
|
||||
var ctx *tspi.Context
|
||||
var err error
|
||||
|
||||
switch tpm.Version {
|
||||
case TPMVersion12:
|
||||
// TPM1.2 must be using Daemon (Connect will fail if not the case)
|
||||
interf = TPMInterfaceDaemonManaged
|
||||
ctx, err = tspi.NewContext()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ctx.Connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case TPMVersion20:
|
||||
// If the TPM has a kernel-provided resource manager, we should
|
||||
// use that instead of communicating directly.
|
||||
devPath := path.Join("/dev", path.Base(tpm.Path))
|
||||
f, err := ioutil.ReadDir(path.Join(tpm.Path, "device", "tpmrm"))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(f) > 0 {
|
||||
devPath = path.Join("/dev", f[0].Name())
|
||||
interf = TPMInterfaceKernelManaged
|
||||
}
|
||||
|
||||
rwc, err = tpm2.OpenTPM(devPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TPM{
|
||||
version: tpm.Version,
|
||||
interf: interf,
|
||||
sysPath: tpm.Path,
|
||||
rwc: rwc,
|
||||
ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close shuts down the connection to the TPM.
|
||||
func (t *TPM) Close() error {
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
return t.ctx.Close()
|
||||
case TPMVersion20:
|
||||
return t.rwc.Close()
|
||||
default:
|
||||
return fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
}
|
||||
}
|
||||
|
||||
func readTPM12VendorAttributes(context *tspi.Context) (TCGVendorID, string, error) {
|
||||
// TPM 1.2 doesn't seem to store vendor data (other than unique ID)
|
||||
vendor, err := context.GetCapability(tspiconst.TSS_TPMCAP_PROPERTY, 4, tspiconst.TSS_TPMCAP_PROP_MANUFACTURER)
|
||||
if err != nil {
|
||||
return TCGVendorID(0), "", fmt.Errorf("tspi::Context::GetCapability failed: %v", err)
|
||||
}
|
||||
if len(vendor) > 4 {
|
||||
return TCGVendorID(0), "", fmt.Errorf("expecting at most 32-bit VendorID, got %d-bit ID instead", len(vendor)*8)
|
||||
}
|
||||
vendorID := TCGVendorID(binary.BigEndian.Uint32(vendor))
|
||||
return vendorID, vendorID.String(), nil
|
||||
}
|
||||
|
||||
// Info returns information about the TPM.
|
||||
func (t *TPM) Info() (*TPMInfo, error) {
|
||||
var manufacturer TCGVendorID
|
||||
var vendorInfo string
|
||||
var err error
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
manufacturer, vendorInfo, err = readTPM12VendorAttributes(t.ctx)
|
||||
case TPMVersion20:
|
||||
manufacturer, vendorInfo, err = readTPM2VendorAttributes(t.rwc)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TPMInfo{
|
||||
Version: t.version,
|
||||
Interface: t.interf,
|
||||
VendorInfo: vendorInfo,
|
||||
Manufacturer: manufacturer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Return value: handle, whether we generated a new one, error
|
||||
func (t *TPM) getPrimaryKeyHandle(pHnd tpmutil.Handle) (tpmutil.Handle, bool, error) {
|
||||
_, _, _, err := tpm2.ReadPublic(t.rwc, pHnd)
|
||||
if err == nil {
|
||||
// Found the persistent handle, assume it's the key we want.
|
||||
return pHnd, false, nil
|
||||
}
|
||||
|
||||
ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("EK CreatePrimary failed: %v", err)
|
||||
}
|
||||
defer tpm2.FlushContext(t.rwc, ekHnd)
|
||||
|
||||
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, ekHnd, pHnd)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("EK EvictControl failed: %v", err)
|
||||
}
|
||||
|
||||
return pHnd, true, nil
|
||||
}
|
||||
|
||||
func readEKCertFromNVRAM12(ctx *tspi.Context) (*x509.Certificate, error) {
|
||||
ekCert, err := attestation.GetEKCert(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading EK cert: %v", err)
|
||||
}
|
||||
return parseCert(ekCert)
|
||||
}
|
||||
|
||||
// EKs returns the endorsement keys burned-in to the platform.
|
||||
func (t *TPM) EKs() ([]PlatformEK, error) {
|
||||
var cert *x509.Certificate
|
||||
var err error
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
cert, err = readEKCertFromNVRAM12(t.ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err)
|
||||
}
|
||||
|
||||
case TPMVersion20:
|
||||
cert, err = readEKCertFromNVRAM20(t.rwc)
|
||||
|
||||
if err != nil {
|
||||
ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
|
||||
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")
|
||||
}
|
||||
|
||||
return []PlatformEK{
|
||||
{nil, &rsa.PublicKey{E: int(pub.RSAParameters.Exponent), N: pub.RSAParameters.Modulus}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
}
|
||||
|
||||
return []PlatformEK{
|
||||
{cert, cert.PublicKey},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Key represents a key bound to the TPM.
|
||||
type Key struct {
|
||||
hnd tpmutil.Handle
|
||||
KeyEncoding KeyEncoding
|
||||
TPMVersion TPMVersion
|
||||
Purpose KeyPurpose
|
||||
|
||||
KeyBlob []byte // exclusive to TPM1.2
|
||||
Public []byte // used by both TPM1.2 and 2.0
|
||||
CreateData []byte
|
||||
CreateAttestation []byte
|
||||
CreateSignature []byte
|
||||
}
|
||||
|
||||
// Marshal represents the key in a persistent format which may be
|
||||
// loaded at a later time using tpm.LoadKey().
|
||||
func (k *Key) Marshal() ([]byte, error) {
|
||||
return json.Marshal(k)
|
||||
}
|
||||
|
||||
// Close frees any resources associated with the key.
|
||||
func (k *Key) Close(tpm *TPM) error {
|
||||
switch tpm.version {
|
||||
case TPMVersion12:
|
||||
return nil
|
||||
case TPMVersion20:
|
||||
return tpm2.FlushContext(tpm.rwc, k.hnd)
|
||||
default:
|
||||
return fmt.Errorf("unsupported TPM version: %x", tpm.version)
|
||||
}
|
||||
}
|
||||
|
||||
// ActivateCredential decrypts the specified credential using key.
|
||||
// This operation is synonymous with TPM2_ActivateCredential.
|
||||
func (k *Key) ActivateCredential(t *TPM, in EncryptedCredential) ([]byte, error) {
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
cred, err := attestation.AIKChallengeResponse(t.ctx, k.KeyBlob, in.Credential, in.Secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to refresh aik: %v", err)
|
||||
}
|
||||
return cred, nil
|
||||
|
||||
case TPMVersion20:
|
||||
ekHnd, _, err := t.getPrimaryKeyHandle(commonEkEquivalentHandle)
|
||||
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, in.Credential[2:], in.Secret[2:])
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Key) quote12(ctx *tspi.Context, nonce []byte) (*Quote, error) {
|
||||
quote, rawSig, err := attestation.GetQuote(ctx, k.KeyBlob, nonce)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetQuote() failed: %v", err)
|
||||
}
|
||||
|
||||
return &Quote{
|
||||
Version: TPMVersion12,
|
||||
Quote: quote,
|
||||
Signature: rawSig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Quote returns a quote over the platform state, signed by the key.
|
||||
func (k *Key) Quote(t *TPM, nonce []byte, alg tpm2.Algorithm) (*Quote, error) {
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
return k.quote12(t.ctx, nonce)
|
||||
|
||||
case TPMVersion20:
|
||||
return quote20(t.rwc, k.hnd, alg, nonce)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
}
|
||||
}
|
||||
|
||||
// MintAIK creates an attestation key.
|
||||
func (t *TPM) MintAIK(opts *MintOptions) (*Key, error) {
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
pub, blob, err := attestation.CreateAIK(t.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateAIK failed: %v", err)
|
||||
}
|
||||
|
||||
return &Key{
|
||||
KeyEncoding: KeyEncodingEncrypted,
|
||||
TPMVersion: t.version,
|
||||
Purpose: AttestationKey,
|
||||
KeyBlob: blob,
|
||||
Public: pub,
|
||||
}, nil
|
||||
|
||||
case TPMVersion20:
|
||||
// TODO(jsonp): Abstract choice of hierarchy & parent.
|
||||
keyHandle, pub, creationData, creationHash, tix, _, err := tpm2.CreatePrimaryEx(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", aikTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreatePrimaryEx failed: %v", err)
|
||||
}
|
||||
|
||||
// If any errors occur, free the AIK'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, tpm2.SigScheme{tpm2.AlgRSASSA, tpm2.AlgSHA256, 0}, tix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CertifyCreation failed: %v", err)
|
||||
}
|
||||
// Pack the raw structure into a TPMU_SIGNATURE.
|
||||
signature, err := tpmutil.Pack(tpm2.AlgRSASSA, tpm2.AlgSHA256, sig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pack TPMT_SIGNATURE: %v", err)
|
||||
}
|
||||
|
||||
return &Key{
|
||||
hnd: keyHandle,
|
||||
KeyEncoding: KeyEncodingParameterized,
|
||||
TPMVersion: t.version,
|
||||
Purpose: AttestationKey,
|
||||
Public: pub,
|
||||
CreateData: creationData,
|
||||
CreateAttestation: attestation,
|
||||
CreateSignature: signature,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadKey loads a previously-created key into the TPM for use.
|
||||
// A key loaded via this function needs to be closed with .Close().
|
||||
func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) {
|
||||
// TODO(b/124266168): Load under the key handle loaded by t.getPrimaryKeyHandle()
|
||||
|
||||
var k Key
|
||||
var err error
|
||||
if err = json.Unmarshal(opaqueBlob, &k); err != nil {
|
||||
return nil, fmt.Errorf("Unmarshal failed: %v", err)
|
||||
}
|
||||
|
||||
if k.TPMVersion != t.version {
|
||||
return nil, errors.New("key TPM version does not match opened TPM")
|
||||
}
|
||||
if k.Purpose != AttestationKey {
|
||||
return nil, fmt.Errorf("unsupported key kind: %x", k.Purpose)
|
||||
}
|
||||
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
if k.KeyEncoding != KeyEncodingEncrypted {
|
||||
return nil, fmt.Errorf("unsupported key encoding: %x", k.KeyEncoding)
|
||||
}
|
||||
|
||||
case TPMVersion20:
|
||||
if k.KeyEncoding != KeyEncodingParameterized {
|
||||
return nil, fmt.Errorf("unsupported key encoding: %x", k.KeyEncoding)
|
||||
}
|
||||
if k.hnd, _, _, _, _, _, err = tpm2.CreatePrimaryEx(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", aikTemplate); err != nil {
|
||||
return nil, fmt.Errorf("CreatePrimaryEx failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &k, nil
|
||||
}
|
||||
|
||||
// allPCRs12 returns a map of all the PCR values on the TPM
|
||||
func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
|
||||
tpm := ctx.GetTPM()
|
||||
PCRlist, err := tpm.GetPCRValues()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read PCRs: %v", err)
|
||||
}
|
||||
|
||||
PCRs := make(map[uint32][]byte)
|
||||
for i := 0; i < len(PCRlist); i++ {
|
||||
PCRs[(uint32)(i)] = PCRlist[i]
|
||||
}
|
||||
return PCRs, nil
|
||||
}
|
||||
|
||||
// PCRs returns the present value of all Platform Configuration Registers.
|
||||
func (t *TPM) PCRs() (map[int]PCR, tpm2.Algorithm, error) {
|
||||
var PCRs map[uint32][]byte
|
||||
var alg crypto.Hash
|
||||
var err error
|
||||
|
||||
switch t.version {
|
||||
case TPMVersion12:
|
||||
PCRs, err = allPCRs12(t.ctx)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to read PCRs: %v", err)
|
||||
}
|
||||
alg = crypto.SHA1
|
||||
|
||||
case TPMVersion20:
|
||||
PCRs, alg, err = allPCRs20(t.rwc)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to read PCRs: %v", err)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("unsupported TPM version: %x", t.version)
|
||||
}
|
||||
|
||||
out := map[int]PCR{}
|
||||
var lastAlg crypto.Hash
|
||||
for index, digest := range PCRs {
|
||||
out[int(index)] = PCR{
|
||||
Index: int(index),
|
||||
Digest: digest,
|
||||
DigestAlg: alg,
|
||||
}
|
||||
lastAlg = alg
|
||||
}
|
||||
|
||||
switch lastAlg {
|
||||
case crypto.SHA1:
|
||||
return out, tpm2.AlgSHA1, nil
|
||||
case crypto.SHA256:
|
||||
return out, tpm2.AlgSHA256, nil
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("unexpected algorithm: %v", lastAlg)
|
||||
}
|
||||
}
|
||||
|
||||
// MeasurementLog returns the present value of the System Measurement Log.
|
||||
func (t *TPM) MeasurementLog() ([]byte, error) {
|
||||
return ioutil.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements")
|
||||
}
|
Reference in New Issue
Block a user