Skip to content

ports/esp32: added WPA-Enterprise (new) #17234

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 9 commits into
base: master
Choose a base branch
from

Conversation

h-milz
Copy link

@h-milz h-milz commented May 1, 2025

Summary

This PR supersedes #16463 which is FUBAR.

This PR adds WPA2 Enterprise support to the ESP32 port. In particular, the patch supports EAP-PWD, EAP-PEAP, and EAP-TTLS (all supported ttls phase2 methods). Code for EAP-TLS is also included but UNTESTED and EXPERIMENTAL. The patch is a thin wrapper around the ESP-IDF functions and does not implement any further network or security relevant programming. Consequently, it is specific to the ESP32 port.

Testing

The patch was developed and tested in the Technical University of Munich eduroam network on an ESP32_GENERIC_S3 board, namely, a CrowPanel 5.0"-HMI ESP32 Display board with a ESP32-S3-WROOM-1-N4R8 module, as well as a generic ESP32-C6 board from DFRobot, on MPY v1.23.0 using ESP-IDF 5.22 and on MPY v1.25.0 using ESP-IDF 5.4.0.

Usage example:

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

identity = "anonymous@eduroam.mwn.de"           # set accordingly for EAP-PEAP and EAP-TTLS
username = "my_username@eduroam.mwn.de"         # set your username
password = "my_password"                        # set your password
certfile = "/T-TeleSec_GlobalRoot_Class_2.pem"  # needs to be uploaded first
ssid     = "eduroam"
method   = wlan.EAP_method    #   method = { EAP, PEAP, TTLS, TLS }
ttls_phase2_method = 1        #   0 = EAP, 1 = MSCHAPv2 (default), 2 = MSCHAP, 3 = PAP, 4 = CHAP

        
with open (certfile, 'rb') as file:
    ca_cert = file.read()
            
try:
    if method == wlan.EAP_PWD:
        wlan.eap_connect(ssid=ssid, eap_method=method, 
                        username=username, password=password)
    elif method == wlan.EAP_PEAP:
        wlan.eap_connect(ssid=ssid, eap_method=method, 
                        username=username, password=password, 
                        identity=identity, ca_cert=ca_cert)        
    elif method == wlan.EAP_TTLS:  
        wlan.eap_connect(ssid=ssid, eap_method=method, 
                        username=username, password=password, 
                        identity=identity, ca_cert=ca_cert,
                        ttls_phase2_method=ttls_phase2_method)
except Exception as e:
    print (f"error: {e}")

Trade-offs and Alternatives

If your board does not have a hardware RTC, odds are that the server certificate validation for EAP-PEAP, -TTLS and potentially -TLS will fail due to the system time being way off. As a workaround, you can set the system time to build time on system start like this:

import sys
import machine

(year, month, day) = sys.version.split(" on ")[1].split("-")
rtc = machine.RTC()
date_time = (int(year), int(month), int(day), 0, 0, 0, 0, 0)
rtc.init(date_time) 

and from then on, synchronize the internal RTC using NTP in regular intervals.

More documentation is contained in ports/esp32/README.md.

@robert-hh
Copy link
Contributor

Thank you for updating. There are a few minor code formatting complaints. Mostly trailing spaces and wrong indentation. And a wrong formatted commit message. But that is easy to fix.
The documentation about the new methods should go to docs, not esp32/README.md. The latter is dedicated to build information of the port. At the docs directory the documentation could for instance go into docs/library/network.WLAN.rst. There is also esp32/quickref.rst, but that is less suited. Both places do not tell much about the security setting. So it might be a good time to spend a few words about all possible options.

@h-milz
Copy link
Author

h-milz commented May 1, 2025

Thank you for updating. There are a few minor code formatting complaints. Mostly trailing spaces and wrong indentation. And a wrong formatted commit message. But that is easy to fix.
The documentation about the new methods should go to docs, not esp32/README.md. The latter is dedicated to build information of the port. At the docs directory the documentation could for instance go into docs/library/network.WLAN.rst. There is also esp32/quickref.rst, but that is less suited. Both places do not tell much about the security setting. So it might be a good time to spend a few words about all possible options.

No problem, I'll take care of it tomorrow.

