Skip to content

Adds TLS-PSK support to Python SSL context - #14396

Closed
aminop1us wants to merge 7 commits into
micropython:masterfrom
aminop1us:tls_psk
Closed

aminop1us wants to merge 7 commits into
micropython:masterfrom
aminop1us:tls_psk

Conversation

@aminop1us

Copy link
Copy Markdown

This pull request adds support to the ssl module for TLS-PSK (pre-shared key) authentication. This implementation is code compatible with CPython 3.13 for both client and server side, for example as described here:
https://docs.python.org/3.13/library/ssl.html#ssl.SSLContext.set_psk_server_callback
https://docs.python.org/3.13/library/ssl.html#ssl.SSLContext.set_psk_client_callback

The strong motivation for adding this functionality to MicroPython is that typical TSL using PKI on microcontrollers is heavy and resource intensive. For the kinds of platforms that MicroPython is targeting, many developers will prefer the lighter and simpler PSK alternative, particularly for development and testing. In fact this is exactly the target that TLS-PSK was designed for, so MicroPython would benefit greatly from this support being built-in to the ssl module. Since CPython 3.13 added this as a standard, we tried to be as compliant as possible. We are currently using these TLS-PSK capabilities in several in-house projects, and wanted to share it back in the hope that others might benefit from this, too.

Note: due to the underlying MBEDTLS library not supporting client side callbacks for TLS-PSK, we kept the client-side callback API compatible with CPython, but call the callback immediately and pass the returned identity and key to MBEDTLS when it creates the TLS connection. We tried to keep to CPython compatibility as much as possible for interoperability. We have tested with simple server and client code that runs on both our MicroPython branch as well as CPython 3.13.0a3.

We have tried to follow the MicroPython standard coding conventions, but if there are any requests for changes before it can be merged, please let us know and we will do our best to assist in any changes required.

In summary, this implementation allows the application to set a TLS PSK callback function on SSLContext for server-side connections, so the callback function is called for each handshake. Here is the documentation from the set_psk_server_callback link above:
"The parameter callback is a callable object with the signature: def callback(identity: str | None) -> bytes. The identity parameter is an optional identity sent by the client which can be used to select a corresponding PSK. The return value is a bytes-like object representing the pre-shared key. Return a zero length PSK to reject the connection."

@aminop1us
aminop1us marked this pull request as ready for review April 30, 2024 04:00
@codecov

codecov Bot commented Apr 30, 2024 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.43%. Comparing base (55e75c4) to head (3ecb741).
⚠️ Report is 2264 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #14396   +/-   ##
=======================================
  Coverage   98.42%   98.43%           
=======================================
  Files         161      161           
  Lines       21253    21278   +25     
=======================================
+ Hits        20919    20944   +25     
  Misses        334      334           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Apr 30, 2024 •

Copy link
Copy Markdown

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64: +3080 +0.368% standard[incl +224(data) +32(bss)]
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2: +1464 +0.167% RPI_PICO_W[incl +24(bss)]
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS

@Carglglz

Copy link
Copy Markdown
Contributor

@aminop1us since this would be a new feature, could you add a test (see tests/multi_net for examples) so the code coverage can pass, thanks 👍🏼

@aminop1us

Copy link
Copy Markdown
Author

@aminop1us since this would be a new feature, could you add a test (see tests/multi_net for examples) so the code coverage can pass, thanks 👍🏼

thank for your suggestion, I will add a new test case.

@aminop1us

Copy link
Copy Markdown
Author

@aminop1us since this would be a new feature, could you add a test (see tests/multi_net for examples) so the code coverage can pass, thanks 👍🏼

I added the test and updated the pull request

@aminop1us

Copy link
Copy Markdown
Author

please let me know if there is anything else I can do

@dpgeorge dpgeorge added the extmod Relates to extmod/ directory in source label Jul 15, 2024
@dpgeorge

Copy link
Copy Markdown
Member

Thank you for the contribution, this is a good feature that's definitely very useful in embedded contexts.

Please see a prior attempt at this in #5544.

I did not realise that CPython had very recently added support for PSK. That's great... although we did move to a custom tls module so we could implement things like PSK and DTLS (see #10062) without having to wait for CPython do it.

So, since we have our own tls module, we can implement PSK in the best (minimal) way for embedded, ie we don't need to follow CPython's API. Then the ssl wrapper code in micropython-lib can be updated to support the CPython compatible PSK API (which I think requires calling ssl_context.set_ciphers("PSK")).

@aminop1us for your use case, do you find it beneficial for your MicroPython code to use PSK in a CPython compatible way, ie with this callback function? Or would you be just as happy using a custom MicroPython tls PSK API? Eg maybe instead of a callback function one just passes in a dictionary mapping the identity to the key, like ssl_context.psk_keys = my_dict. In your use case is a dict of identity/key pairs good enough, or do you use the full power of a callback?

Signed-off-by: Sumeta Boonchamoi <[email protected]>
@dpgeorge

Copy link
Copy Markdown
Member

For reference, this is the PR that added PSK support in CPython: python/cpython#103181

