Skip to content

Conversation

keenanjohnson
Copy link
Contributor

@keenanjohnson keenanjohnson commented Apr 4, 2025

This pull request adds support to the ssl module for TLS-PSK (pre-shared key) authentication.

Copy link

github-actions bot commented Apr 4, 2025

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64: +2920 +0.340% standard[incl +240(data) +64(bss)]
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2: +1660 +0.180% RPI_PICO_W[incl +28(bss)]
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

Copy link

codecov bot commented Apr 4, 2025

Codecov Report

Attention: Patch coverage is 93.87755% with 3 lines in your changes missing coverage. Please review.

Project coverage is 98.53%. Comparing base (3805e65) to head (1622787).
Report is 161 commits behind head on master.

Files with missing lines Patch % Lines
extmod/modtls_mbedtls.c 93.87% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #17074      +/-   ##
==========================================
- Coverage   98.54%   98.53%   -0.01%     
==========================================
  Files         169      169              
  Lines       21890    21939      +49     
==========================================
+ Hits        21571    21618      +47     
- Misses        319      321       +2     

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

@dpgeorge dpgeorge added the extmod Relates to extmod/ directory in source label May 7, 2025
@keenanjohnson
Copy link
Contributor Author

Hey @projectgus and @dpgeorge . I've created this draft of an approach to add support for TLS PSK based on the work in #17074. I've bumped the other thread a few times, but figured I would move the discussion into my actual implementation here.

There was some discussion on the different possible approaches and I believe what I have implemented here follows @dpgeorge 's suggestion in 17074. How does it look and does this direction seem good? If so, I can finalize the paperwork to close this out.

@projectgus projectgus self-requested a review August 26, 2025 02:10
Copy link
Contributor

@AJMansfield AJMansfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commits in this PR need to be squashed and reworded to match MicroPython's Code Conventions. Aside from that:

Comment on lines 443 to 452
if (strcmp(ciphername, "PSK") == 0) {
ssl_context->use_psk = true;
set_psk_ciphersuites(&ssl_context->conf);
return mp_const_none;
}

