diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index ee0ef490fda8..bf42cb952a96 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -32,12 +32,82 @@ Methods argument is passed. Otherwise, query current state if no argument is provided. Most other methods require active interface. -.. method:: WLAN.connect(ssid=None, key=None, *, bssid=None) +.. method:: WLAN.connect(ssid=None, key=None, *, bssid=None, wpa3=False, param=value, ...) 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. + + WPA Enterprise (ESP32 port only) + + * eap_method -- EAP method to use (string) + + Connect to the specified wireless network, using WPA-Enterprise authentication and + the specified parameters. The EAP methods provided are EAP-PWD, EAP-PEAP, + EAP-TTLS, and EAP-TLS. EAP-TLS is UNTESTED and thus EXPERIMENTAL. + + Common parameters: + + * ssid -- WiFi access point name, (string, e.g. "eduroam") + + EAP-PWD parameters + + * username -- your network username (string) + * password -- your network password (string) + + EAP-PEAP parameters: + + * username -- your network username (string) + * password -- your network password (string) + * identity -- anonymous identity (string) + * ca_cert -- the CA certificate (filename, string) + + EAP-TTLS parameters: + + * username -- your network username (string) + * password -- your network password (string) + * identity -- anonymous identity (string) + * ca_cert -- the CA certificate (filename, string) + * ttls_phase2_method -- TTLS Phase 2 method (integer) + + EAP-TTLS supports the following TTLS Phase 2 methods: + + * 0 -- PWD + * 1 -- MSCHAPv2 (default) + * 2 -- MSCHAP + * 3 -- PAP + * 4 -- CHAP + + Please note that MSCHAPv2 and CHAP have known security issues and should be avoided. + + EAP-TLS parameters: + + * client_cert -- client certificate filename (string) + * private_key -- private key filename (string) + * private_key_password -- private key password (string, optional) + * disable_time_check -- suppress the validity check for the local client certificate when using EAP-TLS (boolean, default False) + + disable_time_check is only included for the sake of completeness. In practice, + you want to renew the client certificate before expiry. + + Certificate files need to be uploaded first, e.g.:: + + mpremote cp : + + EAP-PWD should be used whenever possible. It connects swiftly and uses the least resources. + When using one of the other methods, make sure the system time is correct to prevent + certificate validation errors. Best practice is to use a battery buffered RTC and to set + the system time using NTP regularly. A temporary workaround if no battery buffered RTC is + available is to set the system time to the image build time, like: + + 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) .. method:: WLAN.disconnect() @@ -147,6 +217,8 @@ Methods pm WiFi Power Management setting (see below for allowed values) ============= =========== + + Constants --------- diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index dedef6d782ca..592056c69a0d 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -190,6 +190,7 @@ list(APPEND IDF_COMPONENTS ulp usb vfs + wpa_supplicant ) # Provide the default LD fragment if not set diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index e85d1328fdc2..77a77d52fb90 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -40,6 +40,7 @@ #include "modnetwork.h" #include "esp_wifi.h" +#include "esp_eap_client.h" #include "esp_log.h" #include "esp_psram.h" @@ -298,12 +299,35 @@ static mp_obj_t network_wlan_active(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_active_obj, 1, 2, network_wlan_active); +// WLAN connect including WPA2/WPA3 Enterprise Connect +// there are no official constants in ESP-IDF for this so far +#define WIFI_AUTH_EAP_NONE 0 +#define WIFI_AUTH_EAP_PWD 1 +#define WIFI_AUTH_EAP_PEAP 2 +#define WIFI_AUTH_EAP_TTLS 3 +#define WIFI_AUTH_EAP_TLS 4 + static mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_ssid, ARG_key, ARG_bssid }; + enum { ARG_ssid, ARG_key, ARG_bssid, + ARG_eap_method, ARG_username, ARG_password, + ARG_identity, ARG_ca_cert, ARG_ttls_phase2_method, + ARG_client_cert, ARG_private_key, ARG_private_key_password, ARG_disable_time_check, + ARG_wpa3 }; static const mp_arg_t allowed_args[] = { { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_eap_method, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = WIFI_AUTH_EAP_NONE} }, // default PSK + { MP_QSTR_username, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_password, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_identity, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_ca_cert, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_ttls_phase2_method, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = ESP_EAP_TTLS_PHASE2_MSCHAPV2} }, // default MSCHAPV2 + { MP_QSTR_client_cert, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_private_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_private_key_password, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_disable_time_check, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_wpa3, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} } // if true, enforces WPA3-SAE or WPA3 Enterprise }; // parse args @@ -311,32 +335,161 @@ static mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); wifi_config_t wifi_sta_config = {0}; + int16_t eap_method = (int16_t)args[ARG_eap_method].u_int; + size_t len; + const char *p; - // configure any parameters that are given - if (n_args > 1) { - size_t len; - const char *p; - if (args[ARG_ssid].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); - memcpy(wifi_sta_config.sta.ssid, p, MIN(len, sizeof(wifi_sta_config.sta.ssid))); - } - if (args[ARG_key].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_key].u_obj, &len); - memcpy(wifi_sta_config.sta.password, p, MIN(len, sizeof(wifi_sta_config.sta.password))); - } - if (args[ARG_bssid].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); - if (len != sizeof(wifi_sta_config.sta.bssid)) { - mp_raise_ValueError(NULL); + // parameter check + if (eap_method < WIFI_AUTH_EAP_NONE || eap_method > WIFI_AUTH_EAP_TLS) { + mp_raise_ValueError(MP_ERROR_TEXT("unknown config value for eap_method")); + } + + // this is mandatory and should not be None + if (args[ARG_ssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); + memcpy(wifi_sta_config.sta.ssid, p, MIN(len, sizeof(wifi_sta_config.sta.ssid))); + } + + if (args[ARG_key].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_key].u_obj, &len); + memcpy(wifi_sta_config.sta.password, p, MIN(len, sizeof(wifi_sta_config.sta.password))); + } + + if (args[ARG_bssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); + if (len != sizeof(wifi_sta_config.sta.bssid)) { + mp_raise_ValueError(NULL); + } + wifi_sta_config.sta.bssid_set = 1; + memcpy(wifi_sta_config.sta.bssid, p, sizeof(wifi_sta_config.sta.bssid)); + } + + if (eap_method == WIFI_AUTH_EAP_NONE) { + // WPA2 or WPA3 PSK + if (args[ARG_wpa3].u_bool == true) { + // this will only affect WPA3-personal, not enterprise + wifi_sta_config.sta.threshold.authmode = WIFI_AUTH_WPA3_PSK; + } + } else { + // WPA2/3 Enterprise + // At the moment (using ESP-IDF 5.4), setting WIFI_AUTH_WPA3_ENTERPRISE in eduroam networks will + // actually default to WPA2 because suite B compatibility is not implemented + // The reason is outlined here: https://eduroam.org/eduroam-and-wpa3/ + if (args[ARG_wpa3].u_bool == true) { + wifi_sta_config.sta.threshold.authmode = WIFI_AUTH_WPA3_ENTERPRISE; + } else { + wifi_sta_config.sta.threshold.authmode = WIFI_AUTH_WPA2_WPA3_ENTERPRISE; + } + } + + esp_exceptions(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config)); + + if (eap_method == WIFI_AUTH_EAP_PWD || eap_method == WIFI_AUTH_EAP_PEAP || eap_method == WIFI_AUTH_EAP_TTLS) { + // use username and password + if (args[ARG_username].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_username].u_obj, &len); + esp_exceptions(esp_eap_client_set_username((const unsigned char *)p, len)); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("missing config param username")); + } + if (args[ARG_password].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_password].u_obj, &len); + esp_exceptions(esp_eap_client_set_password((const unsigned char *)p, len)); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("missing config param password")); + } + } + + if (eap_method == WIFI_AUTH_EAP_PEAP || eap_method == WIFI_AUTH_EAP_TTLS) { + // additionally use identity and ca_cert + if (args[ARG_identity].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_identity].u_obj, &len); + esp_exceptions(esp_eap_client_set_identity((const unsigned char *)p, len)); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("missing config param identity")); + } + if (args[ARG_ca_cert].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_ca_cert].u_obj, &len); + // see comment (1) below. + esp_exceptions(esp_eap_client_set_ca_cert((const unsigned char *)p, len + 1)); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("missing config param ca_cert")); + } + } + + if (eap_method == WIFI_AUTH_EAP_TTLS) { + // additionally use ttls_phase2_method, defaulting to MSCHAPV2 (and then very similar to EAP-PEAP) + // tested and verified with all 5 supported phase 2 methods + if (args[ARG_ttls_phase2_method].u_obj != mp_const_none) { + int16_t ttls_phase2_method = (int16_t)args[ARG_ttls_phase2_method].u_int; + if (ttls_phase2_method < ESP_EAP_TTLS_PHASE2_EAP || ttls_phase2_method > ESP_EAP_TTLS_PHASE2_CHAP) { + mp_raise_ValueError(MP_ERROR_TEXT("unknown config value for ttls_phase2_method")); } - wifi_sta_config.sta.bssid_set = 1; - memcpy(wifi_sta_config.sta.bssid, p, sizeof(wifi_sta_config.sta.bssid)); + mp_printf(&mp_plat_print, "ttls_phase2_method: \"%d\"\n", ttls_phase2_method); + esp_exceptions(esp_eap_client_set_ttls_phase2_method(ttls_phase2_method)); + } + } + + if (eap_method == WIFI_AUTH_EAP_TLS) { + // UNTESTED! + // EAP-TLS uses client cert, private key, and (optionally) private key password, and (optionally) ca_cert + // please note that this release does not implement WIFI_AUTH_WPA3_ENT_192 + // The reason is outlined here: https://eduroam.org/eduroam-and-wpa3/ + size_t client_cert_len = 0; + const char *client_cert = NULL; + size_t private_key_len = 0; + const char *private_key = NULL; + size_t private_key_password_len = 0; + const char *private_key_password = NULL; + + mp_printf(&mp_plat_print, "\nPlease note that EAP-TLS is so far UNTESTED and thus EXPERIMENTAL.\nUse at your own risk!\n\n"); + if (args[ARG_client_cert].u_obj != mp_const_none) { + client_cert = mp_obj_str_get_data(args[ARG_client_cert].u_obj, &client_cert_len); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("missing config param client_cert")); + } + if (args[ARG_private_key].u_obj != mp_const_none) { + private_key = mp_obj_str_get_data(args[ARG_private_key].u_obj, &private_key_len); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("missing config param private_key")); + } + if (args[ARG_private_key_password].u_obj != mp_const_none) { // password is optional + private_key_password = mp_obj_str_get_data(args[ARG_private_key_password].u_obj, &private_key_password_len); + } + // disable time check or not + if (args[ARG_disable_time_check].u_bool == true) { + esp_exceptions(esp_eap_client_set_disable_time_check(true)); + mp_printf(&mp_plat_print, "disable_time_check: true\n"); + } + + // (1) the documentation for esp_eap_client_set_certificate_and_key() says, + // "2. The client_cert, private_key, and private_key_password should be zero-terminated." + // so we copy 1 byte more to include the null + // in the esp-idf wifi_enterprise example, the null is appended when converting the cert files to byte arrays + esp_exceptions(esp_eap_client_set_certificate_and_key( + (const unsigned char *)client_cert, client_cert_len + 1, + (const unsigned char *)private_key, private_key_len + 1, + (const unsigned char *)private_key_password, private_key_password_len + 1) + ); + // according to the esp-idf wifi_enterprise example, the ca_cert is optional for EAP-TLS + if (args[ARG_ca_cert].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_ca_cert].u_obj, &len); + esp_exceptions(esp_eap_client_set_ca_cert((const unsigned char *)p, len + 1)); } - esp_exceptions(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config)); } esp_exceptions(esp_netif_set_hostname(wlan_sta_obj.netif, mod_network_hostname_data)); + // eap_enable if requested & start + if (eap_method == WIFI_AUTH_EAP_NONE) { + esp_exceptions(esp_wifi_sta_enterprise_disable()); + } else { + esp_exceptions(esp_wifi_sta_enterprise_enable()); + } + if (!wifi_started) { + esp_exceptions(esp_wifi_start()); + } + wifi_sta_reconnects = 0; // connect to the WiFi AP MP_THREAD_GIL_EXIT(); @@ -348,9 +501,11 @@ static mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp } static MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_connect_obj, 1, network_wlan_connect); + static mp_obj_t network_wlan_disconnect(mp_obj_t self_in) { wifi_sta_connect_requested = false; esp_exceptions(esp_wifi_disconnect()); + esp_exceptions(esp_wifi_sta_enterprise_disable()); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_disconnect_obj, network_wlan_disconnect); @@ -768,6 +923,18 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_ENTERPRISE) }, #endif + { MP_ROM_QSTR(MP_QSTR_EAP_NONE), MP_ROM_INT(WIFI_AUTH_EAP_NONE) }, + { MP_ROM_QSTR(MP_QSTR_EAP_PWD), MP_ROM_INT(WIFI_AUTH_EAP_PWD) }, + { MP_ROM_QSTR(MP_QSTR_EAP_PEAP), MP_ROM_INT(WIFI_AUTH_EAP_PEAP) }, + { MP_ROM_QSTR(MP_QSTR_EAP_TTLS), MP_ROM_INT(WIFI_AUTH_EAP_TTLS) }, + { MP_ROM_QSTR(MP_QSTR_EAP_TLS), MP_ROM_INT(WIFI_AUTH_EAP_TLS) }, + + { MP_ROM_QSTR(MP_QSTR_EAP_TTLS_PHASE2_EAP), MP_ROM_INT(ESP_EAP_TTLS_PHASE2_EAP) }, + { MP_ROM_QSTR(MP_QSTR_EAP_TTLS_PHASE2_MSCHAPV2), MP_ROM_INT(ESP_EAP_TTLS_PHASE2_MSCHAPV2) }, + { MP_ROM_QSTR(MP_QSTR_EAP_TTLS_PHASE2_MSCHAP), MP_ROM_INT(ESP_EAP_TTLS_PHASE2_MSCHAP) }, + { MP_ROM_QSTR(MP_QSTR_EAP_TTLS_PHASE2_PAP), MP_ROM_INT(ESP_EAP_TTLS_PHASE2_PAP) }, + { MP_ROM_QSTR(MP_QSTR_EAP_TTLS_PHASE2_CHAP), MP_ROM_INT(ESP_EAP_TTLS_PHASE2_CHAP) }, + { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(WIFI_PS_MAX_MODEM) },