linux/tpm.go (275 lines of code) (raw):
// +build linux
// Copyright (c) Facebook, Inc. and its affiliates.
//
// 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 linux
// This file contains functions for a cryptographic processor (such as a TPM)
import (
"errors"
"fmt"
"os"
"github.com/facebookincubator/sks/diskio"
"github.com/facebookincubator/sks/utils"
"github.com/facebookincubator/flog"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil"
)
// GetCryptoprocessor handles the logic of determining whether to use a physical
// TPM or an on-disk implementation. Set `path` to the absolute path of the TPM
// device to interface with. Unless you have a good reason, this should be set
// to `/dev/tpmrm0`.
// TODO: Only physical TPM is currently supported, support not having one. Once
// support is added, use the existence/mode of `path` to decide whether to use
// a physical TPM or an on-disk implementation
func GetCryptoprocessor(path string) (Cryptoprocessor, error) {
// Check if the TPM path exists
info, err := os.Stat(path)
if err != nil {
return nil, err
}
var tpm Cryptoprocessor
if (os.ModeDevice|os.ModeCharDevice)&info.Mode() != 0 {
tpm = &tpmDevice{
path: path,
}
} else {
return nil, fmt.Errorf(
"path %q is not a character device, only TPMs are supported",
path,
)
}
return tpm, nil
}
func (tpm *tpmDevice) Initialize() error {
if tpm == nil {
return errors.New("cannot insnantiate a nil instance")
}
rwc, err := tpm2.OpenTPM(tpm.path)
if err != nil {
return err
}
tpm.rwc = rwc
flog.Debugf("Loaded TPM device: %s", tpm.path)
db, err := diskio.OpenDB()
if err != nil {
return err
}
handles := make(map[string]tpmutil.Handle)
err = db.Visit(func(key string, val []byte) error {
var keyobj tpmKey
if err := utils.UnmarshalBytes(val, &keyobj); err != nil {
flog.Criticalf("Failed to unmarshal key %q: %+v", key, err)
return err
}
handles[key] = keyobj.Handle
flog.Debugf("Found handle for key %q: %#x", key, keyobj.Handle)
return nil
})
if err != nil {
flog.Criticalf("Failed to init key handler: %+v", err)
return err
}
tpm.keyHandler = NewKeyHandler(handles)
flog.Debugf("Initialized key handler")
return nil
}
func (tpm *tpmDevice) Close() error {
if tpm == nil || tpm.rwc == nil {
return errors.New("TPM session is already closed")
}
tpm.rwc.Close()
tpm.rwc = nil
tpm.path = ""
tpm = nil
return nil
}
func (tpm *tpmDevice) FlushKey(key CryptoKey, closeKey bool) error {
err := tpm2.FlushContext(tpm.rwc, key.GetLoadedHandle())
if err != nil {
return err
}
if closeKey {
err = key.Close()
if err != nil {
return err
}
}
return nil
}
func (tpm *tpmDevice) GenerateKey(parent tpmutil.Handle, keyID string, persistentHandle tpmutil.Handle, template *tpm2.Public) (CryptoKey, error) {
db, err := diskio.OpenDB()
if err != nil {
return nil, err
}
key := &tpmKey{}
if parent == tpm2.HandleEndorsement || parent == tpm2.HandleOwner ||
parent == tpm2.HandleNull {
flog.Debugf("Generating a primary key under hierarchy 0x%x", parent)
// Yes, the canonical Golang way is to use short assignment. But that
// blocks directly using the existing fields in the TPMKey struct. Go
// complain to https://github.com/golang/go/issues/30318
var publicArea []byte
var creationData []byte
var keyName []byte
var err error
key.Handle, publicArea, creationData, key.CreationHash, key.Ticket, keyName, err = tpm2.CreatePrimaryEx(
tpm.rwc,
parent,
// We are not sealing this key to any PCR state
tpm2.PCRSelection{},
"",
"",
// Changing this template will change the primary key that gets
// generated. Modify this at your peril.
DefaultECCEKTemplate(),
)
if err != nil {
flog.Debugf("Error in CreatePrimaryEx: %+v", err)
return nil, err
}
err = key.FillKeyData(publicArea, nil, creationData, keyName)
if err != nil {
return nil, err
}
flog.Debugf("Generated primary key at handle 0x%x", key.Handle)
return key, nil
}
flog.Debugf("Generating new key under parent handle 0x%x", parent)
var keyTmpl tpm2.Public
if template == nil {
keyTmpl = DefaultECCKeyTemplate()
} else {
keyTmpl = *template
}
flog.Debugf("Using template: %+v", keyTmpl)
privateBlob, publicBlob, _, _, _, err := tpm2.CreateKey(
tpm.rwc,
parent,
// We are not sealing this key to any PCR state
tpm2.PCRSelection{},
"",
"",
keyTmpl,
)
if err != nil {
flog.Criticalf("Error generating SRK: %+v", err)
return nil, err
}
flog.Debug("Created new key")
err = key.FillKeyData(publicBlob, privateBlob, nil, nil)
if err != nil {
return nil, err
}
key.Parent = parent
if persistentHandle > 0 {
// Need to load the key, then evict it, and unload it when we're done
loadedHandle, _, err := tpm2.Load(
tpm.rwc,
parent,
"",
publicBlob,
privateBlob,
)
if err != nil {
flog.Criticalf("Failed to load new key: %+v", err)
return nil, err
}
defer tpm2.FlushContext(tpm.rwc, loadedHandle)
flog.Debugf("Loaded new key at handle 0x%x", loadedHandle)
// Attempt to remove the key stored at persistentHandle, in case one
// already exists in the TPM and/or disk cache.
err = tpm.doKeyDeletion(keyID, persistentHandle, false)
if err != nil {
flog.Errorf(
"Failed deleting key at NV index 0x%x: %+v",
persistentHandle,
err,
)
}
err = tpm2.EvictControl(
tpm.rwc,
"",
// You may note elsewhere we state we want only the endorsement
// hierarchy because it's privacy-sensitive, but here we use Owner.
// Owner isn't a hierarchy, it's an authorization handle, but the
// go-tpm library doesn't distinguish between handle types in the
// variable names.
tpm2.HandleOwner,
loadedHandle,
persistentHandle,
)
if err != nil {
flog.Criticalf(
"Failed to evict new key to persistent storage: %+v", err)
return nil, err
}
key.Handle = persistentHandle
flog.Debugf("Key persisted to storage at handle 0x%x", persistentHandle)
// Now save the key so we can use it later, needed for loading the key
// into the TPM once it's been flushed.S
keyBytes, err := utils.MarshalBytes(key)
if err != nil {
flog.Criticalf("Failed to marshal key: %+v", err)
return nil, err
}
_, err = db.Save(keyID, keyBytes)
if err != nil {
flog.Criticalf("Failed to save marshaled key to disk: %+v", err)
return nil, err
}
flog.Debugf("Key marshaled with identifier %s", keyID)
}
return key, nil
}
func (tpm *tpmDevice) LoadKey(keyID string, parentHandle, persistentHandle tpmutil.Handle, template *tpm2.Public) (CryptoKey, error) {
cpKey, err := tpm.LoadDiskKey(keyID)
if err != nil {
return nil, err
}
if cpKey == nil || cpKey.IsEmpty() {
flog.Warningf("Key '%s' not found, attempting to create it", keyID)
cpKey, err = tpm.GenerateKey(
parentHandle, keyID, persistentHandle, template)
if err != nil {
return nil, err
}
if cpKey == nil || cpKey.IsEmpty() {
return nil, errors.New("failed to load key: empty key found")
}
}
loadedHandle, _, err := tpm2.Load(
tpm.rwc,
parentHandle,
"",
cpKey.GetPublicBytes(),
cpKey.GetPrivateBytes(),
)
if err != nil {
return nil, err
}
cpKey.SetLoadedHandle(loadedHandle)
return cpKey, nil
}
func (tpm *tpmDevice) GetOrgRootKey() (CryptoKey, error) {
// Get the organization root key
// We explicitly only want to use the Endorsement Hierarchy, it's the only
// privacy-sensitive hierarchy and the one explicitly recommended for use
// when there are privacy considerations.
primaryKey, err := tpm.GenerateKey(tpm2.HandleEndorsement, "", 0, nil)
if err != nil {
flog.Debugf("Error generating new primary key: %+v", err)
return nil, err
}
defer tpm2.FlushContext(tpm.rwc, primaryKey.GetHandle())
flog.Debug("Generated primary key")
// Try to load the organization root key, create it if it doesn't exist
rootKeyTmpl := DefaultECCEKTemplate()
rootKey, err := tpm.LoadKey(
diskio.OrgRootKey,
primaryKey.GetHandle(),
TPMOrgSRKHandle,
&rootKeyTmpl,
)
if err != nil {
flog.Criticalf("Error loading organization root key: %+v", err)
return nil, err
}
flog.Debugf(
"Found organization root key with handle 0x%x", rootKey.GetHandle())
return rootKey, nil
}
func (tpm *tpmDevice) LoadDiskKey(keyID string) (CryptoKey, error) {
db, err := diskio.OpenDB()
if err != nil {
return nil, err
}
keyBytes, err := db.Load(keyID)
if err != nil {
flog.Warningf("Got error loading key from disk: %+v", err)
return nil, nil
}
if len(keyBytes) <= 0 {
flog.Warning("Loaded key file but got no data")
return nil, nil
}
flog.Debugf("Attempting to unmarshal key '%s'", keyID)
var keyobj tpmKey
if err := utils.UnmarshalBytes(keyBytes, &keyobj); err != nil {
flog.Criticalf("Failed to unmarshal key '%s': %+v", keyID, err)
return nil, err
}
if keyobj.IsEmpty() {
flog.Warningf(
"Key '%s' loaded, but has no data; generate a new key",
keyID,
)
return nil, nil
}
keyobj.FillKeyData(keyobj.PublicBytes, nil, nil, nil)
return &keyobj, nil
}
// vim: filetype=go:noexpandtab:ts=2:sts=2:sw=2:autoindent