- What is a tcti?
- tcti Loader
- tcti-device
- tcti-tbs
- tcti-cmd
- tcti-pcap
- tcti-spi-ftdi
- tcti-i2c-ftdi
- tcti-spi-ltt2go
- tcti-spidev
- TPM Simulator tctis
In a strict sense, the TPM Command Transmission Interface (TCTI) is the API for the lowest layer of the TSS. However, we are a bit sloppy with our terminology, here, so we will call any library which implements the TCTI just that: a tcti.
flowchart TD
sapi(SAPI) -->|TCTI API| tcti(tcti)
tcti <-.-> tpm{{TPM}}
style tpm stroke-dasharray: 3, 3
For example, the tcti-device is a library (libtss2-tcti-device.so
) for
interacting with e.g. /dev/tpmrm0
.
As you can see in this example, a tcti is responsible for communicating with the
TPM. Typically, it sends TPM commands to the TPM and reads responses from the
TPM (as bytes). The path to /dev/tpmrm0
is configured via the conf
parameter
when initializing the tcti.
flowchart TD
sapi(SAPI) -->|"conf="/dev/tpmrm0"" | tcti_device(tcti-device)
tcti_device -.->|write| tpm{{/dev/tpmrm0}}
tpm -.->|read| tcti_device
style tpm stroke-dasharray: 3, 3
Most of the time, you will see that the TCTI is specified via a string, like
"device:/dev/tpmrm0"
(e.g. with the
tpm2-tools argument --tcti=...
or the FAPI config param "tcti": "..."
). This indicates that
the tcti is loaded dynamically using the tctildr (tcti loader).
The tctildr is a tcti itself. It dynamically loads and configures a tcti for you. For instance, it can load tcti-device:
flowchart TD
invis[ ] -->|"conf="device:/dev/tpmrm0""| tctildr(tctildr)
tctildr -->|"conf="/dev/tpmrm0""| tcti_device(tcti-device)
tcti_device -.->|write| tpm{{/dev/tpmrm0}}
tpm -.->|read| tcti_device
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tpm stroke-dasharray: 3, 3
Another example for tcti-swtpm:
flowchart TD
invis[ ] -->|"conf="swtpm:host=localhost,port=2321""| tctildr(tctildr)
tctildr -->|"conf="host=localhost,port=2321""| tcti_swtpm(tcti-swtpm)
tcti_swtpm <-.->|localhost:2321| tpm{{"swtpm (TPM simulator)"}}
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tpm stroke-dasharray: 3, 3
conf
<child_name>
, e.g.,device
(child_conf will beNULL
) OR<child_name>:<child_conf>
, e.g.,device:/dev/tpmrm0
ORNULL
, tctildr will attempt to load a child tcti in the following order:libtss2-tcti-default.so
libtss2-tcti-tabrmd.so
libtss2-tcti-device.so.0:/dev/tpmrm0
libtss2-tcti-device.so.0:/dev/tpm0
libtss2-tcti-device.so.0:/dev/tcm0
libtss2-tcti-swtpm.so
libtss2-tcti-mssim.so
Where:
child_name
- If not empty, tctildr will try to dynamically load the child tcti library in
the following order:
<child_name>
libtss2-tcti-<child_name>.so.0
libtss2-tcti-<child_name>.so
libtss2-<child_name>.so.0
libtss2-<child_name>.so
child_conf
conf
param to be passed to the child tcti
To put it simply, tcti-device writes to and reads from a file, typically
/dev/tpm0
or /dev/tpmrm0
or /dev/tcm0
. The character devices are provided by the Linux
kernel module tpm_tis
. If no files like these are present, verify that the
kernel module is loaded (lsmod
) and load it if necessary (modprobe tpm_tis
).
flowchart TD
invis[ ] -->|"conf="device:/dev/tpmrm0""| tctildr(tctildr)
tctildr -->|"conf="/dev/tpmrm0""| tcti_device(tcti-device)
tcti_device -.->|write| tpm{{/dev/tpmrm0}}
tpm -.->|read| tcti_device
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tpm stroke-dasharray: 3, 3
conf
- path to the character device, typically
/dev/tpm0
or/dev/tpmrm0
or/dev/tcm0
The tcti-tbs is used for communicating to the TPM via the TPM Base Services (TBS) on Windows. There might be limitations, especially if you do not have admin rights.
flowchart TD
invis[ ] -->|"conf="tbs""| tctildr(tctildr)
tctildr --> tcti_tbs(tcti-tbs)
tcti_tbs <-.->|"Tbsip_Submit_Command()"| tbs{{"TPM Base Services (TBS)"}}
tbs -.- tpm{{TPM}}
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tbs stroke-dasharray: 3, 3
style tpm stroke-dasharray: 3, 3
The tcti-cmd spawns a process and connects to its stdin
and stdout
. This
enables some advanced shenanigans like sending TPM traffic over the network via
ssh.
The following example makes the TPM communication unnecessary complex, but shows
how tcti-cmd works. Here, commands are piped into tpm2_send
and responses are
read from its stdout
.
flowchart TD
invis[ ] -->|"conf="cmd:tpm2_send --tcti='device:/dev/tpmrm0'""| tctildr1(tctildr)
tctildr1 -->|"conf="tpm2_send --tcti='device:/dev/tpmrm0'""| tcti_cmd(tcti-cmd)
tcti_cmd -.->|stdin| process{{tpm2_send --tcti='device:/dev/tpmrm0'}}
process -.->|stdout| tcti_cmd
process -->|"conf="device:/dev/tpmrm0""| tctildr2(tctildr)
tctildr2 -->|"conf="/dev/tpmrm0""| tcti_device{{tcti-device}}
tcti_device -.->|write| tpm{{/dev/tpmrm0}}
tpm -.->|read| tcti_device
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style process stroke-dasharray: 3, 3
style tpm stroke-dasharray: 3, 3
Now for a real-world example. We can communicate with a remote TPM by invoking
tpm2_send
on another host via ssh
.
flowchart TD
invis[ ] -->|"conf="cmd:ssh 192.168.178.123 tpm2_send --tcti='device:/dev/tpmrm0'""| tctildr1(tctildr)
tctildr1 -->|"conf="ssh 192.168.178.123 tpm2_send --tcti='device:/dev/tpmrm0'""| tcti_cmd(tcti-cmd)
tcti_cmd -.->|stdin| ssh{{ssh}}
ssh -.->|stdout| tcti_cmd
ssh -.-|network| sshd{{sshd}}
sshd -.- process{{tpm2_send -tcti='device:/dev/tpmrm0'}}
process -->|"conf="device:/dev/tpmrm0""| tctildr2(tctildr)
tctildr2 -->|"conf="/dev/tpmrm0""| tcti_device{{tcti-device}}
tcti_device -.->|write| tpm{{/dev/tpmrm0}}
tpm -.->|read| tcti_device
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style ssh stroke-dasharray: 3, 3
style sshd stroke-dasharray: 3, 3
style process stroke-dasharray: 3, 3
style tpm stroke-dasharray: 3, 3
conf
- Command to execute. Actually,
/bin/sh -c '<conf>'
will be called.
The tcti-pcap is used for logging. It is used by prepending any tctildr conf
string with pcap:
, e.g. pcap:device:/dev/tpmrm0
. Then, tcti-pcap will log into
a file specified by the environment variable TCTI_PCAP_FILE
)(default:
tpm2_log.pcap
). This file can be opened by e.g.
wireshark or
tpmstream.
Internally, tcti-pcap delegates to tctildr, again.
flowchart TD
invis2[ ] -.->|"TCTI_PCAP_FILE="tpm2_log.pcap""| tcti_pcap
tcti_pcap -.->|logging| file{{tpm2_log.pcap}}
invis[ ] -->|"conf="pcap:device:/dev/tpmrm0""| tctildr1(tctildr)
tctildr1 -->|"conf="device:/dev/tpmrm0""| tcti_pcap(tcti-pcap)
tcti_pcap -->|"conf="device:/dev/tpmrm0""| tctildr2(tctildr)
tctildr2 -->|"conf="/dev/tpmrm0""| tcti_device(tcti-device)
tcti_device -.->|write| tpm{{/dev/tpmrm0}}
tpm -.->|read| tcti_device
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style invis2 fill:#FFFFFF00, stroke:#FFFFFF00;
style file stroke-dasharray: 3, 3
style tpm stroke-dasharray: 3, 3
conf
conf
which will be passed to tctildr, e.g.device:/dev/tpmrm0
The tcti-spi-ftdi is used for communicating with a SPI-based TPM if there is no TPM driver present (or no OS at all).
For information, see tcti-spi-ftdi.md.
The tcti-i2c-ftdi is used for communicating with a I2C-based TPM if there is no TPM driver present (or no OS at all).
For information, see tcti-i2c-ftdi.md.
The tcti-spi-ltt2go is used specifically for communicating to the LetsTrust-TPM2Go. The LetsTrust-TPM2Go is basically a USB stick which houses a SPI-based TPM and connects that to the host via libusb.
flowchart TD
invis[ ] -->|"conf="libtpms""| tcti_spi_ltt2go(tcti-spi-ltt2go)
tcti_spi_ltt2go <-.->|libusb| cy7c65211a{{"USB2.0 SPI bridge"}}
cy7c65211a -.-|SPI| tpm{{"TPM"}}
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style cy7c65211a stroke-dasharray: 3, 3
style tpm stroke-dasharray: 3, 3
The tcti-spidev is used for communicating to a TPM that is connected via
a spidev device. On a Raspberry Pi for example this happens when enabling
the device tree overlay spi0-cs2
.
There are multiple tctis used for testing.
The tcti-libtpms is a simple TPM simulator based on libtpms, a library implementing TPM behavior. No second process is needed.
If no state file is passed via conf
, all state is held in RAM and discarded
when the process dies.
flowchart TD
invis[ ] -->|"conf="libtpms""| tctildr(tctildr)
tctildr --> tcti_libtpms(tcti-libtpms)
tcti_libtpms -.->|dynamically loads| tpm{{"libtpms.so (TPM simulator)"}}
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tpm stroke-dasharray: 3, 3
If multiple processes need to work on the simulated TPM, state must be saved to
the filesystem and loaded again. This can be achieved by passing a path via
conf
.
flowchart TD
invis[ ] -->|"conf="libtpms:tpm_state.libtpms""| tctildr(tctildr)
tctildr -->|"conf="tpm_state.libtpms""| tcti_libtpms(tcti-libtpms)
tcti_libtpms <-.->|loads/saves state| file{{tpm_state.libtpms}}
tcti_libtpms -.->|dynamically loads| tpm{{"libtpms.so (TPM simulator)"}}
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style file stroke-dasharray: 3, 3
style tpm stroke-dasharray: 3, 3
conf
- Path to a state file. Will be loaded on startup or created if it does not exist.
The tcti-swtpm connects to swtpm, a TPM simulator based on libtpms and socket communication.
Just like mssim, there is a primary socket (default: 2321
) used for TPM
commands/responses and a secondary socket (default: 2322
) for controlling the
simulator, here called control
channel.
While the primary socket is identical with that of mssim, the secondary one is
incompatible.
flowchart TD
invis[ ] -->|"conf="swtpm:host=localhost,port=2321""| tctildr(tctildr)
tctildr -->|"conf="host=localhost,port=2321""| tcti_swtpm(tcti-swtpm)
tcti_swtpm <-.->|TPM commands/responses| port2321{{"localhost:2321"}}
tcti_swtpm <-.->|control channel| port2322{{"localhost:2322"}}
port2321 -.- tpm{{"swtpm (TPM simulator)"}}
port2322 -.- tpm
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tpm stroke-dasharray: 3, 3
style port2321 stroke-dasharray: 3, 3
style port2322 stroke-dasharray: 3, 3
conf
host=<host>,port=<port>
, e.g.,host=192.168.178.123,port=5000
host=<host>
, e.g.,host=192.168.178.123
port=<port>
, e.g.port=5000
Where:
host
- Hostname or IP address to the simulator, default:
localhost
port
- Port to the simulator, default:
2321
. The control channel will be<port> + 1
The tcti-mssim connects to the Microsoft TPM simulator mssim, repackaged by IBM.
Like with swtpm, there is a primary socket (default: 2321
) used for TPM
commands/responses and a secondary socket (default: 2322
) for sending
so-called platform commands which control the simulator. While the primary
socket is identical with that of swtpm, the secondary one is incompatible.
flowchart TD
invis[ ] -->|"conf="mssim:host=localhost,port=2321""| tctildr(tctildr)
tctildr -->|"conf="host=localhost,port=2321""| tcti_mssim(tcti-mssim)
tcti_mssim <-.->|TPM commands/responses| port2321{{"localhost:2321"}}
tcti_mssim <-.->|platform commands| port2322{{"localhost:2322"}}
port2321 -.- tpm{{"mssim (TPM simulator)"}}
port2322 -.- tpm
style invis fill:#FFFFFF00, stroke:#FFFFFF00;
style tpm stroke-dasharray: 3, 3
style port2321 stroke-dasharray: 3, 3
style port2322 stroke-dasharray: 3, 3
conf
host=<host>,port=<port>
, e.g.,host=192.168.178.123,port=5000
host=<host>
, e.g.,host=192.168.178.123
port=<port>
, e.g.port=5000
Where:
host
- Hostname or IP address to the simulator, default:
localhost
port
- Port to the simulator, default:
2321
. The control channel will be<port> + 1