1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
|
// Package tcp provides access to a TPM over TCP.
package tcp
import (
"encoding/binary"
"errors"
"fmt"
"net"
)
var (
ErrPlatformFailed = errors.New("platform command failed")
ErrTPMFailed = errors.New("TPM command failed")
ErrResponseTooBig = errors.New("response too big")
ErrTransport = errors.New("TCP transport error")
ErrEmptyResponse = errors.New("TPM returned empty response (does it need to be powered on?)")
)
const (
maxBufferSize = 1048576
)
// The de-facto TPM-over-TCP protocol is defined by the Reference Implementation.
// See https://github.com/TrustedComputingGroup/TPM/blob/main/TPMCmd/Simulator/include/TpmTcpProtocol.h
type regularCommand uint32
const (
tpmHashStart regularCommand = 5
tpmHashData regularCommand = 6
tpmHashEnd regularCommand = 7
tpmSendCommand regularCommand = 8
tpmRemoteHandshake regularCommand = 15
tpmSetAlternativeResult regularCommand = 16
tpmSessionEnd regularCommand = 20
tpmStop regularCommand = 21
)
func (c regularCommand) String() string {
switch c {
case tpmHashStart:
return "HASH_START"
case tpmHashData:
return "HASH_DATA"
case tpmHashEnd:
return "HASH_END"
case tpmSendCommand:
return "SEND_COMMAND"
case tpmRemoteHandshake:
return "REMOTE_HANDSHAKE"
case tpmSetAlternativeResult:
return "SET_ALTERNATIVE_RESULT"
case tpmSessionEnd:
return "SESSION_END"
case tpmStop:
return "STOP"
default:
return fmt.Sprintf("unknown TPM command (%v)", uint32(c))
}
}
type platformCommand uint32
const (
platformPowerOn platformCommand = 1
platformPowerOff platformCommand = 2
platformPPOn platformCommand = 3
platformPPOff platformCommand = 4
platformCancelOn platformCommand = 9
platformCancelOff platformCommand = 10
platformNVOn platformCommand = 11
platformNVOff platformCommand = 12
platformKeyCacheOn platformCommand = 13
platformKeyCacheOff platformCommand = 14
platformReset platformCommand = 17
platformRestart platformCommand = 18
platformSessionEnd platformCommand = 20
platformStop platformCommand = 21
platformGetCommandResponseSizes platformCommand = 25
platformACTGetSignaled platformCommand = 26
platformTestFailureMode platformCommand = 30
platformSetFWHash platformCommand = 35
platformSetFWSVN platformCommand = 36
)
func (c platformCommand) String() string {
switch c {
case platformPowerOn:
return "POWER_ON"
case platformPowerOff:
return "POWER_OFF"
case platformPPOn:
return "PP_ON"
case platformPPOff:
return "PP_OFF"
case platformCancelOn:
return "CANCEL_ON"
case platformCancelOff:
return "CANCEL_OFF"
case platformNVOn:
return "NV_ON"
case platformNVOff:
return "NV_OFF"
case platformKeyCacheOn:
return "KEY_CACHE_ON"
case platformKeyCacheOff:
return "KEY_CACHE_OFF"
case platformReset:
return "RESET"
case platformRestart:
return "RESTART"
case platformSessionEnd:
return "SESSION_END"
case platformStop:
return "STOP"
case platformGetCommandResponseSizes:
return "GET_COMMAND_RESPONSE_SIZES"
case platformACTGetSignaled:
return "ACT_GET_SIGNALED"
case platformTestFailureMode:
return "TEST_FAILURE_MODE"
case platformSetFWHash:
return "SET_FW_HASH"
case platformSetFWSVN:
return "SET_FW_SVN"
default:
return fmt.Sprintf("unknown platform command (%v)", uint32(c))
}
}
type TPM struct {
cmd *net.TCPConn
plat *net.TCPConn
}
type tpmCommandHeader struct {
tcpCommand regularCommand
locality uint8
cmdLen uint32
}
// Send implements the TPMCloser interface.
func (t *TPM) Send(cmd []byte) ([]byte, error) {
hdr := tpmCommandHeader{
tcpCommand: tpmSendCommand,
locality: 0,
cmdLen: uint32(len(cmd)),
}
// Write the header followed by the request
if err := binary.Write(t.cmd, binary.BigEndian, hdr); err != nil {
return nil, fmt.Errorf("%w: could not send TPM command to service: %v", ErrTransport, err)
}
if n, err := t.cmd.Write(cmd); err != nil {
return nil, fmt.Errorf("%w: could not send TPM command to service: %v", ErrTransport, err)
} else if n != len(cmd) {
return nil, fmt.Errorf("%w: could not send full TPM command: only sent %v out of %v bytes", ErrTransport, n, len(cmd))
}
// Read the response
var rspLen uint32
if err := binary.Read(t.cmd, binary.BigEndian, &rspLen); err != nil {
return nil, fmt.Errorf("%w: could not read TPM response from service: %v", ErrTransport, err)
}
if rspLen > maxBufferSize {
return nil, fmt.Errorf("%w: response (%v bytes) was bigger than max size (%v bytes)", ErrResponseTooBig, rspLen, maxBufferSize)
}
rsp := make([]byte, int(rspLen))
if n, err := t.cmd.Read(rsp); err != nil {
return nil, fmt.Errorf("%w: could not read TPM response from service: %v", ErrTransport, err)
} else if n != len(rsp) {
return nil, fmt.Errorf("%w: could not read full TPM response: only got %v out of %v bytes", ErrTransport, n, len(rsp))
}
// The server also provides a TCP error code at the end.
var rspCode uint32
if err := binary.Read(t.cmd, binary.BigEndian, &rspCode); err != nil {
return nil, fmt.Errorf("%w: %v returned %v", ErrTPMFailed, hdr.tcpCommand, rspCode)
}
if rspLen == 0 {
return nil, ErrEmptyResponse
}
return rsp, nil
}
// Close implements the TPMCloser interface.
func (t *TPM) Close() error {
return errors.Join(t.cmd.Close(), t.plat.Close())
}
// PowerOn powers on the TPM.
// Note: This is distinct from sending the TPM2_Startup command.
func (t *TPM) PowerOn() error {
return errors.Join(t.sendBasicPlatformCommand(platformPowerOn),
t.sendBasicPlatformCommand(platformNVOn))
}
// PowerOff powers off the TPM.
func (t *TPM) PowerOff() error {
return errors.Join(t.sendBasicPlatformCommand(platformPowerOff),
t.sendBasicPlatformCommand(platformNVOff))
}
// Reset power-cycles the TPM if it is already on. If it is not already on,
// nothing happens.
func (t *TPM) Reset() error {
return t.sendBasicPlatformCommand(platformReset)
}
// Config provides the connection information for a running TCP TPM.
type Config struct {
// CommandAddress is the full host:port address of the Command server, e.g.,
// "localhost:2321"
CommandAddress string
// CommandAddress is the full host:port address of the Platform server, e.g.,
// "localhost:2322"
PlatformAddress string
}
func resolveAndConnect(addr string) (*net.TCPConn, error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, fmt.Errorf("could not resolve %q: %w", addr, err)
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
return nil, fmt.Errorf("could not dial %q: %w", addr, err)
}
return conn, nil
}
// Open opens a connection to the TPM. It may still need to be powered on using PowerOn().
func Open(config Config) (*TPM, error) {
cmd, err := resolveAndConnect(config.CommandAddress)
if err != nil {
return nil, fmt.Errorf("could not connect to command service at %q: %w", config.CommandAddress, err)
}
plat, err := resolveAndConnect(config.PlatformAddress)
if err != nil {
return nil, fmt.Errorf("could not connect to platform service at %q: %w", config.PlatformAddress, err)
}
return &TPM{
cmd: cmd,
plat: plat,
}, nil
}
// sendBasicPlatformCommand sends a command to the platform service. This only
// supports 'basic' commands (i.e., send just a command code, receive just a
// response code).
func (t *TPM) sendBasicPlatformCommand(cmd platformCommand) error {
if err := binary.Write(t.plat, binary.BigEndian, cmd); err != nil {
return fmt.Errorf("could not write %v to platform service: %w", cmd, err)
}
var result uint32
if err := binary.Read(t.plat, binary.BigEndian, &result); err != nil {
return fmt.Errorf("could not read %v result from platform service: %w", cmd, err)
}
if result != 0 {
return fmt.Errorf("%w: %v returned %v", ErrPlatformFailed, cmd, result)
}
return nil
}
|