Skip to content

Commit e6df905

Browse files
committed
Add support for the NVIDIA Jetson Orin OP-TEE firmware TPM
1 parent fbbfabd commit e6df905

10 files changed

Lines changed: 338 additions & 15 deletions

File tree

.github/workflows/make-test-swtpm.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ jobs:
193193
needs_swtpm: false
194194
test_command: "true"
195195

196+
# Kernel-device autodetect: /dev/tpmrm0 -> /dev/tpm0 -> SPI fallback.
197+
# Compiles a different path than devtpm (WOLFTPM_LINUX_DEV_AUTODETECT
198+
# keeps the TIS/SPI HAL in the build). Build-only for the same reason.
199+
# Runtime coverage of either needs a self-hosted runner with a real
200+
# TPM bound to the kernel driver -- see docs/DEVTPM.md.
201+
- name: devtpm-autodetect
202+
wolftpm_config: --enable-autodetect
203+
needs_swtpm: false
204+
test_command: "true"
205+
196206
# Negative tests: configure must error on conflicting flag combos,
197207
# and we verify the SPECIFIC error message (not just a non-zero
198208
# exit). wolfSSL is installed by the earlier `Setup wolfSSL` step,
@@ -252,7 +262,10 @@ jobs:
252262
wolftpm_config: --enable-advio --disable-fwtpm
253263
needs_swtpm: false
254264

255-
# Autodetect (default configure, /dev/tpm0 + SPI dual support)
265+
# Default configure. NOTE this does NOT build the /dev/tpmX path:
266+
# WOLFTPM_SWTPM is still auto-enabled here, which suppresses
267+
# WOLFTPM_LINUX_DEV_AUTODETECT (wolftpm/tpm2_types.h). The kernel
268+
# device path is covered by the devtpm-autodetect entry below.
256269
- name: autodetect
257270
wolftpm_config: "--disable-fwtpm"
258271
needs_swtpm: false