@h-milz h-milz force-pushed the ports/esp32/add-wpa2-enterprise branch from 89f82e8 to 7c1623d Compare May 2, 2025 08:43
@h-milz
Copy link
Author

h-milz commented May 2, 2025

@robert-hh

OK The checks now finish successfully and all should be clean. If I should add anything to the documentation just drop me a message.

@Josverl
Copy link
Contributor

Josverl commented May 2, 2025

@h-milz
To understand what you have already tested, and where others could help I have tried to put this in a table.
Is this correct or are there other parts you have already confirmed ? ( I'll update where needed)

Protocol Method Phase2 methods status notes
WPA2-Ent. EAP-PWD - ✅ Tested Password
WPA2-Ent. EAP-PEAP - ✅ Tested EAP in TLS. Usernames and passwords are mandatory
WPA2-Ent. EAP-TTLS 0 EAP ✅ Tested EAP in TLS
WPA2-Ent. EAP-TTLS 1 MSCHAPv2 ✅ Tested MCHAPv2 in TLS
WPA2-Ent. EAP-TTLS 2 MSCHAP ✅ Tested MCHAP in TLS
WPA2-Ent. EAP-TTLS 3 PAP ✅ Tested PAP in TLS
WPA2-Ent. EAP-TTLS 4 CHAP ✅ Tested CHAP in TLS
WPA2-Ent. EAP-TLS - TLS - Client & Server Auth using certs
WPA2-Ent. EAP-FAST X Not implemented in this PR

@h-milz
Copy link
Author

h-milz commented May 2, 2025

@Josverl I forgot to mention that since my first tests in December, my uni apparently implemented EAP-TTLS entirely, so the five methods are all tested and working fine. The only test missing is EAP-TLS which is not supported in our network according to our network admins.

As far as FAST, Espressif does support it in ESP-IDF as documented in https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/network/esp_wifi.html but as far as I'm aware, I have no way of testing it, so I omitted it for now. I think this is a call for other developers to jump in and provide the proper interface. But then, the whole WPA3 enterprise stuff is still missing as well. Basically, WPA3 is supported in eduroam but I don't know if my uni implements it. Let me check, and if yes, I may be able to provide some working code as well.

@robert-hh
Copy link
Contributor

Yes, please add a documentation.
Looking through the code it does not seam reasonable to add a new method eap_connect(). It should all be handled within connect() and diverge depending on an authentication type argument, which defaults to the existing mode. But Damien may have a different opinion.

@h-milz
Copy link
Author

h-milz commented May 2, 2025

Yes, please add a documentation.
Looking through the code it does not seam reasonable to add a new method eap_connect(). It should all be handled within connect() and diverge depending on an authentication type argument, which defaults to the existing mode. But Damien may have a different opinion.

I understand. Thing is, I intended to be as little intrusive as possible. Integrating it in connect() would probably require adding another switch, or I can just use eap_method and branch accordingly. But we would have to pack the whole arg parsing into connect() ...

@Josverl
Copy link
Contributor

Josverl commented May 2, 2025

... the five methods are all tested and working fine. The only test missing is EAP-TLS which is not supported in our network according to our network admins.

Thanks - Updated the table accordingly

But then, the whole WPA3 enterprise stuff is still missing as well.

Perhaps that can be a follow-up PR, having this merged in would be a step forward.
Lets keep it in mind in the API design though

@Josverl
Copy link
Contributor

Josverl commented May 2, 2025

Yes, please add a documentation.
Looking through the code it does not seam reasonable to add a new method eap_connect(). It should all be handled within connect() and diverge depending on an authentication type argument, which defaults to the existing mode. But Damien may have a different opinion.

I understand. Thing is, I intended to be as little intrusive as possible. Integrating it in connect() would probably require adding another switch, or I can just use eap_method and branch accordingly. But we would have to pack the whole arg parsing into connect() ...

About integrating this, from a Python perspective I think this can be modelled as an overload
The interface could be something like :

MicroPython interface `WLAN.connect()`

class WLAN: 
   # not sure if these should be classvars as that takes some space , or just documented 
    AUTH_EAP_PWD = 0
    AUTH_EAP_PEAP = 1
    AUTH_EAP_TTLS = 2
    AUTH_EAP_TLS = 3

    TTLS_PWD = 0 
    TTLS_MSCHAPV2= 1 
    TTLS_MSCHAP = 2 
    TTLS_PAP = 3 
    TTLS_CHAP  = 4 

    @overload
    def connect(
        self,
        ssid: str | None = None,
        password: str | None = None,
        /,
        *,
        bssid: bytes | None = None,
    ) -> None:
        """
        Connect to the specified wireless network, 
        ...
        """
        ...
    @overload
    def connect(
        self,
        ssid: str | None = None,
        /,
        password: str | None = None,
        *,
        bssid: bytes | None = None, 
        eap_method : int  = AUTH_EAP_PWD , # what is a sensible default 
        username: str | None = None,
        identity: str | None = None,
        ca_cert: bytes | None = None,  # or server_cert ? 
        ttls_method: int = TTLS_MSCHAPV2,
        client_cert: bytes | None = None, 
        private_key: bytes | None = None,
        private_key_passphrase: str | None = None,
        disable_time_check: bool = False, 
    ) -> None:
        """
        Connect to the specified wireless network using WPA2

        """
        ...

( Actually it could be a Protocol as the same protocols can be used for wired network access as well. but I think that is of less concern now)

@h-milz
Copy link
Author

h-milz commented May 3, 2025

So - what should we do? Leave it as-is and leave it to the user to use a wrapper on top, or go the whole nine yards and modify network_wlan_connect() (of which I pretty much copied the whole idea and structure as you can tell)? From the amount of code there is, this would rather mean to absorb connect() into eap_connect() and rename it ;-)

