Skip to content

Adds TLS-PSK support to Python SSL context #14396

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

aminop1us
Copy link

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."

moprg added 4 commits April 30, 2024 10:30
Signed-off-by: moprg <moprg@yannix.com>
Signed-off-by: moprg <moprg@yannix.com>
Signed-off-by: moprg <moprg@yannix.com>
Signed-off-by: moprg <moprg@yannix.com>
@aminop1us aminop1us marked this pull request as ready for review April 30, 2024 04:00
Copy link

codecov bot commented Apr 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.43%. Comparing base (55e75c4) to head (3ecb741).

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.

Copy link

github-actions bot commented Apr 30, 2024

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
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
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.

Signed-off-by: moprg <moprg@yannix.com>
@aminop1us
Copy link
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
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
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 <sumeta.prg@gmail.com>
@dpgeorge
Copy link
Member

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

@aminop1us
Copy link
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
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
Contributor

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

@keenanjohnson
Copy link
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
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?

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.

4 participants