-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create individual packages for Windows and Linux TPM transport (#369)
* Create individual packages for Windows and Linux TPM transport #364 called to attention some long-standing technical debt around TPM transport. In particular, the stack looks like: (Linux or Windows) `OpenTPM` function calls the legacy `OpenTPM` function calls the tpmutil `OpenTPM` function At the bottom of the stack, tpmutil does some runtime introspection to see what type of TPM it wants to open (e.g., on Linux, the device could be either a device file or a socket). This runtime support is convenient, but also breaks dead-code elimination (for example, tinygo will fail to compile the UDS support code, and users have no way of leaving that out without patches). In principle, we've found within Google that "open my TPM" should be as un-smart as possible, to avoid awkward edge cases (for example, what happens if the logic finds two different TPMs on the system; which should it prefer; should it invisibly succeed and surprise the user?). Instead, the preferred pattern is to require the user to explicitly say which TPM they are trying to open. This change introduces 3 packages as a replacement for `transport.OpenTPM` (which this change marks as now Deprecated): `transport/linuxtpm.Open(path)` opens Linux device TPMs (e.g., /dev/tpm0 or /dev/tpmrm0) `transport/linuxudstpm.Open(path)` opens Linux Unix Domain Socket TPMs `transport/windowstpm.Open()` opens the TPM from TBS.dll Intentionally, the now-deprecated `transport.OpenTPM` is not touched. This would create an import cycle. * Add small tests for each of the openers * fix lint * fix linuxudstpm and test * fix the test in the case that the UDS simulator is not running * remove extraneous test for windows
- Loading branch information
1 parent
ec70209
commit d96ccf7
Showing
10 changed files
with
360 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//go:build !windows | ||
|
||
// Package linuxtpm provides access to a physical TPM device via the device file. | ||
package linuxtpm | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/google/go-tpm/tpm2/transport" | ||
) | ||
|
||
var ( | ||
// ErrFileIsNotDevice indicates that the TPM file mode was not a device. | ||
ErrFileIsNotDevice = errors.New("TPM file is not a device") | ||
) | ||
|
||
// Open opens the TPM device file at the given path. | ||
func Open(path string) (transport.TPMCloser, error) { | ||
fi, err := os.Stat(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if fi.Mode()&os.ModeDevice == 0 { | ||
return nil, fmt.Errorf("%w: %s (%s)", ErrFileIsNotDevice, fi.Mode().String(), path) | ||
} | ||
var f *os.File | ||
f, err = os.OpenFile(path, os.O_RDWR, 0600) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return transport.FromReadWriteCloser(f), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
//go:build !windows | ||
|
||
package linuxtpm | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/google/go-tpm/tpm2/transport" | ||
testhelper "github.com/google/go-tpm/tpm2/transport/test" | ||
) | ||
|
||
func open(path string) func() (transport.TPMCloser, error) { | ||
return func() (transport.TPMCloser, error) { | ||
return Open(path) | ||
} | ||
} | ||
|
||
func TestLocalTPM(t *testing.T) { | ||
testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrFileIsNotDevice}, open("/dev/tpm0")) | ||
} | ||
|
||
func TestLocalResourceManagedTPM(t *testing.T) { | ||
testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrFileIsNotDevice}, open("/dev/tpmrm0")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
//go:build !windows | ||
|
||
// Package linuxudstpm provides access to a TPM device via a Unix domain socket. | ||
package linuxudstpm | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net" | ||
"os" | ||
|
||
"github.com/google/go-tpm/tpm2/transport" | ||
) | ||
|
||
var ( | ||
// ErrFileIsNotSocket indicates that the TPM file is not a socket. | ||
ErrFileIsNotSocket = errors.New("TPM file is not a socket") | ||
// ErrMustCallWriteThenRead indicates that the file was not written-then-read in the expected pattern. | ||
ErrMustCallWriteThenRead = errors.New("must call Write then Read in an alternating sequence") | ||
// ErrNotOpen indicates that the TPM file is not currently open. | ||
ErrNotOpen = errors.New("no connection is open") | ||
) | ||
|
||
// Open opens the TPM socket at the given path. | ||
func Open(path string) (transport.TPMCloser, error) { | ||
fi, err := os.Stat(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if fi.Mode()&os.ModeSocket == 0 { | ||
return nil, fmt.Errorf("%w: %s (%s)", ErrFileIsNotSocket, fi.Mode().String(), path) | ||
} | ||
return transport.FromReadWriteCloser(newEmulatorReadWriteCloser(path)), nil | ||
} | ||
|
||
// dialer abstracts the net.Dial call so test code can provide its own net.Conn | ||
// implementation. | ||
type dialer func(network, path string) (net.Conn, error) | ||
|
||
// emulatorReadWriteCloser manages connections with a TPM emulator over a Unix | ||
// domain socket. These emulators often operate in a write/read/disconnect | ||
// sequence, so the Write method always connects, and the Read method always | ||
// closes. emulatorReadWriteCloser is not thread safe. | ||
type emulatorReadWriteCloser struct { | ||
path string | ||
conn net.Conn | ||
dialer dialer | ||
} | ||
|
||
// newEmulatorReadWriteCloser stores information about a Unix domain socket to | ||
// write to and read from. | ||
func newEmulatorReadWriteCloser(path string) *emulatorReadWriteCloser { | ||
return &emulatorReadWriteCloser{ | ||
path: path, | ||
dialer: net.Dial, | ||
} | ||
} | ||
|
||
// Read implements the io.Reader interface. | ||
func (erw *emulatorReadWriteCloser) Read(p []byte) (int, error) { | ||
// Read is always the second operation in a Write/Read sequence. | ||
if erw.conn == nil { | ||
return 0, ErrMustCallWriteThenRead | ||
} | ||
n, err := erw.conn.Read(p) | ||
erw.conn.Close() | ||
erw.conn = nil | ||
return n, err | ||
} | ||
|
||
// Write implements the io.Writer interface. | ||
func (erw *emulatorReadWriteCloser) Write(p []byte) (int, error) { | ||
if erw.conn != nil { | ||
return 0, ErrMustCallWriteThenRead | ||
} | ||
var err error | ||
erw.conn, err = erw.dialer("unix", erw.path) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return erw.conn.Write(p) | ||
} | ||
|
||
// Close implements the io.Closer interface. | ||
func (erw *emulatorReadWriteCloser) Close() error { | ||
if erw.conn == nil { | ||
// This is an expected possible state, e.g., if someone sent the TPM a command and didn't read the response. | ||
return nil | ||
} | ||
err := erw.conn.Close() | ||
erw.conn = nil | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
//go:build !windows | ||
|
||
package linuxudstpm | ||
|
||
import ( | ||
"flag" | ||
"os" | ||
"syscall" | ||
"testing" | ||
|
||
"github.com/google/go-tpm/tpm2/transport" | ||
testhelper "github.com/google/go-tpm/tpm2/transport/test" | ||
) | ||
|
||
var tpmSocket = flag.String("tpm_socket", "/dev/tpm0", "path to the TPM simulator UDS") | ||
|
||
func TestMain(m *testing.M) { | ||
flag.Parse() | ||
os.Exit(m.Run()) | ||
} | ||
|
||
func open() func() (transport.TPMCloser, error) { | ||
return func() (transport.TPMCloser, error) { | ||
return Open(*tpmSocket) | ||
} | ||
} | ||
|
||
func TestLocalUDSTPM(t *testing.T) { | ||
testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrFileIsNotSocket, syscall.ECONNREFUSED}, open()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Package testhelper provides some helper code for TPM transport tests. | ||
package testhelper | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"errors" | ||
"testing" | ||
|
||
"github.com/google/go-tpm/tpm2" | ||
"github.com/google/go-tpm/tpm2/transport" | ||
) | ||
|
||
// RunTest checks that the connection to the given TPM seems to be working. | ||
func RunTest(t *testing.T, skipErrs []error, tpmOpener func() (transport.TPMCloser, error)) { | ||
tpm, err := tpmOpener() | ||
for _, skipErr := range skipErrs { | ||
if errors.Is(err, skipErr) { | ||
t.Skipf("%v", err) | ||
} | ||
} | ||
if err != nil { | ||
t.Fatalf("Failed to open TPM: %v", err) | ||
} | ||
defer func(tpm transport.TPMCloser) { | ||
if err := tpm.Close(); err != nil { | ||
t.Fatalf("tpm.Close() = %v", err) | ||
} | ||
}(tpm) | ||
|
||
// Ping the TPM to ask it what the manufacturer is, as a basic consistency check. | ||
cap, err := tpm2.GetCapability{ | ||
Capability: tpm2.TPMCapTPMProperties, | ||
Property: uint32(tpm2.TPMPTManufacturer), | ||
PropertyCount: 1, | ||
}.Execute(tpm) | ||
|
||
// We might run into one of the known "skip if this error" cases. | ||
for _, skipErr := range skipErrs { | ||
if errors.Is(err, skipErr) { | ||
t.Skipf("%v", err) | ||
} | ||
} | ||
if err != nil { | ||
t.Fatalf("GetCapability() = %v", err) | ||
} | ||
props, err := cap.CapabilityData.Data.TPMProperties() | ||
if err != nil { | ||
t.Fatalf("cap.TPMProperties() = %v", err) | ||
} | ||
if len(props.TPMProperty) != 1 { | ||
t.Fatalf("GetCapability() = %v properties, want 1", len(props.TPMProperty)) | ||
} | ||
|
||
var idBuf bytes.Buffer | ||
idBuf.Grow(4) | ||
binary.Write(&idBuf, binary.BigEndian, props.TPMProperty[0].Value) | ||
t.Logf("Manufacturer ID: %q", idBuf.String()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//go:build windows | ||
|
||
// Package windowstpm implements the TPM transport on Windows using tbs.dll. | ||
package windowstpm | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/google/go-tpm/tpm2/transport" | ||
"github.com/google/go-tpm/tpmutil/tbs" | ||
) | ||
|
||
var ( | ||
// ErrNotTPM20 indicates that a TPM 2.0 was not found. | ||
ErrNotTPM20 = errors.New("device is not a TPM 2.0") | ||
) | ||
|
||
const ( | ||
maxTPMResponse = 4096 | ||
) | ||
|
||
// Open opens a channel to the TPM via TBS. | ||
func Open() (transport.TPMCloser, error) { | ||
info, err := tbs.GetDeviceInfo() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if info.TPMVersion != tbs.TPMVersion20 { | ||
return nil, fmt.Errorf("%w: %v", ErrNotTPM20, info.TPMVersion) | ||
} | ||
|
||
tpmContext, err := tbs.CreateContext(tbs.TPMVersion20, tbs.IncludeTPM20) | ||
rwc := &winTPMBuffer{ | ||
context: tpmContext, | ||
outBuffer: make([]byte, 0, maxTPMResponse), | ||
} | ||
return transport.FromReadWriteCloser(rwc), err | ||
} | ||
|
||
// winTPMBuffer is a ReadWriteCloser to access the TPM in Windows. | ||
type winTPMBuffer struct { | ||
context tbs.Context | ||
outBuffer []byte | ||
} | ||
|
||
// Write implements the io.Writer interface. | ||
// | ||
// Executes the TPM command specified by commandBuffer (at Normal Priority), returning the number | ||
// of bytes in the command and any error code returned by executing the TPM command. Command | ||
// response can be read by calling Read(). | ||
func (rwc *winTPMBuffer) Write(commandBuffer []byte) (int, error) { | ||
// TPM spec defines longest possible response to be maxTPMResponse. | ||
rwc.outBuffer = rwc.outBuffer[:maxTPMResponse] | ||
|
||
outBufferLen, err := rwc.context.SubmitCommand( | ||
tbs.NormalPriority, | ||
commandBuffer, | ||
rwc.outBuffer, | ||
) | ||
|
||
if err != nil { | ||
rwc.outBuffer = rwc.outBuffer[:0] | ||
return 0, err | ||
} | ||
// Shrink outBuffer so it is length of response. | ||
rwc.outBuffer = rwc.outBuffer[:outBufferLen] | ||
return len(commandBuffer), nil | ||
} | ||
|
||
// Read implements the io.Reader interface. | ||
// | ||
// Provides TPM response from the command called in the last Write call. | ||
func (rwc *winTPMBuffer) Read(responseBuffer []byte) (int, error) { | ||
if len(rwc.outBuffer) == 0 { | ||
return 0, io.EOF | ||
} | ||
lenCopied := copy(responseBuffer, rwc.outBuffer) | ||
// Cut out the piece of slice which was just read out, maintaining original slice capacity. | ||
rwc.outBuffer = append(rwc.outBuffer[:0], rwc.outBuffer[lenCopied:]...) | ||
return lenCopied, nil | ||
} | ||
|
||
// Close implements the io.Closer interface. | ||
func (rwc *winTPMBuffer) Close() error { | ||
return rwc.context.Close() | ||
} |
Oops, something went wrong.