From a timeline perspective, I'd vote for leaving it as it is right now, as an intermediary step so to speak. Let people use the code, find potential issues and pitfalls, identify things to add like FAST and WPA3, and plan for a unified and extended solution for the next release. I must also say I have a pretty busy semester ahead of me, and potentially little spare time until early August.

@Josverl
Copy link
Contributor

Josverl commented May 3, 2025

In the earlier conversation @dpgeorge commented:

Thanks for the contribution, this is a very nice feature to add.

And yes, it is port specific and will not help the STM32 or RP2 guys

Right, so this is the main thing to discuss and get right first: how this API will generalise to other WLAN drivers.

Should it be a new method like eap_connect(), or should the existing connect() method be reused and new security arguments/options added to it? The latter seems the more obvious path to me.

@robert-hh
Copy link
Contributor

From a timeline perspective, I'd vote for leaving it as it is right now, as an intermediary step so to speak.
It would be confusing to publish an API which is known to be changed later. Breaking changes are bad. Since the official review has not yet started, there is plenty of time to make a change. It should require only some re-ordering of the code.
You could keep the network_wlan_eap_connect() function and call that from the network_wlan_connect() function with slightly modified arguments. You can use a common file-scope list of parameters which can be used by both network_wlan_eap_connect() and network_wlan_connect(). The formal args parse step would be in network_wlan_connect(). It needs an additional argument for the authentication method, and once it is one of the EAP methods, it would call network_wlan_eap_connect() with a pointer to args and n_args as arguments.
And obviously the line that defines network_wlan_eap_connect() as wlan method has to go.

@h-milz
Copy link
Author

h-milz commented Jun 10, 2025

Hi guys, I finally managed to find some time to merge wlan_connect() and eap_connect() as discussed, and I also enable WPA3. Sadly, after extending the eap_connect() function, I get an compiler error which leaves me wondering. I'm attaching the local diff against the last successfully compiling version and the new one, as well as the relevant part of the idf.py build log. ATM I'm stuck.

Does it have to do with the number of args? I can't see how and where MP_DEFINE_CONST_OBJ_TYPE_NARGS_2 would be used in this case, and why it would complain.

network_wlan.c.errs.txt
network_wlan.c.diff.txt

Thanks for any insight.

@mrcreatd
Copy link

Sadly, after extending the eap_connect() function, I get an compiler error which leaves me wondering. I'm attaching the local diff against the last successfully compiling version and the new one, as well as the relevant part of the idf.py build log. ATM I'm stuck.

G'day! Take a look at this diff when you get a chance. I haven’t tested Wi-Fi. But it builds fine for ESP32-S3, flashed without issues, and all added attributes are in place.
network_wlan.c.diff.txt

