Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TPM backed mTLS in Python #3461

Closed
howaryoo opened this issue Feb 26, 2025 · 6 comments
Closed

TPM backed mTLS in Python #3461

howaryoo opened this issue Feb 26, 2025 · 6 comments

Comments

@howaryoo
Copy link

howaryoo commented Feb 26, 2025

I wish to have a Python asyncio streaming server and client communicate in mTLS.
In issue #3397 @wudiqiang2024 has confirmed that this it is possible since paho-mqtt also uses Python's ssl standard library under the hood.

My issue is that I get an ErrorCode (0x0000018a)

In my python code as below, when using the TPM backed certificate, I get an ErrorCode (0x0000018a)
context.load_cert_chain(certfile="server.crt", keyfile="tpmkey.pem")

Full error and all details below.
Any information on what I did wrong would be greatly appreciated.

OS: ubuntu 24.04 LTS
openssl version: OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
tpm2-tools version: 5.6
tpm2-openssl version: 1.2.0
installed with the package manager of the distribution.

Commands used to create the keys/pem:

tpm2_createprimary -C o -G rsa -c primary.ctx                                                                                          
tpm2_evictcontrol -c primary.ctx 0x81100001                                                                                            
tpm2_getcap handles-persistent                                                                                                         
tpm2_create -C 0x81100001 -G rsa -u key.pub -r key.priv                                                                                
tpm2_load -C 0x81100001 -u key.pub -r key.priv -c key.ctx                                                                              
tpm2_evictcontrol -c key.ctx 0x81100002                                                                                                
tpm2_encodeobject -C 0x81100002 -u key.pub -r key.priv -o tpmkey.pem          

openssl configuration

$ openssl list -providers
Providers:
  default
    name: OpenSSL Default Provider
    version: 3.0.13
    status: active
  tpm2
    name: TPM 2.0 Provider
    version: 1.2.0
    status: active

Commands used to create the server certificate:

Please note that I had to use open ssl version 3.4 installed with conda for these commands to work.
Otherwise with my system install of openssl I would get this error:
4027CB3B267B0000:error:0300009E:digital envelope routines:do_sigver_init:no default digest:../crypto/evp/m_sigver.c:318:

openssl req -new -key "handle:0x81100002" -subj "/CN=server" -out server.csr -provider tpm2 -provider default
openssl x509 -req -in server.csr -signkey "handle:0x81100002" -out server.crt -days 365 -provider tpm2 -provider default

Python server code

import ssl
import asyncio

print("openssl version:", ssl.OPENSSL_VERSION)

def create_server_ssl_context():
    # Create an SSL context for client authentication (mTLS)
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.verify_mode = ssl.CERT_REQUIRED

    # Load the server certificate and the TPM key reference.
    # The key file 'tpmkey.pem' contains a pointer to the TPM key via the tpm2tss engine.
    context.load_cert_chain(certfile="server.crt", keyfile="tpmkey.pem")
    
    # Load the CA certificate(s) to verify client certificates.
    context.load_verify_locations("client.crt")
    return context

async def handle_client(reader, writer):
    try:
        data = await reader.read(1024)
        print("Server received:", data.decode().strip())
        writer.write(b"Hello from TPM-backed mTLS server!")
        await writer.drain()
    except Exception as e:
        print("Error handling client:", e)
    finally:
        writer.close()
        await writer.wait_closed()

async def main():
    ssl_context = create_server_ssl_context()
    server = await asyncio.start_server(handle_client, '0.0.0.0', 8443, ssl=ssl_context)
    print("Server listening on port 8443")
    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

Error:

openssl version: OpenSSL 3.0.13 30 Jan 2024
WARNING:esys:src/tss2-esys/api/Esys_Load.c:324:Esys_Load_Finish() Received TPM Error 
ERROR:esys:src/tss2-esys/api/Esys_Load.c:112:Esys_Load() Esys Finish ErrorCode (0x0000018a) 
WARNING:esys:src/tss2-esys/api/Esys_Load.c:324:Esys_Load_Finish() Received TPM Error 
ERROR:esys:src/tss2-esys/api/Esys_Load.c:112:Esys_Load() Esys Finish ErrorCode (0x0000018a) 
Traceback (most recent call last):
  File "/tmp/provider_test_new/mtls_server.py", line 39, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/tmp/provider_test_new/mtls_server.py", line 32, in main
    ssl_context = create_server_ssl_context()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/provider_test_new/mtls_server.py", line 13, in create_server_ssl_context
    context.load_cert_chain(certfile="server.crt", keyfile="tpmkey.pem")
