Part 5 – Using the Private Key from the TPM (Python Approach)¶
In the previous part, we discussed why and when you would want to use a TPM.
Now we return to our original client/server application and modify client.py
so that it uses a private key from the TPM device.
Preparation – Creating a TSS2 Private Key¶
We previously introduced TSS2 private key file. Now we will create one.
On the embedded device (with TPM available), run:
root@imx8mp-var-dart:~# openssl genpkey -provider tpm2 -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out client.tss2key
This command:
- Instructs OpenSSL to use the tpm2 provider
- Generates an EC NIST P-256 key
- Stores a TSS2 key reference file (
client.tss2key)
Important:
The resulting client.tss2key file is bound to this specific TPM.
If you copy it to another device - even one with an identical TPM model - it
will not work. The file references internal TPM state that cannot be transferred.
Creating a Certificate Signing Request (CSR)¶
Next, we create a CSR using the TPM-backed key:
root@imx8mp-var-dart:~# openssl req -new -provider tpm2 -provider default \
-key client.tss2key -subj "/CN=trusted-client" -out client-tpm.csr
The resulting client-tpm.csr would normally be sent to your CA for signing.
For demonstration purposes, we once again sign it ourselves — using the same CA created in Part 3.
We copy client-tpm.csr from our Embedded Device and do this on the development PC.
$ openssl x509 -req \
-in client-tpm.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client-tpm.crt -days 365 -sha256 \
-extfile - < /dev/stdin << 'EOF'
basicConstraints=CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=clientAuth
EOF
# Here starts output from the command
Certificate request self-signature ok
subject=CN=trusted-client
You now have:
client.tss2key- TPM-backed private key referenceclient-tpm.crt- Signed client certificate
Modifying the Client Application¶
The only required code change is how we load the client key into the SSL context.
# Present client certificate
context.load_cert_chain(
certfile=client-tpm.crt,
keyfile=client.tss2key
)
All remaining application logic remains unchanged.
However, we must ensure that OpenSSL loads the TPM provider at runtime.
Configuring OpenSSL to Use the TPM Provider¶
To properly address the TPM module, we create a dedicated OpenSSL configuration file:
root@imx8mp-var-dart:~# cat openssl-tpm2.cnf
# openssl-tpm2.cnf
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
tpm2 = tpm2_sect
[default_sect]
activate = 1
[tpm2_sect]
activate = 1
# module path varies by distro/build:
module = /usr/lib/ossl-modules/tpm2.so
Afterwards this file needs to be put in the environment variable OPENSSL_CONF
Running the TPM-Backed Client¶
Client output:
root@imx8mp-var-dart:~# OPENSSL_CONF=openssl-tpm2.cnf ./client.py 192.168.178.2
Sending: GET_SECRET
Received: THIS_IS_THE_SECRET
The server output (unchanged from Part 3):
$ ./server.py
TLS server listening on 0.0.0.0:5000
[+] TLS connection from ('192.168.178.34', 44778)
Cipher: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
Client cert subject: ((('commonName', 'trusted-client'),),)
From the server's perspective, nothing has changed. The TLS handshake and client authentication work exactly as before.
The critical difference is internal:
The private key never left the TPM.
The signing operation during the TLS handshake is now performed inside the TPM, and only the signature result is returned to OpenSSL.
What We Achieved¶
Compared to Part 3:
- The client still performs mutual TLS authentication.
- The certificate remains unchanged.
- The application code barely changed.
But:
- The private key is no longer stored in plaintext on the filesystem.
- The private key is never loaded into system RAM.
In the next part we will look at a C implementation. If you prefer to skip that, you can also go straight to the Conclusion which summarizes the overall architecture for both Python and C.