>>> import network
>>> wlan = network.WLAN(network.WLAN.IF_STA)
>>> wlan.active(True)
True
>>> dir(wlan)
['__class__', 'EAP_NONE', 'EAP_PEAP', 'EAP_PWD', 'EAP_TLS', 'EAP_TTLS', 'EAP_TTLS_PHASE2_CHAP', 'EAP_TTLS_PHASE2_EAP', 'EAP_TTLS_PHASE2_MSCHAP', 'EAP_TTLS_PHASE2_MSCHAPV2', 'EAP_TTLS_PHASE2_PAP', 'IF_AP', 'IF_STA', 'PM_NONE', 'PM_PERFORMANCE', 'PM_POWERSAVE', 'SEC_DPP', 'SEC_OPEN', 'SEC_OWE', 'SEC_WAPI', 'SEC_WEP', 'SEC_WPA', 'SEC_WPA2', 'SEC_WPA2_ENT', 'SEC_WPA2_WPA3', 'SEC_WPA2_WPA3_ENT', 'SEC_WPA3', 'SEC_WPA3_ENT', 'SEC_WPA3_ENT_192', 'SEC_WPA3_EXT_PSK', 'SEC_WPA3_EXT_PSK_MIXED_MODE', 'SEC_WPA_WPA2', 'active', 'config', 'connect', 'disconnect', 'eap_connect', 'ifconfig', 'ipconfig', 'isconnected', 'scan', 'status']


@h-milz
Copy link
Author

h-milz commented Jun 11, 2025

@mrcreatd thank you for the heads up but your diff lacks my latest changes, merging wifi_connect() and eap_connect() as well as a few wpa3 extensions. My earlier version compiles and works fine as well but my question is what in my latest diff causes the problem. Maybe I don't see the forest for the trees.

@robert-hh @Josverl sorry to bother you but can you please look at the issue? Thank you !

@robert-hh
Copy link
Contributor

I doubt that I can help. I have no access to a WPA-Enterprise net.

@h-milz
Copy link
Author

h-milz commented Jun 11, 2025

