mirror of
https://github.com/google/go-attestation.git
synced 2024-12-19 04:57:59 +00:00
b128fd7448
Moving AIK to storage hierarchy so that the key blob can be saved and loaded instead of recreating the aik each time.
542 lines
14 KiB
Go
542 lines
14 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.
|
|
|
|
// +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
|
|
}
|
|
|
|
var keyHnd tpmutil.Handle
|
|
switch pHnd {
|
|
case commonSrkEquivalentHandle:
|
|
keyHnd, _, err = tpm2.CreatePrimary(t.rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", defaultSRKTemplate)
|
|
case commonEkEquivalentHandle:
|
|
keyHnd, _, err = tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
|
|
}
|
|
if err != nil {
|
|
return 0, false, fmt.Errorf("CreatePrimary failed: %v", err)
|
|
}
|
|
defer tpm2.FlushContext(t.rwc, keyHnd)
|
|
|
|
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, pHnd)
|
|
if err != nil {
|
|
return 0, false, fmt.Errorf("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.
|
|
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get SRK handle: %v", err)
|
|
}
|
|
|
|
_, blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", aikTemplate)
|
|
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 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: KeyEncodingEncrypted,
|
|
TPMVersion: t.version,
|
|
Purpose: AttestationKey,
|
|
KeyBlob: blob,
|
|
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 != KeyEncodingEncrypted {
|
|
return nil, fmt.Errorf("unsupported key encoding: %x", k.KeyEncoding)
|
|
}
|
|
|
|
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get SRK handle: %v", err)
|
|
}
|
|
if k.hnd, _, err = tpm2.Load(t.rwc, srk, "", k.Public, k.KeyBlob); err != nil {
|
|
return nil, fmt.Errorf("Load 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")
|
|
}
|