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 = "[email protected]"           # set accordingly for EAP-PEAP and EAP-TTLS
username = "[email protected]"         # 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 2 commits June 13, 2025 11:46
@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 <[email protected]>
@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?

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.

5 participants