Using the Mender client with a TPM (pkcs11)

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 through the PKCS#11 API.

Prerequisites

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:

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

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

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:

ls -l /dev/tpm0

Install the TPM and PKCS#11 packages

Install the base tpm2 tooling we’ll need:

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:

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.

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

Check that there are no handles left:

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.

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.

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:

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.

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.

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:

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:

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.

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

Wrap up

Congratulations, you have successfully set up mender to use the TPM using the PKCS#11 provider!:partying_face: 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!:backhand_index_pointing_down: