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 be NULL
) 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
<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 tctiTo 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
/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
/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
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
localhost
port
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
localhost
port
2321
. The control channel will be <port> + 1