@aminop1us

Copy link
Copy Markdown
Author

Thank you for the contribution, this is a good feature that's definitely very useful in embedded contexts.

Please see a prior attempt at this in #5544.

I did not realise that CPython had very recently added support for PSK. That's great... although we did move to a custom tls module so we could implement things like PSK and DTLS (see #10062) without having to wait for CPython do it.

So, since we have our own tls module, we can implement PSK in the best (minimal) way for embedded, ie we don't need to follow CPython's API. Then the ssl wrapper code in micropython-lib can be updated to support the CPython compatible PSK API (which I think requires calling ssl_context.set_ciphers("PSK")).

@aminop1us for your use case, do you find it beneficial for your MicroPython code to use PSK in a CPython compatible way, ie with this callback function? Or would you be just as happy using a custom MicroPython tls PSK API? Eg maybe instead of a callback function one just passes in a dictionary mapping the identity to the key, like ssl_context.psk_keys = my_dict. In your use case is a dict of identity/key pairs good enough, or do you use the full power of a callback?

As long as it is possible for us to write a wrapper to be CPython compatible, we are fine with that.

@dpgeorge dpgeorge added this to the release-1.25.0 milestone Oct 16, 2024
@keenanjohnson

Copy link
Copy Markdown
Contributor

Hey @aminop1us and @dpgeorge I was just inquiring what the status here was and if you wanted anyone to jump in to finish this task out?

Having a PSK would micropython to fully support DTLS which we merged the main work for earlier this year in #15764.

Our project previously implemented a similiar version of this in the way that @dpgeorge described above I think spread through these commits:

If it is useful for someone help complete this, I'm happy to jump in or submit our work in a new PR or whatever is most useful to see this closed out.

Thanks all!

@keenanjohnson

Copy link
Copy Markdown
Contributor

I am assuming the original author does not intend to finish this, so I'll try to pick it up.

@keenanjohnson

Copy link
Copy Markdown
Contributor

I have a draft of the approach suggested by @dpgeorge above here: #17074.

If that looks in line with your suggested approach, I'm happy to clean up the PR (docs, squash commits) so it's ready for merge.

@keenanjohnson

Copy link
Copy Markdown
Contributor

Hey @dpgeorge ! Just wondering if you had a chance to look at the progress in #17074 and if that seems in line with the direction you would like to head?

@projectgus

Copy link
Copy Markdown
Contributor

Just to catch up where TLS-PSK is at between this PR and #17074, as it would be great to have this support in MicroPython:

On balance we either need the CPython-style callbacks built-in, or (as @dpgeorge and @aminop1us have commented) we need a design which can implement a CPython compatibility layer on top of whatever we have in MicroPython. This PR has CPython-style callbacks already and looks to be pretty lightweight, so that actually seems pretty good to me.

One thing I'm unsure about: do we also need a version of CPython's ctx.set_ciphers('PSK') that calls through to mbedtls_ssl_conf_ciphersuites() and limits the accepted ciphers to only PSK ones (as implemented in #17074)? It's unclear to me if calling mbedtls_ssl_conf_psk() enforces a PSK requirement on both client and server, or if this leaves a gap where a connection using a non-PSK suite could be accepted (therefore breaking the expectation of mutual auth). The mbedTLS ssl_client2.c and ssl_server2.c examples don't seem to do anything special unless the user also passes a ciphersuite on the command line, but that's example code not a production application. 🤷

Related point, it's fantastic this PR has a test (thank you) but probably we also need some basic "adversarial" testing for cases like the above (i.e. that a PSK-enabled client won't connect to a non-PSK server, and vice versa).

As no one is actively working on this I'm going to take it off the next milestone, but TLS-PSK support would be very welcome if someone does want to pick it up and submit a new PR which takes the above into consideration.

@projectgus projectgus removed this from the release-1.29.0 milestone May 27, 2026
@dpgeorge

dpgeorge commented May 27, 2026 •

Copy link
Copy Markdown
Member

To add to @projectgus 's comments above, I think it is very useful to allow a callback for the server side (at the very least so we can implement CPython compatible set_psk_server_callback() in ssl.py).

This PR already implements a server callback, but if we want to do that in a more MicroPython-y way, as mentioned in #14396 (comment), we could have an attribute to which you store a dictionary that maps identity string to the required PSK:

ssl_ctx.server_psk_keys = dict

Allowing this also allows callbacks to user code: the user could pass in an object that implements __getitem__ like this:

class PSKDict:
    def __getitem__(self, identity_str):
        # called when mbedTLS needs a PSK for the given identity_str
        return ...

ssl_ctx.server_psk_keys = PSKDict()

This approach is relatively minimal, allows the simple case of supplying a fixed key (or keys per client), but also allows a callback if needed.

Note: I haven't tested this idea and maybe there are some caveats that make it unworkable.

@dpgeorge

dpgeorge commented Jun 9, 2026

Copy link
Copy Markdown
Member

Closing in favour of #17074.

@dpgeorge dpgeorge closed this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extmod Relates to extmod/ directory in source

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants