Add support for WPA-Enterprise (#5194)
* Squashed commit of the following: commit 70fe1fc76d3d88947d4c9f8b43d58ea90f944230 Author: Benjamin Kraus <ben@benkraus.com> Date: Fri Oct 31 20:52:13 2025 -0400 Added support for enterprise WiFi. * Updated based on feedback from CodeRabbit. * Fixed issue with strncmp identified by CodeRabbit. * Replaced split declaration-then-assignment with a single statement. * Revert whitespace only changes. * Move WPA enterprise behind a feature flag.
This commit is contained in:
@@ -107,6 +107,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
|||||||
multiWiFi[n].staticIP = nIP;
|
multiWiFi[n].staticIP = nIP;
|
||||||
multiWiFi[n].staticGW = nGW;
|
multiWiFi[n].staticGW = nGW;
|
||||||
multiWiFi[n].staticSN = nSN;
|
multiWiFi[n].staticSN = nSN;
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
byte encType = WIFI_ENCRYPTION_TYPE_PSK;
|
||||||
|
char anonIdent[65] = "";
|
||||||
|
char ident[65] = "";
|
||||||
|
CJSON(encType, wifi[F("enc_type")]);
|
||||||
|
getStringFromJson(anonIdent, wifi["e_anon_ident"], 65);
|
||||||
|
getStringFromJson(ident, wifi["e_ident"], 65);
|
||||||
|
multiWiFi[n].encryptionType = encType;
|
||||||
|
strlcpy(multiWiFi[n].enterpriseAnonIdentity, anonIdent, 65);
|
||||||
|
strlcpy(multiWiFi[n].enterpriseIdentity, ident, 65);
|
||||||
|
#endif
|
||||||
if (++n >= WLED_MAX_WIFI_COUNT) break;
|
if (++n >= WLED_MAX_WIFI_COUNT) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -866,6 +877,13 @@ void serializeConfig(JsonObject root) {
|
|||||||
wifi_gw.add(multiWiFi[n].staticGW[i]);
|
wifi_gw.add(multiWiFi[n].staticGW[i]);
|
||||||
wifi_sn.add(multiWiFi[n].staticSN[i]);
|
wifi_sn.add(multiWiFi[n].staticSN[i]);
|
||||||
}
|
}
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
wifi[F("enc_type")] = multiWiFi[n].encryptionType;
|
||||||
|
if (multiWiFi[n].encryptionType == WIFI_ENCRYPTION_TYPE_ENTERPRISE) {
|
||||||
|
wifi[F("e_anon_ident")] = multiWiFi[n].enterpriseAnonIdentity;
|
||||||
|
wifi[F("e_ident")] = multiWiFi[n].enterpriseIdentity;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray dns = nw.createNestedArray(F("dns"));
|
JsonArray dns = nw.createNestedArray(F("dns"));
|
||||||
|
|||||||
@@ -211,6 +211,12 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
|||||||
#define USERMOD_ID_BRIGHTNESS_FOLLOW_SUN 57 //Usermod "usermod_v2_brightness_follow_sun.h"
|
#define USERMOD_ID_BRIGHTNESS_FOLLOW_SUN 57 //Usermod "usermod_v2_brightness_follow_sun.h"
|
||||||
#define USERMOD_ID_USER_FX 58 //Usermod "user_fx"
|
#define USERMOD_ID_USER_FX 58 //Usermod "user_fx"
|
||||||
|
|
||||||
|
//Wifi encryption type
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
#define WIFI_ENCRYPTION_TYPE_PSK 0 //None/WPA/WPA2
|
||||||
|
#define WIFI_ENCRYPTION_TYPE_ENTERPRISE 1 //WPA/WPA2-Enterprise
|
||||||
|
#endif
|
||||||
|
|
||||||
//Access point behavior
|
//Access point behavior
|
||||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||||
#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost)
|
#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost)
|
||||||
|
|||||||
@@ -118,11 +118,24 @@
|
|||||||
gId("wifi_add").style.display = (i<maxNetworks) ? "inline":"none";
|
gId("wifi_add").style.display = (i<maxNetworks) ? "inline":"none";
|
||||||
gId("wifi_rem").style.display = (i>1) ? "inline":"none";
|
gId("wifi_rem").style.display = (i>1) ? "inline":"none";
|
||||||
}
|
}
|
||||||
function addWiFi(ssid="",pass="",bssid="",ip=0,gw=0,sn=0x00ffffff) { // little endian
|
function addWiFi(ssid="",pass="",bssid="",ip=0,gw=0,sn=0x00ffffff,type=-1,anon="",ident="") { // little endian
|
||||||
var i = gId("wifi_entries").childNodes.length;
|
var i = gId("wifi_entries").childNodes.length;
|
||||||
if (i >= maxNetworks) return;
|
if (i >= maxNetworks) return;
|
||||||
|
var encryptionTypeField = "";
|
||||||
|
if (type >=0 && type < 2) {
|
||||||
|
encryptionTypeField = `WiFi encryption type:<br>
|
||||||
|
<select id="ET${i}" name="ET${i}" onchange="E(${i})">
|
||||||
|
<option value="0"${(type==0) ? ' selected':''}>None/WPA/WPA2</option>
|
||||||
|
<option value="1"${(type==1) ? ' selected':''}>WPA/WPA2-Enterprise</option>
|
||||||
|
</select><br>
|
||||||
|
<div id="IDS${i}" style="${(type==0) ? 'display:none;':''}">
|
||||||
|
Anonymous identity:<br><input type="text" id="EA${i}" name="EA${i}" maxlength="64" value="${anon}"><br>
|
||||||
|
Identity:<br><input type="text" id="EI${i}" name="EI${i}" maxlength="64" value="${ident}"><br>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
var b = `<div id="net${i}"><hr class="sml">
|
var b = `<div id="net${i}"><hr class="sml">
|
||||||
Network name (SSID${i==0?", empty to not connect":""}):<br><input type="text" id="CS${i}" name="CS${i}" maxlength="32" value="${ssid}" ${i>0?"required":""}><br>
|
Network name (SSID${i==0?", empty to not connect":""}):<br><input type="text" id="CS${i}" name="CS${i}" maxlength="32" value="${ssid}" ${i>0?"required":""}><br>
|
||||||
|
${encryptionTypeField}
|
||||||
Network password:<br><input type="password" name="PW${i}" maxlength="64" value="${pass}"><br>
|
Network password:<br><input type="password" name="PW${i}" maxlength="64" value="${pass}"><br>
|
||||||
BSSID (optional):<br><input type="text" id="BS${i}" name="BS${i}" maxlength="12" value="${bssid}"><br>
|
BSSID (optional):<br><input type="text" id="BS${i}" name="BS${i}" maxlength="12" value="${bssid}"><br>
|
||||||
Static IP (leave at 0.0.0.0 for DHCP)${i==0?"<br>Also used by Ethernet":""}:<br>
|
Static IP (leave at 0.0.0.0 for DHCP)${i==0?"<br>Also used by Ethernet":""}:<br>
|
||||||
@@ -185,6 +198,14 @@ Static subnet mask:<br>
|
|||||||
rC++;
|
rC++;
|
||||||
gId('+').style.display = gId("rml").childElementCount < 10 ? 'inline' : 'none'; // can't append to list anymore, hide button
|
gId('+').style.display = gId("rml").childElementCount < 10 ? 'inline' : 'none'; // can't append to list anymore, hide button
|
||||||
}
|
}
|
||||||
|
function E(i) {
|
||||||
|
const select = gId("ET"+i);
|
||||||
|
if (select.value === "0") {
|
||||||
|
gId("IDS"+i).style.display = "none";
|
||||||
|
} else {
|
||||||
|
gId("IDS"+i).style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -64,13 +64,26 @@ typedef struct WiFiConfig {
|
|||||||
IPAddress staticIP;
|
IPAddress staticIP;
|
||||||
IPAddress staticGW;
|
IPAddress staticGW;
|
||||||
IPAddress staticSN;
|
IPAddress staticSN;
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
byte encryptionType;
|
||||||
|
char enterpriseAnonIdentity[65];
|
||||||
|
char enterpriseIdentity[65];
|
||||||
|
WiFiConfig(const char *ssid="", const char *pass="", uint32_t ip=0, uint32_t gw=0, uint32_t subnet=0x00FFFFFF // little endian
|
||||||
|
, byte enc_type=WIFI_ENCRYPTION_TYPE_PSK, const char *ent_anon="", const char *ent_iden="")
|
||||||
|
#else
|
||||||
WiFiConfig(const char *ssid="", const char *pass="", uint32_t ip=0, uint32_t gw=0, uint32_t subnet=0x00FFFFFF) // little endian
|
WiFiConfig(const char *ssid="", const char *pass="", uint32_t ip=0, uint32_t gw=0, uint32_t subnet=0x00FFFFFF) // little endian
|
||||||
|
#endif
|
||||||
: staticIP(ip)
|
: staticIP(ip)
|
||||||
, staticGW(gw)
|
, staticGW(gw)
|
||||||
, staticSN(subnet)
|
, staticSN(subnet)
|
||||||
{
|
{
|
||||||
strncpy(clientSSID, ssid, 32); clientSSID[32] = 0;
|
strncpy(clientSSID, ssid, 32); clientSSID[32] = 0;
|
||||||
strncpy(clientPass, pass, 64); clientPass[64] = 0;
|
strncpy(clientPass, pass, 64); clientPass[64] = 0;
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
encryptionType = enc_type;
|
||||||
|
strncpy(enterpriseAnonIdentity, ent_anon, 64); enterpriseAnonIdentity[64] = 0;
|
||||||
|
strncpy(enterpriseIdentity, ent_iden, 64); enterpriseIdentity[64] = 0;
|
||||||
|
#endif
|
||||||
memset(bssid, 0, sizeof(bssid));
|
memset(bssid, 0, sizeof(bssid));
|
||||||
}
|
}
|
||||||
} wifi_config;
|
} wifi_config;
|
||||||
|
|||||||
@@ -27,13 +27,18 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address
|
char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address
|
||||||
char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address
|
char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address
|
||||||
char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask
|
char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
char et[4] = "ET"; et[2] = 48+n; et[3] = 0; //WiFi encryption type
|
||||||
|
char ea[4] = "EA"; ea[2] = 48+n; ea[3] = 0; //enterprise anonymous identity
|
||||||
|
char ei[4] = "EI"; ei[2] = 48+n; ei[3] = 0; //enterprise identity
|
||||||
|
#endif
|
||||||
if (request->hasArg(cs)) {
|
if (request->hasArg(cs)) {
|
||||||
if (n >= multiWiFi.size()) multiWiFi.emplace_back(); // expand vector by one
|
if (n >= multiWiFi.size()) multiWiFi.emplace_back(); // expand vector by one
|
||||||
char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID);
|
char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID);
|
||||||
char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass);
|
char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass);
|
||||||
|
|
||||||
strlcpy(multiWiFi[n].clientSSID, request->arg(cs).c_str(), 33);
|
strlcpy(multiWiFi[n].clientSSID, request->arg(cs).c_str(), 33);
|
||||||
if (strlen(oldSSID) == 0 || !strncmp(multiWiFi[n].clientSSID, oldSSID, 32)) {
|
if (strlen(oldSSID) == 0 || strncmp(multiWiFi[n].clientSSID, oldSSID, 32) != 0) {
|
||||||
forceReconnect = true;
|
forceReconnect = true;
|
||||||
}
|
}
|
||||||
if (!isAsterisksOnly(request->arg(pw).c_str(), 65)) {
|
if (!isAsterisksOnly(request->arg(pw).c_str(), 65)) {
|
||||||
@@ -49,6 +54,34 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
multiWiFi[n].staticGW[i] = request->arg(gw).toInt();
|
multiWiFi[n].staticGW[i] = request->arg(gw).toInt();
|
||||||
multiWiFi[n].staticSN[i] = request->arg(sn).toInt();
|
multiWiFi[n].staticSN[i] = request->arg(sn).toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
byte oldType = multiWiFi[n].encryptionType;
|
||||||
|
char oldAnon[65]; strcpy(oldAnon, multiWiFi[n].enterpriseAnonIdentity);
|
||||||
|
char oldIden[65]; strcpy(oldIden, multiWiFi[n].enterpriseIdentity);
|
||||||
|
if (request->hasArg(et) && request->hasArg(ea) && request->hasArg(ei)) {
|
||||||
|
multiWiFi[n].encryptionType = request->arg(et).toInt();
|
||||||
|
strlcpy(multiWiFi[n].enterpriseAnonIdentity, request->arg(ea).c_str(), 65);
|
||||||
|
strlcpy(multiWiFi[n].enterpriseIdentity, request->arg(ei).c_str(), 65);
|
||||||
|
} else {
|
||||||
|
// No enterprise settings provided, default to PSK
|
||||||
|
multiWiFi[n].encryptionType = WIFI_ENCRYPTION_TYPE_PSK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiWiFi[n].encryptionType == WIFI_ENCRYPTION_TYPE_PSK) {
|
||||||
|
// PSK - Clear the anonymous identity and identity fields
|
||||||
|
multiWiFi[n].enterpriseAnonIdentity[0] = '\0';
|
||||||
|
multiWiFi[n].enterpriseIdentity[0] = '\0';
|
||||||
|
}
|
||||||
|
forceReconnect |= oldType != multiWiFi[n].encryptionType;
|
||||||
|
if (strncmp(multiWiFi[n].enterpriseAnonIdentity, oldAnon, 64) != 0) {
|
||||||
|
forceReconnect = true;
|
||||||
|
}
|
||||||
|
if (strncmp(multiWiFi[n].enterpriseIdentity, oldIden, 64) != 0) {
|
||||||
|
forceReconnect = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -711,7 +711,39 @@ void WLED::initConnection()
|
|||||||
// convert the "serverDescription" into a valid DNS hostname (alphanumeric)
|
// convert the "serverDescription" into a valid DNS hostname (alphanumeric)
|
||||||
char hostname[25];
|
char hostname[25];
|
||||||
prepareHostname(hostname);
|
prepareHostname(hostname);
|
||||||
|
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
if (multiWiFi[selectedWiFi].encryptionType == WIFI_ENCRYPTION_TYPE_PSK) {
|
||||||
|
DEBUG_PRINTLN(F("Using PSK"));
|
||||||
|
#ifdef ESP8266
|
||||||
|
wifi_station_set_wpa2_enterprise_auth(0);
|
||||||
|
wifi_station_clear_enterprise_ca_cert();
|
||||||
|
wifi_station_clear_enterprise_cert_key();
|
||||||
|
wifi_station_clear_enterprise_identity();
|
||||||
|
wifi_station_clear_enterprise_username();
|
||||||
|
wifi_station_clear_enterprise_password();
|
||||||
|
#endif
|
||||||
|
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass);
|
||||||
|
} else { // WIFI_ENCRYPTION_TYPE_ENTERPRISE
|
||||||
|
DEBUG_PRINTF_P(PSTR("Using WPA2_AUTH_PEAP (Anon: %s, Ident: %s)\n"), multiWiFi[selectedWiFi].enterpriseAnonIdentity, multiWiFi[selectedWiFi].enterpriseIdentity);
|
||||||
|
#ifdef ESP8266
|
||||||
|
struct station_config sta_conf;
|
||||||
|
os_memset(&sta_conf, 0, sizeof(sta_conf));
|
||||||
|
os_memcpy(sta_conf.ssid, multiWiFi[selectedWiFi].clientSSID, 32);
|
||||||
|
os_memcpy(sta_conf.password, multiWiFi[selectedWiFi].clientPass, 64);
|
||||||
|
wifi_station_set_config(&sta_conf);
|
||||||
|
wifi_station_set_wpa2_enterprise_auth(1);
|
||||||
|
wifi_station_set_enterprise_identity((u8*)(void*)multiWiFi[selectedWiFi].enterpriseAnonIdentity, os_strlen(multiWiFi[selectedWiFi].enterpriseAnonIdentity));
|
||||||
|
wifi_station_set_enterprise_username((u8*)(void*)multiWiFi[selectedWiFi].enterpriseIdentity, os_strlen(multiWiFi[selectedWiFi].enterpriseIdentity));
|
||||||
|
wifi_station_set_enterprise_password((u8*)(void*)multiWiFi[selectedWiFi].clientPass, os_strlen(multiWiFi[selectedWiFi].clientPass));
|
||||||
|
wifi_station_connect();
|
||||||
|
#else
|
||||||
|
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, WPA2_AUTH_PEAP, multiWiFi[selectedWiFi].enterpriseAnonIdentity, multiWiFi[selectedWiFi].enterpriseIdentity, multiWiFi[selectedWiFi].clientPass);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#else // WLED_ENABLE_WPA_ENTERPRISE
|
||||||
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times
|
WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times
|
||||||
|
#endif // WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
WiFi.setTxPower(wifi_power_t(txPower));
|
WiFi.setTxPower(wifi_power_t(txPower));
|
||||||
|
|||||||
@@ -77,6 +77,9 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
#include "wpa2_enterprise.h"
|
||||||
|
#endif
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include <ESPAsyncTCP.h>
|
#include <ESPAsyncTCP.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
|||||||
@@ -198,6 +198,18 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
memset(fpass,'*',l);
|
memset(fpass,'*',l);
|
||||||
char bssid[13];
|
char bssid[13];
|
||||||
fillMAC2Str(bssid, multiWiFi[n].bssid);
|
fillMAC2Str(bssid, multiWiFi[n].bssid);
|
||||||
|
#ifdef WLED_ENABLE_WPA_ENTERPRISE
|
||||||
|
settingsScript.printf_P(PSTR("addWiFi(\"%s\",\"%s\",\"%s\",0x%X,0x%X,0x%X,\"%u\",\"%s\",\"%s\");"),
|
||||||
|
multiWiFi[n].clientSSID,
|
||||||
|
fpass,
|
||||||
|
bssid,
|
||||||
|
(uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct
|
||||||
|
(uint32_t) multiWiFi[n].staticGW,
|
||||||
|
(uint32_t) multiWiFi[n].staticSN,
|
||||||
|
multiWiFi[n].encryptionType,
|
||||||
|
multiWiFi[n].enterpriseAnonIdentity,
|
||||||
|
multiWiFi[n].enterpriseIdentity);
|
||||||
|
#else
|
||||||
settingsScript.printf_P(PSTR("addWiFi(\"%s\",\"%s\",\"%s\",0x%X,0x%X,0x%X);"),
|
settingsScript.printf_P(PSTR("addWiFi(\"%s\",\"%s\",\"%s\",0x%X,0x%X,0x%X);"),
|
||||||
multiWiFi[n].clientSSID,
|
multiWiFi[n].clientSSID,
|
||||||
fpass,
|
fpass,
|
||||||
@@ -205,6 +217,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
|||||||
(uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct
|
(uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct
|
||||||
(uint32_t) multiWiFi[n].staticGW,
|
(uint32_t) multiWiFi[n].staticGW,
|
||||||
(uint32_t) multiWiFi[n].staticSN);
|
(uint32_t) multiWiFi[n].staticSN);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
printSetFormValue(settingsScript,PSTR("D0"),dnsAddress[0]);
|
printSetFormValue(settingsScript,PSTR("D0"),dnsAddress[0]);
|
||||||
|
|||||||
Reference in New Issue
Block a user