README.md

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ Tested with:
243243
* Nuvoton NPCT65X or NPCT75x TPM2.0 modules
244244
* Nations Technologies Z32H330 or NS350 TPM 2.0 modules
245245
* SealSQ QVault TPM 2.0 module (SPI, post-quantum ML-DSA / ML-KEM)
246+
* NVIDIA Jetson Orin (Tegra234) firmware TPM - a TPM 2.0 running as an OP-TEE trusted application, reached through the Linux kernel driver rather than a bus. See [docs/DEVTPM.md](docs/DEVTPM.md#nvidia-jetson-orin-tegra234-firmware-tpm).
246247

247248
#### Device Identification
248249

@@ -292,6 +293,11 @@ SealSQ QVault TPM 2.0
292293
TPM2: Caps 0x30000797, Did 0x0083, Vid 0x2406, Rid 0x 3
293294
Mfg SEAL (6), Vendor QVault TPM, Fw 2.1 (0x3010303), FIPS 140-3, CC-EAL4 0
294295

296+
NVIDIA Jetson Orin (Tegra234) OP-TEE firmware TPM, via /dev/tpmrm0
297+
Mfg MSFT (7), Vendor SSE fTPM, Fw 8216.1808 (0x105300), FIPS 140-2, CC-EAL4 0
298+
299+
There is no `Caps/Did/Vid/Rid` line above because those values come from TIS bus registers, which a firmware TPM does not have. The entry was captured with `--enable-autodetect`, where `wolfTPM2_Init_ex` returns as soon as the kernel device opens, so the debug line is never reached; an `--enable-devtpm` build still prints it, reading all zeros. `Fw 8216.1808` is `TPM_PT_FIRMWARE_VERSION_1` = `0x20180710`, which this implementation uses to carry a build date (2018-07-10) rather than a version number. Spec revision is 1.62, and all four PCR banks (SHA-1, SHA-256, SHA-384, SHA-512) are allocated with PCRs 0-23.
300+
295301
## Building
296302

297303
### Building wolfSSL
@@ -463,24 +469,26 @@ idf.py build
463469

464470
### Building for "/dev/tpmX"
465471

466-
**Auto-detection (recommended):** On Linux, a default `./configure && make` will automatically try `/dev/tpmrm0` then `/dev/tpm0` at runtime. If the kernel driver is available it will be used; otherwise wolfTPM falls back to direct SPI access. No special configure options are needed.
472+
**Auto-detection (recommended):** `--enable-autodetect` tries `/dev/tpmrm0` then `/dev/tpm0` at runtime. If the kernel driver is available it will be used; otherwise wolfTPM falls back to direct SPI access.
467473

468474
```bash
469475
./autogen.sh
470-
./configure
476+
./configure --enable-autodetect
471477
make
472478
```
473479

474-
Previously, using the kernel TPM driver required the `--enable-devtpm` flag. This is no longer necessary with autodetect (enabled by default). You can still use `--enable-devtpm` to force kernel-driver-only mode, which disables SPI fallback.
480+
**Important:** on Linux `x86_64` and `aarch64`, a bare `./configure` does *not* reach `/dev/tpmX`. On those hosts the software TPMs (swTPM and fwTPM) are auto-enabled so that `make check` works without hardware, and defining `WOLFTPM_SWTPM` suppresses the kernel-device autodetect path. The resulting build talks to a simulator on TCP port 2321, not to your TPM. Selecting any hardware path explicitly - `--enable-autodetect`, `--enable-devtpm`, or any `--enable-<vendor>` - turns the software defaults back off. This matters on single-board machines with a firmware TPM, such as the NVIDIA Jetson Orin, where the kernel device is the only transport.
475481

476-
To specify a different `/dev/tpmX` device use `CFLAGS="-DTPM2_LINUX_DEV=/dev/tpm1"`
482+
Use `--enable-devtpm` to force kernel-driver-only mode, which disables the SPI fallback:
477483

478484
```bash
479485
./autogen.sh
480486
./configure --enable-devtpm
481487
make
482488
```
483489

490+
To specify a different `/dev/tpmX` device use `CFLAGS='-DTPM2_LINUX_DEV="/dev/tpm1"'` - the inner quotes are required, since the macro is used directly as a C string literal. To pin the resource manager and never fall back to the raw device, build with `-DWOLFTPM_USE_TPMRM`.
491+
484492
The `TPM2_Init` or `wolfTPM2_Init` calls should use NULL for the HAL IO callback argument. The default HAL IO `TPM2_IoCb` maps to a macro specifying NULL (`#define TPM2_IoCb NULL`) in tpm_io.h for the devtpm option.
485493

486494
By default the `/dev/tpmX` requires sudo permissions to use it. If using the tpm2-tss it will install a "tss" group that you can add permissions to `sudo adduser [username] tss`.
@@ -505,6 +513,8 @@ KERNEL=="tpm[0-9]*", TAG+="systemd", MODE="0660", GROUP="wolftpm"
505513

506514
4) Reboot or reload rules: `sudo udevadm control -R`
507515

516+
For the resource manager (`/dev/tpmrm0`) versus the raw device, which operations the kernel refuses, and firmware-TPM platforms such as the NVIDIA Jetson Orin, see [docs/DEVTPM.md](docs/DEVTPM.md).
517+
508518

509519
### Building for SWTPM
510520