// Check if this is a PSK ciphersuite name
if (strncmp(ciphername, "PSK-", 4) == 0 ||
strncmp(ciphername, "TLS-PSK-", 8) == 0 ||
strncmp(ciphername, "TLS_PSK_", 8) == 0) {
Copy link
Contributor

@AJMansfield AJMansfield Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reference that can be cited here to justify this parse? This needs some authority behind it.

Also, why not just use mbedtls's own API to determine both the "is psk" and "is recognised" question together?

const mbedtls_ssl_ciphersuite_t *info = mbedtls_ssl_ciphersuite_from_string(ciphername);
if (mbedtls_ssl_ciphersuite_uses_psk(info)) {
    ...

(With some extra bits to correctly handle the 'failed lookup' case, of course.)
(Assuming that special-casing PSK here is even the right design anyway.)

// Enable PSK mode
ssl_context->use_psk = true;

// Create a ciphersuite array with just this one ciphersuite
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should PSK ciphers be singleton, rather than parsed and accumulated as part of the existing list code?

Comment on lines 557 to 651
// SSLContext.set_psk_identity(identity)
static mp_obj_t ssl_context_set_psk_identity(mp_obj_t self_in, mp_obj_t identity) {
mp_obj_ssl_context_t *self = MP_OBJ_TO_PTR(self_in);
self->psk_identity = identity;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(ssl_context_set_psk_identity_obj, ssl_context_set_psk_identity);

// SSLContext.set_psk_key(key)
static mp_obj_t ssl_context_set_psk_key(mp_obj_t self_in, mp_obj_t key) {
mp_obj_ssl_context_t *self = MP_OBJ_TO_PTR(self_in);
self->psk_key = key;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(ssl_context_set_psk_key_obj, ssl_context_set_psk_key);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the intermediate self->psk_identity and self->psk_key variables? i.e. instead of a single function that combines the code with the code in ssl_socket_make_new to call mbedtls_ssl_conf_psk directly.

@@ -603,6 +708,22 @@ static mp_obj_t ssl_socket_make_new(mp_obj_ssl_context_t *ssl_context, mp_obj_t

mbedtls_ssl_init(&o->ssl);

// Configure PSK if enabled
if (ssl_context->use_psk && ssl_context->psk_identity != mp_const_none && ssl_context->psk_key != mp_const_none) {
Copy link
Contributor

@AJMansfield AJMansfield Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition should be more paranoid so it can give a better error. Mbedtls would error eventually, but any case where you don't have either all or none of those conditions, should be an error already.

Comment on lines 714 to 805
size_t psk_identity_len;
const byte *psk_identity = (const byte *)mp_obj_str_get_data(ssl_context->psk_identity, &psk_identity_len);

size_t psk_key_len;
const byte *psk_key = (const byte *)mp_obj_str_get_data(ssl_context->psk_key, &psk_key_len);
Copy link
Contributor

@AJMansfield AJMansfield Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These conversions could hit the bad_implicit_conversion path and bypass cleanup --- neither this method nor the setters for those variables actually check mp_obj_is_str_or_bytes.

Comment on lines +401 to +445
// Define known PSK ciphersuites
// These are common PSK ciphersuites supported by mbedtls
static const int known_psk_ciphersuites[] = {
MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA256,
MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA,
MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA,
MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256,
MBEDTLS_TLS_PSK_WITH_AES_256_GCM_SHA384,
0 // Terminating zero
};

// Count available PSK ciphersuites
int count = 0;
for (int i = 0; known_psk_ciphersuites[i] != 0; i++) {
count++;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get the actual authoritative list of supported PSK suites by filtering the output from mbedtls_ssl_list_ciphersuites using mbedtls_ssl_ciphersuite_from_id and mbedtls_ssl_ciphersuite_uses_psk.

// Allocate memory for PSK ciphersuites
psk_ciphersuites = m_new(int, count + 1);
if (psk_ciphersuites == NULL) {
mp_raise_OSError(MP_ENOMEM);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant with the m_malloc_fail path already present in the allocator.

// Create a ciphersuite array with just this one ciphersuite
ssl_context->ciphersuites = m_new(int, 2);
if (ssl_context->ciphersuites == NULL) {
mp_raise_OSError(MP_ENOMEM);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant with the m_malloc_fail path already present in the allocator.

// Configure PSK
ret = mbedtls_ssl_conf_psk(&ssl_context->conf, psk_key, psk_key_len, psk_identity, psk_identity_len);
if (ret != 0) {
goto cleanup;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path needs coverage, with test cases that set invalid keys and identites.

@AJMansfield
Copy link
Contributor

AJMansfield commented Aug 26, 2025

For overall structure and direction, though:

  • Drop the separate setters and wrap mbedtls_ssl_conf_psk a bit more thinly.
  • Let the SSL/TLS cipher suite abstraction work for you, instead of making PSK a special case in places it could just not be.

@AJMansfield
Copy link
Contributor

AJMansfield commented Aug 26, 2025

This should probably be feature-gated? e.g. with a new MICROPY_PY_SSL_PSK added to py/mpconfig.h next to the other MICROPY_PY_SSL_* flags. +3kB is a lot, even for mbedtls builds.

Re: #14396 and the client-callback and server-callback API, in my opinion it could be appropriate to add those too, at least if behind another feature gate like MICROPY_PY_SSL_PSK_CALLBACKS; though that would be a PR for after the basic version lands.

This commit adds Pre-Shared Key (PSK) support to the mbedTLS-based TLS
implementation in MicroPython.

New features:
- SSLContext.set_psk_identity(identity) method for setting PSK identity
- SSLContext.set_psk_key(key) method for setting PSK key
- SSLContext.set_ciphers('PSK') method for enabling PSK ciphersuites
- Support for both generic PSK mode and specific PSK ciphersuite names
- Full client and server PSK support

The implementation enables PSK authentication mode which allows TLS connections
without traditional certificate-based authentication, using pre-shared keys
instead.

Tests are added to verify PSK functionality with both client and server roles.
Replace string-based PSK ciphersuite detection with proper mbedtls API calls.

  - Use mbedtls_ssl_ciphersuite_from_string() instead of strncmp() calls
  - Add ciphersuite_uses_psk() helper function for PSK detection
  - Apply consistent mbedtls validation for both PSK and non-PSK ciphersuites
  - Improve robustness by eliminating fragile string parsing
  - Maintain backward compatibility with existing PSK functionali
@keenanjohnson
Copy link
Contributor Author

Squashed and working through review feedback now.

Comment on lines +531 to +570
// Helper function to check if a ciphersuite uses PSK
static bool ciphersuite_uses_psk(const mbedtls_ssl_ciphersuite_t *info) {
if (info == NULL) {
return false;
}

// Check if ciphersuite ID corresponds to any PSK ciphersuite
int id = mbedtls_ssl_ciphersuite_get_id(info);

// Check for common PSK ciphersuites based on their IDs
// These correspond to the MBEDTLS_TLS_*_PSK_* constants
return (id == 0x2C || // MBEDTLS_TLS_PSK_WITH_NULL_SHA
id == 0x2D || // MBEDTLS_TLS_DHE_PSK_WITH_NULL_SHA
id == 0x2E || // MBEDTLS_TLS_RSA_PSK_WITH_NULL_SHA
id == 0x8C || // MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA
id == 0x8D || // MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA
id == 0x90 || // MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA
id == 0x91 || // MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA
id == 0x94 || // MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA
id == 0x95 || // MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA
id == 0xA8 || // MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256
id == 0xA9 || // MBEDTLS_TLS_PSK_WITH_AES_256_GCM_SHA384
id == 0xAA || // MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256
id == 0xAB || // MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384
id == 0xAC || // MBEDTLS_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256
id == 0xAD || // MBEDTLS_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384
id == 0xAE || // MBEDTLS_TLS_PSK_WITH_AES_128_CBC_SHA256
id == 0xAF || // MBEDTLS_TLS_PSK_WITH_AES_256_CBC_SHA384
id == 0xB0 || // MBEDTLS_TLS_PSK_WITH_NULL_SHA256
id == 0xB1 || // MBEDTLS_TLS_PSK_WITH_NULL_SHA384
id == 0xB2 || // MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256
id == 0xB3 || // MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384
id == 0xB4 || // MBEDTLS_TLS_DHE_PSK_WITH_NULL_SHA256
id == 0xB5 || // MBEDTLS_TLS_DHE_PSK_WITH_NULL_SHA384
id == 0xB6 || // MBEDTLS_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256
id == 0xB7 || // MBEDTLS_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384
id == 0xB8 || // MBEDTLS_TLS_RSA_PSK_WITH_NULL_SHA256
id == 0xB9); // MBEDTLS_TLS_RSA_PSK_WITH_NULL_SHA384
}

Copy link
Contributor

@AJMansfield AJMansfield Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a pity the actual mbedtls_ssl_ciphersuite_uses_psk is only part of mbedtls/library/ssl_ciphersuites_internal.h header rather than a public include --- but IMO it's still better to just link against it anyway instead of re-implementing it. Just declare it yourself the same way its header does without defining it:

int mbedtls_ssl_ciphersuite_uses_psk(const mbedtls_ssl_ciphersuite_t *info);

If that's too spicy, add MBEDTLS_ALLOW_PRIVATE_ACCESS to mbedtls_config_common.h and examine the value of info->key_exchange against the four values it can have that indicate PSK suites.

Copy link
Contributor

@AJMansfield AJMansfield Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, don't hardcode the numeric values, use the symbols! You can just id == MBEDTLS_TLS_PSK_WITH_NULL_SHA, you already even have the mbedtls/ssl_ciphersuites.h header that defines those.

Comment on lines +625 to +641
// SSLContext.set_psk_identity(identity) and set_psk_key(key)
// These methods now configure PSK directly with mbedtls instead of storing values
static mp_obj_t psk_identity = mp_const_none;
static mp_obj_t psk_key = mp_const_none;

static mp_obj_t ssl_context_set_psk_identity(mp_obj_t self_in, mp_obj_t identity) {
psk_identity = identity;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(ssl_context_set_psk_identity_obj, ssl_context_set_psk_identity);

static mp_obj_t ssl_context_set_psk_key(mp_obj_t self_in, mp_obj_t key) {
psk_key = key;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(ssl_context_set_psk_key_obj, ssl_context_set_psk_key);

Copy link
Contributor

@AJMansfield AJMansfield Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did ChatGPT write this? Because that's a lie:

These methods now configure PSK directly with mbedtls instead of storing values

Copy link
Contributor

@AJMansfield AJMansfield Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra variables in mp_obj_ssl_context_t was a bit sub-optimal, but using shared static variables like this is completely unacceptable. Even if we ignore mutliple contexts clobbering each other, it's also a use-after-free bug. (Can you spot it? It's because BSS variables aren't visible to micropython's garbage collector unless you give them a root-pointer decorator. The key and identity values here could be eligible for collection the instant these functions return.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point of my earlier comment was this: The mp_obj_ssl_context_t object already has a place to store these values, in its contained mbedtls_ssl_config value --- i.e. the place that calling mbedtls_ssl_conf_psk stores them.
The API here should just be the thinnest possible veneer over that function to make it callable from micropython --- just a function that takes two string arguments, validates that they're strings, extracts the underlying string pointers, and calls mbedtls_ssl_conf_psk with them.

@keenanjohnson
Copy link
Contributor Author

Hey @AJMansfield , I appreciate the detailed feedback, but I'm still working through it all, so I'm not sure that's very useful you providing feedback on my intermediate work. I'll ping you directly and set this PR out of draft to ready once I'm ready for another round of feedback. I just like to push to the branch often to get the CI to run and cross check my local setup.

@AJMansfield
Copy link
Contributor

Hey @AJMansfield , I appreciate the detailed feedback, but I'm still working through it all, so I'm not sure that's very useful you providing feedback on my intermediate work. I'll ping you directly and set this PR out of draft to ready once I'm ready for another round of feedback. I just like to push to the branch often to get the CI to run and cross check my local setup.

Understood! Apologies for the pressure: you're right it does neither of us any good to have me constantly looking over your shoulder, we've all got our own workflows. (I certainly made extensive use of the CI for testing changes myself at one point, lol.) Just be careful with the LLMs --- this is a cryptographic module, after all.

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.

3 participants