diff --git a/tpm/internal/mssim/gotpm.go b/tpm/internal/mssim/gotpm.go new file mode 100644 index 00000000..5d6749bc --- /dev/null +++ b/tpm/internal/mssim/gotpm.go @@ -0,0 +1,204 @@ +// Copyright (c) 2018, Google LLC All rights reserved. +// +// 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 mssim implements the Microsoft simulator TPM2 Transmission Interface +// +// The Microsoft simulator TPM Command Transmission Interface (TCTI) is a +// remote procedure interface donated to the TPM2 Specification by Microsoft. +// Its primary implementation is the tpm_server maintained by IBM. +// +// https://sourceforge.net/projects/ibmswtpm2/ +// +// This package implements client code to communicate with server code described +// in the document "TPM2 Specification Part 4: Supporting Routines – Code" +// +// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-4-Supporting-Routines-01.38-code.pdf +// +// This file was copied from https://github.com/google/go-tpm/tree/main/tpmutil/mssim. +// Some small changes were made so that platform commands can be skipped based on +// configuration. + +//nolint:errorlint,gocritic // copied package +package mssim + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" +) + +// Constants defined in "D.3.2. Typedefs and Defines" +const ( + tpmSignalPowerOn uint32 = 1 + tpmSignalPowerOff uint32 = 2 + tpmSendCommand uint32 = 8 + tpmSignalNVOn uint32 = 11 + tpmSessionEnd uint32 = 20 +) + +// Config holds configuration parameters for connecting to the simulator. +type config struct { + // Addresses of the command and platform handlers. + // + // Defaults to port 2321 and 2322 on localhost. + commandAddress string + platformAddress string + + // Skip platform powerup/startup commands + skipPlatformCommands bool +} + +// Open creates connections to the simulator's command and platform ports and +// power cycles the simulator to initialize it. +func open(config config) (*Conn, error) { + cmdAddr := config.commandAddress + if cmdAddr == "" { + cmdAddr = "127.0.0.1:2321" + } + + if !config.skipPlatformCommands { + platformAddr := config.platformAddress + if platformAddr == "" { + platformAddr = "127.0.0.1:2322" + } + + conn, err := net.Dial("tcp", platformAddr) + if err != nil { + return nil, fmt.Errorf("dial platform address: %v", err) + } + defer conn.Close() + + // Startup the simulator. This order of commands copies IBM's TPM2 tool's + // "powerup" command line tool and will reset the simulator. + // + // https://sourceforge.net/projects/ibmtpm20tss/ + + if err := sendPlatformCommand(conn, tpmSignalPowerOff); err != nil { + return nil, fmt.Errorf("power off platform command failed: %v", err) + } + if err := sendPlatformCommand(conn, tpmSignalPowerOn); err != nil { + return nil, fmt.Errorf("power on platform command failed: %v", err) + } + if err := sendPlatformCommand(conn, tpmSignalNVOn); err != nil { + return nil, fmt.Errorf("nv on platform command failed: %v", err) + } + + // Gracefully close the connection. + if err := binary.Write(conn, binary.BigEndian, tpmSessionEnd); err != nil { + return nil, fmt.Errorf("shutdown platform connection failed: %v", err) + } + } + + cmdConn, err := net.Dial("tcp", cmdAddr) + if err != nil { + return nil, fmt.Errorf("dial command address: %v", err) + } + + return &Conn{conn: cmdConn}, nil +} + +// sendPlatformCommand sends device management commands to the simulator. +// +// See: "D.4.3.2. PlatformServer()" +func sendPlatformCommand(conn net.Conn, u uint32) error { + if err := binary.Write(conn, binary.BigEndian, u); err != nil { + return fmt.Errorf("write platform command: %v", err) + } + + var rc uint32 + if err := binary.Read(conn, binary.BigEndian, &rc); err != nil { + return fmt.Errorf("read platform command: %v", err) + } + if rc != 0 { + return fmt.Errorf("unexpected platform command response code: 0x%x", rc) + } + return nil +} + +// Conn is a Microsoft Simulator client that can be used as a connection for the +// tpm2 package. +type Conn struct { + // Cached connection + conn net.Conn + + // Response bytes left over from the previous read. + prevRead *bytes.Reader +} + +// Read a response from the simulator. If the response is longer than the provided +// buffer, the remainder will be cached for the next read. +func (c *Conn) Read(b []byte) (int, error) { + if c.prevRead != nil && c.prevRead.Len() > 0 { + return c.prevRead.Read(b) + } + + // Response frame: + // - uint32 (size of response) + // - []byte (response) + // - uint32 (always 0) + var respLen uint32 + if err := binary.Read(c.conn, binary.BigEndian, &respLen); err != nil { + return 0, fmt.Errorf("read MS simulator response header: %v", err) + } + + resp := make([]byte, int(respLen)) + if _, err := io.ReadFull(c.conn, resp[:]); err != nil { + return 0, fmt.Errorf("read MS simulator response: %v", err) + } + + var rc uint32 + if err := binary.Read(c.conn, binary.BigEndian, &rc); err != nil { + return 0, fmt.Errorf("read MS simulator return code: %v", err) + } + if rc != 0 { + return 0, fmt.Errorf("MS simulator returned invalid return code: 0x%x", rc) + } + + c.prevRead = bytes.NewReader(resp) + return c.prevRead.Read(b) +} + +// Write a raw command to the simulator. Commands must be written in a single call +// to Write. Commands split over multiple calls will result in multiple framed +// requests. +func (c *Conn) Write(b []byte) (int, error) { + // See: D.4.3.12. TpmServer() + buff := &bytes.Buffer{} + // "send command" flag + binary.Write(buff, binary.BigEndian, tpmSendCommand) + // locality 0 + buff.WriteByte(0) + // size of the command + binary.Write(buff, binary.BigEndian, uint32(len(b))) + // raw command + buff.Write(b) + + if _, err := buff.WriteTo(c.conn); err != nil { + return 0, fmt.Errorf("write MS simulator command: %v", err) + } + return len(b), nil +} + +// Close closes any outgoing connections to the TPM simulator. +func (c *Conn) Close() error { + // See: D.4.3.12. TpmServer() + // Gracefully close the connection. + if err := binary.Write(c.conn, binary.BigEndian, tpmSessionEnd); err != nil { + c.conn.Close() + return fmt.Errorf("shutdown platform connection failed: %v", err) + } + return c.conn.Close() +} diff --git a/tpm/internal/mssim/mssim.go b/tpm/internal/mssim/mssim.go new file mode 100644 index 00000000..97ae4e61 --- /dev/null +++ b/tpm/internal/mssim/mssim.go @@ -0,0 +1,50 @@ +package mssim + +import ( + "fmt" + "io" + "net" + "strconv" + + "go.step.sm/crypto/kms/uri" +) + +func New(u *uri.URI) (rwc io.ReadWriteCloser, err error) { + host := "127.0.0.1" + port := 2321 + if h := u.Get("host"); h != "" { + host = h + } + if p := u.Get("port"); p != "" { + port, err = strconv.Atoi(p) + if err != nil { + return nil, fmt.Errorf("failed parsing %q as integer: %w", p, err) + } + } + + c := config{ + commandAddress: net.JoinHostPort(host, fmt.Sprint(port)), + platformAddress: net.JoinHostPort(host, fmt.Sprint(port+1)), + skipPlatformCommands: true, // TODO(hs): assumes these steps are performed out of band; does that make sense? + } + + rwc, err = open(c) + if err != nil { + return nil, fmt.Errorf("failed opening connection to TPM: %w", err) + } + + // TODO(hs): make connection open lazily? And/or support connection management internally? + // Sometimes it happens that the connection is very slow, or there seems to be no connection + // at all. This is likely due to how we've implemented opening the TPM (once, generally), and + // then reusing that instance. + + return +} + +type CommandChannelWithoutMeasurementLog struct { + io.ReadWriteCloser +} + +func (c *CommandChannelWithoutMeasurementLog) MeasurementLog() ([]byte, error) { + return nil, nil +} diff --git a/tpm/internal/open/open.go b/tpm/internal/open/open.go index c88b6813..39c57657 100644 --- a/tpm/internal/open/open.go +++ b/tpm/internal/open/open.go @@ -1,9 +1,26 @@ package open import ( + "fmt" "io" + "strings" + + "go.step.sm/crypto/kms/uri" + "go.step.sm/crypto/tpm/internal/mssim" ) func TPM(deviceName string) (io.ReadWriteCloser, error) { + if strings.HasPrefix(deviceName, "mssim:") { + u, err := uri.ParseWithScheme("mssim", deviceName) + if err != nil { + return nil, fmt.Errorf("failed parsing %q: %w", deviceName, err) + } + rwc, err := mssim.New(u) + if err != nil { + return nil, err + } + return rwc, nil + } + return open(deviceName) } diff --git a/tpm/manufacturer/manufacturers.go b/tpm/manufacturer/manufacturers.go index 5c749676..bc6c4e7c 100644 --- a/tpm/manufacturer/manufacturers.go +++ b/tpm/manufacturer/manufacturers.go @@ -38,7 +38,7 @@ func GetEncodings(id ID) (ascii, hexa string) { // GetNameByASCII returns the manufacturer name based on its // ASCII identifier. func GetNameByASCII(ascii string) string { - if name, ok := manufacturerByASCII[ascii]; ok { + if name, ok := manufacturerByASCII[strings.TrimSpace(ascii)]; ok { return name } return "unknown" diff --git a/tpm/tpm.go b/tpm/tpm.go index 2f998635..41530bc1 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -6,11 +6,14 @@ import ( "fmt" "io" "net/http" + "strings" "sync" "github.com/smallstep/go-attestation/attest" + "go.step.sm/crypto/kms/uri" closer "go.step.sm/crypto/tpm/internal/close" + "go.step.sm/crypto/tpm/internal/mssim" "go.step.sm/crypto/tpm/internal/open" "go.step.sm/crypto/tpm/internal/socket" "go.step.sm/crypto/tpm/simulator" @@ -230,19 +233,30 @@ func (t *TPM) initializeCommandChannel() error { t.commandChannel = t.options.commandChannel } - // finally, check if the device name points to a UNIX socket, and use that - // as the command channel, if available. if t.commandChannel == nil { - if socketCommandChannel, err := trySocketCommandChannel(t.deviceName); err != nil { - switch { - case errors.Is(err, socket.ErrNotSupported): - // don't try to use socket command channel if not supported. No need to return - // an error, because the code should still rely on the default TPM command channel. - case !errors.Is(err, socket.ErrNotAvailable): - return err + switch { + case strings.HasPrefix(t.deviceName, "mssim:"): + // if the TPM device name looks like a URI that starts with "mssim:", try + // using a TCP connection to the TPM using the Microsoft PTM simulator TCTI. + msSimCommandChannel, err := tryMsSimCommandChannel(t.deviceName) + if err != nil { + return fmt.Errorf("invalid Microsoft TPM Simulator device name %q: %w", t.deviceName, err) + } + t.commandChannel = msSimCommandChannel + default: + // finally, check if the device name points to a UNIX socket, and use that + // as the command channel, if available. + if socketCommandChannel, err := trySocketCommandChannel(t.deviceName); err != nil { + switch { + case errors.Is(err, socket.ErrNotSupported): + // don't try to use socket command channel if not supported. No need to return + // an error, because the code should still rely on the default TPM command channel. + case !errors.Is(err, socket.ErrNotAvailable): + return err + } + } else { + t.commandChannel = socketCommandChannel } - } else { - t.commandChannel = socketCommandChannel } } @@ -257,7 +271,8 @@ func (t *TPM) initializeCommandChannel() error { return nil } -// trySocketCommandChannel tries +// trySocketCommandChannel tries to establish connections to a TPM using +// a UNIX socket. func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurementLog, error) { rwc, err := socket.New(path) if err != nil { @@ -266,6 +281,20 @@ func trySocketCommandChannel(path string) (*socket.CommandChannelWithoutMeasurem return &socket.CommandChannelWithoutMeasurementLog{ReadWriteCloser: rwc}, nil } +// tryMsSimCommandChannel tries to establish connections to a TPM using the +// Microsoft TPM Simulator TCTI. +func tryMsSimCommandChannel(rawuri string) (*mssim.CommandChannelWithoutMeasurementLog, error) { + u, err := uri.ParseWithScheme("mssim", rawuri) + if err != nil { + return nil, fmt.Errorf("failed parsing %q: %w", rawuri, err) + } + rwc, err := mssim.New(u) + if err != nil { + return nil, err + } + return &mssim.CommandChannelWithoutMeasurementLog{ReadWriteCloser: rwc}, nil +} + // Close closes the TPM instance, cleaning up resources and // marking it ready to be use again. func (t *TPM) close(ctx context.Context) error {