@@ -913,6 +923,53 @@ ECDSA 256 verify 26 ops took 1.018 sec, avg 39.164 ms, 25.533 ops/sec
913923
ECDHE 256 agree 35 ops took 1.029 sec, avg 29.402 ms, 34.011 ops/sec
914924
```
915925

926+
Run on the NVIDIA Jetson Orin (Tegra234) OP-TEE firmware TPM, via `/dev/tpmrm0`.
927+
Jetson Linux R36.4.4, kernel 5.15.148-tegra, `MAXN_SUPER` power mode with all six
928+
Cortex-A78AE cores at 1728 MHz:
929+
930+
```
931+
./examples/bench/bench
932+
TPM2 Benchmark using Wrapper API's
933+
Use Parameter Encryption: NULL
934+
Loading SRK: Storage 0x81000200 (282 bytes)
935+
RNG 563 KB took 1.001 seconds, 562.277 KB/s
936+
AES-128-CBC-enc 3 MB took 1.000 seconds, 2.798 MB/s
937+
AES-128-CBC-dec 3 MB took 1.000 seconds, 2.780 MB/s
938+
AES-256-CBC-enc 3 MB took 1.000 seconds, 2.898 MB/s
939+
AES-256-CBC-dec 3 MB took 1.000 seconds, 2.888 MB/s
940+
AES-128-CTR-enc 3 MB took 1.000 seconds, 2.910 MB/s
941+
AES-128-CTR-dec 3 MB took 1.000 seconds, 2.762 MB/s
942+
AES-256-CTR-enc 3 MB took 1.000 seconds, 2.764 MB/s
943+
AES-256-CTR-dec 3 MB took 1.000 seconds, 2.730 MB/s
944+
AES-128-CFB-enc 3 MB took 1.000 seconds, 2.752 MB/s
945+
AES-128-CFB-dec 3 MB took 1.000 seconds, 2.688 MB/s
946+
AES-256-CFB-enc 3 MB took 1.000 seconds, 2.881 MB/s
947+
AES-256-CFB-dec 3 MB took 1.000 seconds, 2.830 MB/s
948+
SHA1 2 MB took 1.000 seconds, 1.521 MB/s
949+
SHA256 2 MB took 1.000 seconds, 1.572 MB/s
950+
SHA384 2 MB took 1.000 seconds, 1.554 MB/s
951+
SHA512 2 MB took 1.000 seconds, 1.569 MB/s
952+
RSA 2048 key gen 21 ops took 15.465 sec, avg 736.433 ms, 1.358 ops/sec
953+
RSA 2048 Public 1145 ops took 1.001 sec, avg 0.874 ms, 1144.407 ops/sec
954+
RSA 2048 Private 85 ops took 1.012 sec, avg 11.900 ms, 84.033 ops/sec
955+
RSA 2048 Pub OAEP 1086 ops took 1.000 sec, avg 0.921 ms, 1085.719 ops/sec
956+
RSA 2048 Priv OAEP 84 ops took 1.002 sec, avg 11.934 ms, 83.793 ops/sec
957+
ECC 256 key gen 9 ops took 1.081 sec, avg 120.163 ms, 8.322 ops/sec
958+
ECDSA 256 sign 23 ops took 1.038 sec, avg 45.139 ms, 22.154 ops/sec
959+
ECDSA 256 verify 32 ops took 1.016 sec, avg 31.737 ms, 31.509 ops/sec
960+
ECDHE 256 agree 12 ops took 1.076 sec, avg 89.632 ms, 11.157 ops/sec
961+
```
962+
963+
Unlike the discrete parts above, every operation the benchmark exercises is
964+
supported, and the throughput figures are one to two orders of magnitude higher.
965+
That is a property of where the TPM runs rather than of the TPM itself: the
966+
firmware TPM executes on an application core with no serial bus in the path,
967+
whereas a discrete part is a small microcontroller reached over SPI or I2C. The
968+
comparison is useful for capacity planning, not as a security ranking - the
969+
discrete parts are separate silicon with their own tamper boundary, while the
970+
firmware TPM shares the SoC with the software it attests. See
971+
[docs/DEVTPM.md](docs/DEVTPM.md#nvidia-jetson-orin-tegra234-firmware-tpm).
972+
916973
### TPM2 Native Tests
917974

918975
```

configure.ac

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,15 @@ then
309309

310310
AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_SWTPM"
311311