OK, the reason was a missing closing brace m(

I have everything up and running and thoroughly tested as listed in Jos' list above, and all the new stuff is merged into connect(). I'll do some cleanups by tomorrow EOB, and if there's any documentation to add just ping me.

Other than that, I noticed that this extension dropped from the 1.26 merge list because the old PR was closed. Can you add this one then?

@Josverl
Copy link
Contributor

Josverl commented Jun 11, 2025

extension dropped from the 1.26 merge list because the old PR was closed

@dpgeorge , @projectgus , can you please re-add this PR to the 1.26 milestone if that is still feasible .

@mrcreatd
Copy link

@h-milz I based it on the master branch and just tried to get the PR and your patch compiling.
You're right — I should've sent only the diff on top of your patch.
Glad you found the issue already.

I'm happy to help with testing this PR going forward if needed.
That said, for my projects, I prefer to keep esp_eap_client logic in a separate wrapper module, rather than embedding it directly into the network_wlan code.

@h-milz h-milz force-pushed the ports/esp32/add-wpa2-enterprise branch 2 times, most recently from 75a4a34 to e79bff1 Compare June 12, 2025 14:29
h-milz added 6 commits June 13, 2025 11:46
Signed-off-by: Harald Milz <hm@seneca.muc.de>
Signed-off-by: Harald Milz <hm@seneca.muc.de>
Signed-off-by: Harald Milz <hm@seneca.muc.de>
Signed-off-by: Harald Milz <hm@seneca.muc.de>
Signed-off-by: Harald Milz <hm@seneca.muc.de>
Signed-off-by: Harald Milz <hm@seneca.muc.de>
h-milz added 2 commits June 13, 2025 11:46
Signed-off-by: Harald Milz <hm@seneca.muc.de>
Signed-off-by: Harald Milz <hm@seneca.muc.de>
@h-milz h-milz force-pushed the ports/esp32/add-wpa2-enterprise branch from e79bff1 to 085a8ab Compare June 13, 2025 09:47
Signed-off-by: Harald Milz <hm@seneca.muc.de>
@h-milz
Copy link
Author

h-milz commented Jun 13, 2025

OK, that seems to work now. I had to reword some older commits that still had "ports/esp32" in the commit messages which made the old code pop up again.

@h-milz
Copy link
Author

h-milz commented Jun 25, 2025

@Josverl, @dpgeorge, @projectgus is there anything missing to add this to the 1.26 merge list?

@projectgus projectgus self-requested a review July 15, 2025 01:56
Copy link
Contributor

@projectgus projectgus left a comment

Choose a reason for hiding this comment

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

This feature looks potentially pretty useful to me, and I appreciate your tenacity keeping the PR up to date.

I am curious about the binary size impact? In particular, we have a D2WD variant of ESP32_GENERIC that has a number of features disabled to save code size and particularly IRAM, and I wonder whether this feature might need to support being disabled for this use case.

I'm also going to copy-paste @dpgeorge's comment from the old PR, as I'm not sure about at least one of these points (apologies if I missed a reply on the old PR):

Thanks for the contribution, this is a very nice feature to add.

And yes, it is port specific and will not help the STM32 or RP2 guys

Right, so this is the main thing to discuss and get right first: how this API will generalise to other WLAN drivers.

Should it be a new method like eap_connect(), or should the existing connect() method be reused and new security arguments/options added to it? The latter seems the more obvious path to me.

Then, it would be great to try implementing this on at least one other WLAN driver, to see how general it can be.

Regarding PR management, there are commits here that will need to be squashed before merging. We can help with that, however if you're keen then jimmo has a guide here which may be useful. If you find yourself with a fubar PR branch again then please don't make a third PR, instead you can ping me and we'll work out how to restore it.

@@ -190,6 +190,7 @@ list(APPEND IDF_COMPONENTS
ulp
usb
vfs
wpa_supplicant
Copy link
Contributor

Choose a reason for hiding this comment

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

Stray tab

@@ -147,6 +217,8 @@ Methods
pm WiFi Power Management setting (see below for allowed values)
============= ===========



Copy link
Contributor

Choose a reason for hiding this comment

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

Stray whitespace?

in this case). *wpa3* enforces WPA3 authentication and will reject the
network if WPA3 is not supported.

WPA Enterprise (ESP32 port only)
Copy link
Contributor

Choose a reason for hiding this comment

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

As this is a large amount of specific documentation I suggest putting it under its own section at the bottom of this doc and linking to that section from here (i.e. "ESP32 port supports additional arguments for WPA Enterprise support, see (link)").

mp_raise_ValueError(MP_ERROR_TEXT("unknown config value for eap_method"));
}

// this is mandatory and should not be None
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is true, at least for non-Enterprise networks you can set bssid instead...?


Connect to the specified wireless network, using the specified key.
If *bssid* is given then the connection will be restricted to the
access-point with that MAC address (the *ssid* must also be specified
in this case).
in this case). *wpa3* enforces WPA3 authentication and will reject the
network if WPA3 is not supported.
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to make this more general, i.e. "require minimum security level" to future-proof it, as eventually something will replace WPA3 authentication. A feature to require a minimum auth level on the AP is a useful feature by itself (and could be generalised to other ports).

What about having an argument like min_sec=None that can be set to one of the existing SEC_ constants?

@h-milz
Copy link
Author

h-milz commented Jul 22, 2025

@projectgus Thank you for your feedback, and I will definitely look into it. Albeit not before the 1.26 merge / release date because I will have to write two uni exams next week and need some prep time.

As for some of the comments:

Should it be a new method like eap_connect(), or should the existing connect() method be reused and new security arguments/options added to it? The latter seems the more obvious path to me.

That's what I did, merge my original eap_connect() into connect(). I would have had to add all the new options to connect() anyway in order to then invoke eap_connect(), so this seemed more straightforward.

Then, it would be great to try implementing this on at least one other WLAN driver, to see how general it can be.

Not at all, because it is a wrapper around methods in ESP-IDF. Someone else would have to look into this one at a later date, because I have no other Wifi capable / MPY supported MCU at hand. My take is, let's start with this one and other MCUs can be added later if someone volunteers to do so.

As this is a large amount of specific documentation I suggest putting it under its own section at the bottom of this doc and linking to that section from here (i.e. "ESP32 port supports additional arguments for WPA Enterprise support, see (link)").

No problem, but again, not before release date.

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

Successfully merging this pull request may close these issues.

6 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy