# Using the Mender client with a TPM (pkcs11)

**URL:** <https://hub.mender.io/t/using-the-mender-client-with-a-tpm-pkcs11/8301>\
**Category:** Tutorials\
**Created:** [June 29, 2026, 1:53pm UTC](https://hub.mender.io/t/using-the-mender-client-with-a-tpm-pkcs11/8301 "2026-06-29T13:53:54Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Alan](https://avatars.discourse-cdn.com/v4/letter/a/a87d85/32.png) [@Alan](https://hub.mender.io/u/Alan)\
**Post date:** [June 29, 2026, 1:53pm UTC](https://hub.mender.io/t/using-the-mender-client-with-a-tpm-pkcs11/8301/1 "2026-06-29T13:53:54Z")

</div>

This tutorial will guide you to set up the Mender Client on a Raspberry Pi 4/5 to use a [TPM based on the Infineon SLB9672](https://letstrust.de/index.php?/categories/1-TPM) through the PKCS#11 API.

## Prerequisites

- A RPi4 or RPi5 with a LetsTrust / Infineon SLB9672 TPM physically attached. See the [instructions](https://cdn-reichelt.de/documents/datenblatt/A300/BEILEGER_LETSTRUST_TPM.pdf) for installation details
- The the [getting started tutorial up to step 7](https://docs.mender.io/get-started/preparation/prepare-a-raspberry-pi-device#step-7-accept-the-device)
- SSH to the RPi as root

## Enabling the TPM

The Raspberry Pi OS (Debian based reference distribution) ships with both SPI and the TPM disabled.  
First, enable SPI as the physical transport connection layer to the TPM:

```bash
sed -i 's/^#dtparam=spi=on/dtparam=spi=on/' /boot/firmware/config.txt

```

Then append the slb9670 device-tree overlay to the boot config:

```bash
grep -qE '^dtoverlay=tpm-slb9670' /boot/firmware/config.txt \
  || echo 'dtoverlay=tpm-slb9670' >> /boot/firmware/config.txt

```

Reboot for the overlay to take effect, then ssh back in. Confirm that the kernel  
actually exposed the TPM:

```bash
ls -l /dev/tpm0

```

## Install the TPM and PKCS#11 packages

Install the base `tpm2` tooling we’ll need:

```bash
apt-get update -qq
apt-get install -y \
  tpm2-openssl tpm2-tools \
  libtpm2-pkcs11-tools libtpm2-pkcs11-1 \
  pkcs11-provider \
  jq

```

As a confirmation that communication with the TPM works, read the properties and try generating some random bytes:

```bash
tpm2_getcap properties-fixed | head
# Example output:
# TPM2_PT_FAMILY_INDICATOR:
# raw: 0x322E3000
# value: "2.0"
# TPM2_PT_LEVEL:
# raw: 0
# TPM2_PT_REVISION:
# raw: 0x9F
# value: 1.59
# TPM2_PT_DAY_OF_YEAR:
# raw: 0xAC

tpm2_getrandom 4 --hex
# Example output:
# fe82d252

```

## Wipe any stale TPM state

Clean the TPM to a blank slate so that there are no leftovers from eventual prior activities.  
Do this to avoid surprises during the tutorial.

```bash
rm -rf /var/lib/tpm2-pkcs11 /root/.tpm2_pkcs11
tpm2_clear

```

Check that there are no handles left:

```auto
tpm2_getcap handles-persistent

```

## Write the OpenSSL config

Generate a dedicated OpenSSL configuration file. This will contain the setup related to the Mender Client instead of polluting the global space. We’ll point the Mender services to this in a later step.

```bash
cat > /etc/mender-openssl.cnf <<'EOF'
openssl_conf = openssl_init

[openssl_init]
providers = provider_sect

[provider_sect]
default = default_sect
pkcs11 = pkcs11_sect

[default_sect]
activate = 1

[pkcs11_sect]
module = /usr/lib/aarch64-linux-gnu/ossl-modules/pkcs11.so
pkcs11-module-path = /usr/lib/aarch64-linux-gnu/pkcs11/libtpm2_pkcs11.so.1.9.0
activate = 1
EOF

```

## Create the PKCS#11 token and the TPM-backed key

Generate the key in the TPM. We assume the address pins are on the `0000` default for the example. If your setup differs, you need to adjust accordingly.

```bash
mkdir -p /var/lib/tpm2-pkcs11
export TPM2_PKCS11_STORE=/var/lib/tpm2-pkcs11
tpm2_ptool init

TPM2_PKCS11_STORE=/var/lib/tpm2-pkcs11 \
  tpm2_ptool addtoken --pid=1 --sopin=0000 --userpin=0000 --label=mender

TPM2_PKCS11_STORE=/var/lib/tpm2-pkcs11 \
  tpm2_ptool addkey --label=mender --userpin=0000 --algorithm=ecc256 --key-label=device-key

```

As a test, reread the public key from the newly generated private key in the TPM:

```bash
PEM=$(OPENSSL_CONF=/etc/mender-openssl.cnf openssl pkey \
  -provider default -provider pkcs11 \
  -in 'pkcs11:token=mender;object=device-key;type=public' \
  -pubin -pubout) \
  && printf '%s\n\nSHA-256: %s\n' "$PEM" "$(printf '%s\n' "$PEM" | sha256sum | cut -d' ' -f1)"

# Example output
# -----BEGIN PUBLIC KEY-----
# MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk4J3wX6XF6lphz27WfaMTrQk6ycB
# DNNLmP5l7+6do7FK6+b35HV5/1fDVw7iOwNloDlsxFvkOJGM9IBfpAW1EA==
# -----END PUBLIC KEY-----
# 
# SHA-256: 2a8167401d66b29c7177a1319f7f4af4d6cac27fe8b86e230f48594fb636566d

```

The TPM is now ready and the key is in place. The steps below this point adjust the Mender client to use the key from the TPM.

## Stop the Mender Client daemons and remove the auto-generated key

By default the Mender client generates a private key on the rootfs. Stop the  
services and delete it.

```bash
systemctl stop mender-authd mender-updated
rm -f /var/lib/mender/mender-agent.pem

```

## Decommission the device in hosted Mender

Because of the steps from the getting started the device already attempted to reach Hosted Mender and is listed as accepted or pending. Decommission this device since the authentication was done with the default key. We will repeat this step with the key from the TPM.

## Point both Mender daemons at the scoped cnf

Point `mender-authd` and `mender-updated` at the OpenSSL config via drop-in environment settings in their systemd units.

```bash
mkdir -p /etc/systemd/system/mender-authd.service.d \
         /etc/systemd/system/mender-updated.service.d

cat > /tmp/tpm2.conf <<'EOF'
[Service]
Environment="OPENSSL_CONF=/etc/mender-openssl.cnf"
Environment="TPM2_PKCS11_STORE=/var/lib/tpm2-pkcs11"
EOF

cp /tmp/tpm2.conf /etc/systemd/system/mender-authd.service.d/tpm2.conf
cp /tmp/tpm2.conf /etc/systemd/system/mender-updated.service.d/tpm2.conf

systemctl daemon-reload

```

## Point `mender.conf` at the TPM key

The getting started process populated the majority of fields in `mender.conf` needed to connect to hosted Mender. The only field we need to change is `Security.AuthPrivateKey`.

Set it to the PKCS#11 URI of the key just created:

```bash
KEY_URI='pkcs11:token=mender;object=device-key;type=private?pin-value=0000'

cp /etc/mender/mender.conf /etc/mender/mender.conf.bak
jq --arg key "$KEY_URI" '.Security.AuthPrivateKey = $key' \
  /etc/mender/mender.conf.bak > /etc/mender/mender.conf

```

## Start the Mender daemons again

Bring both daemons back up:

```bash
systemctl start mender-authd mender-updated

```

## Accept the device in hosted Mender

At this point the device is trying to authorize with hosted Mender again, this time with the TPM key.

As you look at the authset from the key and what is printed in hosted Mender, you can confirm it’s now using the key from the TPM.

```bash
PEM=$(OPENSSL_CONF=/etc/mender-openssl.cnf openssl pkey \
  -provider default -provider pkcs11 \
  -in 'pkcs11:token=mender;object=device-key;type=public' \
  -pubin -pubout) \
  && printf '%s\n\nSHA-256: %s\n' "$PEM" "$(printf '%s\n' "$PEM" | sha256sum | cut -d' ' -f1)"

# Example output
# -----BEGIN PUBLIC KEY-----
# MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk4J3wX6XF6lphz27WfaMTrQk6ycB
# DNNLmP5l7+6do7FK6+b35HV5/1fDVw7iOwNloDlsxFvkOJGM9IBfpAW1EA==
# -----END PUBLIC KEY-----
# 
# SHA-256: 2a8167401d66b29c7177a1319f7f4af4d6cac27fe8b86e230f48594fb636566d

```

 ![key-match](https://canada1.discourse-cdn.com/flex036/uploads/mender/original/2X/2/231eda6450a46bd26fa1483dbaf62eef6fc75333.png)

## Wrap up

Congratulations, you have successfully set up mender to use the TPM using the PKCS#11 provider!🥳 You can accept the device and work with it in the same way as if using the default autogenerated key on the rootfs - without the risk of losing the device key material.

Are you already using TPMs or even pre-generated keys in your connected device? How did you streamline the provisioning process? Let us know!👇