312+
# WOLFTPM_SWTPM suppresses the /dev/tpmX autodetect (see
313+
# WOLFTPM_LINUX_DEV_AUTODETECT in wolftpm/tpm2_types.h), so when it is
314+
# reached by default rather than by request, a host with a real kernel
315+
# TPM would silently build against the simulator. Say so.
316+
if test "x$WOLFTPM_DEFAULT_SWTPM" = "xyes" && test "x$enable_swtpm" = "x"; then
317+
AC_MSG_NOTICE([no hardware TPM interface selected: defaulting to the software TPMs (swTPM/fwTPM) so 'make check' works without hardware.])
318+
AC_MSG_NOTICE([this build will NOT use /dev/tpmrm0 or /dev/tpm0. For a real TPM use --enable-autodetect, --enable-devtpm, or --enable-<vendor>.])
319+
fi
320+
312321
if test "x$ENABLED_SWTPM" = "xuart"
313322
then
314323
AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_SWTPM_UART"

docs/DEVTPM.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# wolfTPM with the Linux Kernel TPM Device (/dev/tpmX)
2+
3+
On Linux the kernel's TPM driver stack exposes a TPM through a character device, and wolfTPM can use it directly instead of driving SPI or I2C itself. This is the right transport whenever the kernel already owns the TPM: a discrete chip bound to a kernel driver, a Windows-style firmware TPM, or a TEE-resident firmware TPM such as the one on NVIDIA Jetson platforms.
4+
5+
With `--enable-devtpm` there is **no TIS layer and no HAL IO callback**: `hal/tpm_io.c` is compiled out entirely and `TPM2_IoCb` is `NULL` (see `hal/tpm_io.h`), so pass `NULL` for the callback argument of `TPM2_Init` / `wolfTPM2_Init`.
6+
7+
With `--enable-autodetect` this is **not** the case. The TIS/SPI HAL stays compiled in on purpose - it is the fallback - and `TPM2_IoCb` is a real function. Keep passing it, or the SPI fallback that build exists to provide is unreachable.
8+
9+
## Two device nodes
10+
11+
The kernel presents up to two nodes per TPM:
12+
13+
* `/dev/tpm0` - the raw device. One user at a time, no resource management. Whatever you send reaches the TPM.
14+
* `/dev/tpmrm0` - the in-kernel resource manager (kernel 4.12+, practical from 5.12+). It virtualizes handles, swaps transient objects and sessions in and out as needed, and flushes everything belonging to a connection when that connection closes.
15+
16+
wolfTPM prefers `/dev/tpmrm0` and falls back to `/dev/tpm0`. The resource manager is the better default: a TPM has very few transient object slots, and without it a program that leaks a handle wedges the TPM for everything else on the system.
17+
18+
Build-time overrides, honored by both `--enable-devtpm` and `--enable-autodetect`:
19+
20+
* `-DWOLFTPM_USE_TPMRM` - use `/dev/tpmrm0` only, with no fallback to the raw device.
21+
* `CFLAGS='-DTPM2_LINUX_DEV="/dev/tpm1"'` - use a specific node. The inner quotes are required: the macro is used directly as a C string literal, so an unquoted value does not compile.
22+
23+
## Startup, shutdown, and shared state
24+
25+
The TPM is started by firmware long before Linux runs, and on the resource manager it is shared with every other process on the system. Restarting or shutting it down is therefore not an individual caller's decision, so wolfTPM stays out of the way on this transport:
26+
27+
* `wolfTPM2_Init` skips the startup and self-test sequence.
28+
* `wolfTPM2_Reset` and `wolfTPM2_Shutdown` are no-ops that return success.
29+
* `wolfTPM2_SetLocality` returns `NOT_COMPILED_IN` - the kernel owns the locality.
30+
31+
Be aware that the kernel does **not** reliably stop you here. Command filtering on `/dev/tpmrm0` is primarily about handle isolation, not about blocking global state changes, and behavior varies by kernel version and TPM implementation. On Linux 5.15 with the Jetson OP-TEE fTPM, a `TPM2_Shutdown(TPM_SU_CLEAR)` sent through the resource manager is passed straight through and returns success - both from wolfTPM and from `tpm2_shutdown`. So this is a case where the library declining to send the command is what protects other users of the TPM, rather than the kernel doing it for you.
32+
33+
If you genuinely need to control TPM startup state, you need `/dev/tpm0` and exclusive use of the TPM, or direct SPI access with wolfTPM's own TIS driver.
34+
35+
## Transient handles do not outlive a process
36+
37+
This is the difference most likely to break an existing application.
38+
39+
On `/dev/tpmrm0` the kernel gives each open file description its own handle space. Transient object handles are **virtualized** - the value the TPM assigned is not the value you get back - and everything in that space is **flushed when the file descriptor closes**. So a transient key created by one process is gone by the time a second process runs, and the handle number it printed is meaningless to anyone else.
40+
41+
Creating a primary key on the Jetson fTPM through the resource manager returns:
42+
43+
```
44+
Create Primary Handle: 0x80ffffff
45+
```
46+
47+
not the `0x80000000` a raw device would report. Query the transient handles from a separate process afterwards and the list is empty:
48+
49+
```bash
50+
tpm2_getcap handles-transient # no output - the space was torn down
51+
```
52+
53+
Two practical consequences:
54+
55+
* A "create a key, keep it, use it from the next command" workflow does not work across processes. Do the whole sequence in one process, or make the object persistent with `TPM2_EvictControl` so it gets a stable `0x81xxxxxx` handle that does survive.
56+
* Passing a hard-coded transient handle such as `0x80000000` on a command line will fail. The kernel rejects the reference before it reaches the TPM, and because that happens at the file-descriptor layer the error surfaces as an `errno 22 = Invalid argument` on `read()`, which wolfTPM reports as `TPM_RC_FAILURE` rather than as a handle error. If you see `TPM_RC_FAILURE` alongside `Failed to read from /dev/tpmrm0 ... errno 22`, suspect a stale or cross-process transient handle before suspecting the TPM.
57+
58+
wolfTPM's own `examples/run_examples.sh` hits exactly this: its provisioning section creates IAK and IDevID primaries with `-keep` in one process and then references `0x80000000` / `0x80000001` from another. That block cannot pass on the resource manager by construction. Everything either side of it is unaffected. Use `/dev/tpm0` with exclusive access if you need to run it as written.
59+
60+
## Building
61+
62+
```bash
63+
./autogen.sh
64+
./configure --enable-devtpm
65+
make
66+
```
67+
68+
`--enable-devtpm` uses the kernel node only. Use `--enable-autodetect` instead if you want wolfTPM to try `/dev/tpmrm0`, then `/dev/tpm0`, and finally fall back to probing SPI - useful for one binary that has to run on several boards.
69+
70+
Only one transport can be enabled at a time. `--enable-devtpm` conflicts with `--enable-swtpm` and `--enable-winapi`, and configure will stop if you ask for more than one.
71+
72+
### The x86_64 / aarch64 default
73+
74+
A bare `./configure` on Linux `x86_64` or `aarch64` does **not** produce a build that talks to `/dev/tpmX`. On those hosts wolfTPM auto-enables the software TPMs (swTPM and fwTPM) so that `make check` passes with no hardware attached, and defining `WOLFTPM_SWTPM` suppresses the kernel-device autodetect path. The result talks to a simulator on TCP port 2321.
75+
76+
Selecting any hardware path explicitly turns that default back off - `--enable-autodetect`, `--enable-devtpm`, or any `--enable-<vendor>`. Configure prints a notice when the software default is taken, so check the tail of its output if a build unexpectedly fails to find your TPM.
77+
78+
This bites hardest on single-board `aarch64` machines with a firmware TPM, where the kernel device is the only transport there is.
79+
80+
## Permissions
81+
82+
The TPM character devices are not world-accessible. On a typical system they are mode `0660` owned by group `tss`:
83+
84+
```
85+
crw-rw---- 1 tss root 10, 224 /dev/tpm0
86+
crw-rw---- 1 tss tss 252, 65536 /dev/tpmrm0
87+
```
88+
89+
wolfTPM detects `EACCES` and reports it plainly:
90+
91+
```
92+
Permission denied on /dev/tpm0
93+
Use sudo or add tss group to user.
94+
```
95+
96+
The fix is to put your user in the owning group and start a new login session:
97+
98+
```bash
99+
sudo usermod -aG tss $USER
100+
```
101+
102+
Note that the `tss` group is created by tpm2-tss, and on distributions that ship it the group frequently exists with no members - so this step is required even though the group looks correctly set up.
103+
104+
To use a group of your own instead, add a udev rule:
105+
106+
1) Create the group and add your user:
107+
108+
```bash
109+
sudo addgroup wolftpm
110+
sudo adduser [username] wolftpm
111+
```
112+
113+
2) Create `/etc/udev/rules.d/wolftpm-udev.rules` containing:
114+
115+
```
116+
KERNEL=="tpm[0-9]*", TAG+="systemd", MODE="0660", GROUP="wolftpm"
117+
```
118+
119+
3) Reload the rules: `sudo udevadm control -R`, then re-plug or reboot.
120+
121+
## NVIDIA Jetson Orin (Tegra234) firmware TPM
122+
123+
Jetson Orin platforms carry a TPM 2.0 implemented in firmware, running as a trusted application inside OP-TEE rather than as a discrete package on a bus. Linux reaches it through the `tpm_ftpm_tee` driver, which speaks to the TA over the TEE interface and registers an ordinary TPM chip - so from wolfTPM's point of view it is just another `/dev/tpmrm0`.
124+
125+
Confirm the device is present before building:
126+
127+
```bash
128+
lsmod | grep tpm_ftpm_tee
129+
ls -l /dev/tpm*
130+
cat /sys/class/tpm/tpm0/tpm_version_major # expect 2
131+
```
132+
133+
If the module is missing, try `sudo modprobe tpm_ftpm_tee` and check that the kernel was configured with `CONFIG_TCG_FTPM_TEE`. On NVIDIA's Jetson Linux (L4T) images the driver is present and an `fTPM Device Provisioning Service` systemd unit runs at boot; you can see it complete in the boot log.
134+
135+
Note that an OP-TEE boot message about silicon-identity fTPM provisioning not being enabled refers to a separate NVIDIA feature and does **not** mean the TPM 2.0 device is unavailable.
136+
137+
Build as above with `--enable-devtpm` or `--enable-autodetect`, then confirm with:
138+
139+
```bash
140+
./examples/wrap/caps
141+
```
142+
143+
Because this is a firmware TPM, expect two differences from a discrete part. There is no TIS bus, so the `TPM2: Caps/Did/Vid/Rid` values do not exist and the device is identified purely from `TPM2_GetCapability` properties. Under `--enable-devtpm` the `DEBUG_WOLFTPM` line is still printed but reads all zeros; under `--enable-autodetect` `wolfTPM2_Init_ex` returns as soon as the kernel device opens, before that printf, so the line is absent entirely. And its algorithm coverage is set by the firmware build rather than by a datasheet, so some operations the benchmark exercises may report as unsupported; that is expected, not a fault.
144+
145+
See the main [README.md](/README.md#device-identification) for this platform's identification values and benchmark results.
146+
147+
## Testing
148+
149+
The examples run unchanged on this transport:
150+
151+
```bash
152+
./examples/wrap/caps
153+
./examples/native/native_test
154+
./examples/wrap/wrap_test
155+
./examples/bench/bench
156+
./examples/run_examples.sh
157+
```
158+
159+
`run_examples.sh` already skips the locality test on backends that do not support it.
160+
161+
## CI coverage
162+
163+
Both `--enable-devtpm` and `--enable-autodetect` are build-tested in CI, but not run - GitHub-hosted runners have no `/dev/tpm*` node. Runtime coverage of this transport requires a self-hosted runner with a real TPM bound to the kernel driver.

docs/include.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
dist_doc_DATA+= docs/README.md
66
dist_doc_DATA+= docs/SWTPM.md
7+
dist_doc_DATA+= docs/DEVTPM.md
8+
dist_doc_DATA+= docs/FWTPM.md
79
dist_doc_DATA+= docs/WindowTBS.md
810
dist_doc_DATA+= docs/Doxyfile
911

0 commit comments

Comments
 (0)