ssl.SSLError: [SSL] PEM lib (_ssl.c:3916)
@JuergenReppSIT
Copy link
Member

JuergenReppSIT commented Feb 26, 2025

context.load_cert_chain(certfile="server.crt", keyfile="tpmkey.pem")
should work if you use the parent 0x81100001 for load and encodeobject:
tpm2_load -C 0x81100001 -u key.pub -r key.priv -c key.ctx
tpm2_encodeobject -C 0x81100001 -u key.pub -r key.priv -o tpmkey.pem

@JuergenReppSIT
Copy link
Member

@howaryoo There was a fix for tpm2_encodeobject (#3459). it would be good to use the current master of the tpm tools.

@howaryoo
Copy link
Author

thanks @JuergenReppSIT!

using the parent handle (0x81100001) for load and encodeobject commands fixed the 0x0000018a error.

with my installed version of the tpm tools (5.6)
the .pem file is read but openssl requires a pass phrase. Your fix in #3459 is therefore mandatory.

openssl version: OpenSSL 3.0.13 30 Jan 2024
Enter PEM pass phrase:

using the current master of the tpm tools, I loaded the handle again and created the .pem file again with:

~/usr/bin/tpm2_load -C 0x81100001 -u key.pub -r key.priv -c key.ctx
~/usr/bin/tpm2_encodeobject -C 0x81100001 -u key.pub -r key.priv -o tpmkey.pem

The .pem file is now apparently loaded without errors in the ssl context but I get a Segmentation fault (core dumped) when the server recieves a client connection.
Any recommendations you might have would be grealty appreciated.
Can building everything from source instead of using the distibution packages help?

openssl version: OpenSSL 3.0.13 30 Jan 2024
Server listening on port 8443
Segmentation fault (core dumped)

@wudiqiang2024
Copy link

wudiqiang2024 commented Feb 27, 2025

Hi @howaryoo , you can try adding -p to this command, and the passphrase should not appear.
tpm2_encodeobject -C 0x81100001 -u key.pub -r key.priv -o tpmkey.pem -p

@howaryoo
Copy link
Author

Thanks @wudiqiang2024,
and really thanks also for your original issue #3397 which helped me get on the right track.

Below a recap of the commands I used to have a working client and server using mTLS.
I followed @billatarm's advice not to make the child's handle persistent which is consistent with @JuergenReppSIT response also.

TPM commands

tpm2_createprimary -C o -G ecc -c primary.ctx
tpm2_evictcontrol -c primary.ctx 0x81110001
tpm2_create -C 0x81110001 -G ecc -u key.pub -r key.priv
tpm2_encodeobject -C 0x81110001 -u key.pub -r key.priv -o tpm.pem -p

Server certificate (TPM backed)

Please note that I used open ssl version 3.4 installed with conda for everything below

openssl req -new -key tpm.pem -subj "/CN=server" -out server.csr -provider tpm2 -provider default
openssl x509 -req -in server.csr -signkey tpm.pem -out server.crt -days 365 -provider tpm2 -provider default

Client certificate (non TPM backed)

openssl genpkey -algorithm RSA -out client.key
openssl req -new -key client.key -subj "/CN=client" -out client.csr
openssl x509 -req -in client.csr -signkey client.key -out client.crt -days 365

Server

code as above with the pem file name changed

$ python mtls_server.py 
openssl version: OpenSSL 3.4.0 22 Oct 2024
Server listening on port 8443
Server received: Hello from client!

Client

import asyncio
import ssl

async def mtls_client():
    ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    ssl_context.load_cert_chain(certfile="client.crt", keyfile="client.key")
    ssl_context.check_hostname = False  # No need for hostname verification
    ssl_context.verify_mode = ssl.CERT_REQUIRED
    ssl_context.load_verify_locations("server.crt")  # Trust only the self-signed server cert

    reader, writer = await asyncio.open_connection("127.0.0.1", 8443, ssl=ssl_context)

    message = "Hello from client!"
    print(f"Sending: {message}")
    writer.write(message.encode())
    await writer.drain()

    response = await reader.read(1024)
    print(f"Received: {response.decode().strip()}")

    writer.close()
    await writer.wait_closed()

asyncio.run(mtls_client())
$ python mtls_client.py 
Sending: Hello from client!
Received: Hello from TPM-backed mTLS server!

@JuergenReppSIT
Copy link
Member

@howaryoo The fix I mentioned (#3397) will change the behavior of tpm2_encodeobject and -p has to be removed if the new version is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants