(This is my first post; hopefully, I've understood the guidelines correctly in posting this here.)
I'm planning to submit a PR and would like feedback regarding the approach before I do so.
Problem
When connecting as a client to hotspots that require EAP-TTLS (or PEAP/FAST/TLS) with TLS 1.0 or 1.1, OpenWrt's UCI cannot generate a working wpa_supplicant configuration — even when WPA Supplicant and the underlying TLS library support it.
The failure looks like this:
wpa_supplicant[1775]: SSL: SSL3 alert: read (remote end reported an error):fatal:handshake failure
wpa_supplicant[1775]: OpenSSL: openssl_handshake - SSL_connect error:0A000410:SSL routines::ssl/tls alert handshake failure
wpa_supplicant[1775]: apcli0: CTRL-EVENT-EAP-FAILURE EAP authentication failed
This affects a meaningful number of real-world deployments. Comcast/Xfinity alone claims over 23 million hotspots, and Spectrum, Cox, and others operate similar infrastructure — many configured (or limited by older firmware) with older TLS versions. The issue also affects enterprise EAP deployments on legacy equipment.
There's an existing forum thread with a workaround that hardcodes a fix in hostapd.sh, but that approach is for older OpenWrt versions, breaks on every upgrade, and doesn't apply to current 25.x (which uses ucode): WPA2-Enterprise Client, 802.1x - How to allow weak SSL Ciphers?. Separately, there is another post here with a solution that does not work with 25.x: WPA2-EAP client mode with TLSv1.0
Root Cause
There is no way to express the needed wpa_supplicant parameters through UCI in /etc/config/wireless.
For OpenSSL, two things are needed in the wpa-supplicant generated config:
- Top-level:
openssl_ciphers=DEFAULT@SECLEVEL=0 - network={} (for TLS/TTLS/PEAP/FAST):
phase1="tls_disable_tlsv1_0=0 tls_disable_tlsv1_1=0" - Note:
tls_min_version=inphase1is silently ignored by OpenSSL.
For WolfSSL, the requirements for the generated config are different and partially incompatible:
- Top-level
openssl_ciphers=DEFAULT@SECLEVEL=0causes WolfSSL to fail cipher initialization entirely - network={}:
phase1="tls_min_version=1.0" - Note:
tls_disable_tlsv1_*inphase1is silently ignored by WolfSSL
For MbedTLS: not tested; my understanding is that it doesn't support EAP.
This means the correctly generated config depends on which TLS library wpad was compiled against — but ucode scripts have no built-in way to determine this, and wpad -v does not expose it.
A practical detection method: since wpad built with WolfSSL or MbedTLS is statically linked, ldd /usr/sbin/wpad | grep /usr/lib/libssl returns output only for OpenSSL builds. Is there a cleaner/more reliable way to do this detection?
Proposed Options
Again, I'm planning to submit a PR and would like feedback on which approach is preferred before I do so.
Option 1 — Do nothing
The workaround is hand-editing /usr/share/ucode/wifi/supplicant.uc, which breaks (will be overwritten) on every upgrade of OpenWRT. From a user perspective: not viable long-term.
Option 2 — Raw passthrough: option openssl_ciphers and option phase1
Expose both parameters directly in UCI as pass-through strings. supplicant.uc would apply light guardrails: only emit openssl_ciphers if wpad is linked with OpenSSL; only emit phase1 for EAP types that use it (TLS, TTLS, PEAP, FAST).
// wireless.wifi-iface.json additions
"openssl_ciphers": {
"description": "Override for OpenSSL ciphers (OpenSSL builds only)",
"type": "string"
},
"phase1": {
"description": "Phase 1 EAP parameters (TLS/TTLS/PEAP/FAST only)",
"type": "string"
}
// supplicant.uc — top level
if (interface.config.openssl_ciphers &&
interface.config.eap_type in ['tls', 'ttls', 'peap', 'fast'] &&
length(fs.popen('/usr/bin/ldd /usr/sbin/wpad 2>/dev/null | grep /usr/lib/libssl').read('all')) > 0)
append_raw(`openssl_ciphers=${interface.config.openssl_ciphers}`);
// supplicant.uc - optional filter then, necessary per-network phase1 passthrough
// optionally if NOT EAP, TLS, TTLS, PEAP, FAST - remove phase1
// I'm not sure if this is even necessary
if (!interface.config.eap_type in ['tls', 'ttls', 'peap', 'fast'])
interface.config.phase1 = "";
network_append_string_vars(config, ['ssid',
...
'phase1',
]);
Pros: Maximum flexibility, future-proof, LuCI or users can supply whatever parameters are needed.
Cons: Shifts complexity to the user/LuCI; less discoverable; feels inconsistent with other UCI options.
Option 3 — Single option tls_min with generated output (preferred candidate)
Add a single option tls_min '1.0' UCI option. supplicant.uc then generates the correct openssl_ciphers and phase1 content for whichever TLS library is present.
// wireless.wifi-iface.json addition
"tls_min": {
"description": "Minimum TLS version for EAP authentication",
"type": "string",
"enum": [null, "1.0", "1.1", "1.2", "1.3"],
"default": null
}
// supplicant.uc — top level
let tls_min = interface.config.tls_min
? (([a, b] = interface.config.tls_min.split(".")), a * 1 + b / 10)
: null;
if (tls_min != null &&
tls_min < 1.2 &&
interface.config.eap_type in ['tls', 'ttls', 'peap', 'fast'] &&
length(fs.popen('/usr/bin/ldd /usr/sbin/wpad 2>/dev/null | grep /usr/lib/libssl').read('all')) > 0)
append_raw(`openssl_ciphers=DEFAULT@SECLEVEL=0`);
// per-network phase1 generation (applies for both EAP-TLS and EAP-PEAP/TTLS/FAST sections in supplicant.uc)
if (tls_min != null) {
let d10 = (tls_min <= 1.0) ? 0 : 1;
let d11 = (tls_min <= 1.1) ? 0 : 1;
let d12 = (tls_min <= 1.2) ? 0 : 1;
let d13 = (tls_min <= 1.3) ? 0 : 1;
// openssl uses tls_disable_tlsv1_#; wolfssl uses tls_min_version; both silently ignore the other
config.phase1 = `tls_disable_tlsv1_0=${d10} tls_disable_tlsv1_1=${d11} tls_disable_tlsv1_2=${d12} tls_disable_tlsv1_3=${d13} tls_min_version=${tls_min}`;
}
network_append_string_vars(config, ['ssid',
...
'phase1',
]);
Pros: Single intuitive UCI option; correct output generated automatically per TLS library; consistent with existing UCI design patterns.
Cons: Less flexible if future parameters are needed; logic must be maintained in two places (EAP-TLS section and EAP-PEAP/TTLS/FAST section).
Open Questions for the Community
- Option 2 vs Option 3 — Is the passthrough approach or the abstracted
tls_minoption more consistent with OpenWrt's UCI design philosophy? Option 3 feels cleaner for users and LuCI, but Option 2 offers more flexibility if otherphase1or cipher parameters are ever needed. - Library detection — Is
ldd /usr/sbin/wpad | grep libsslthe right approach for detecting whether the installedwpadis linked against OpenSSL, or is there a more idiomatic/reliable method from ucode? Since WolfSSL and MbedTLS builds are statically linked, thislddcheck appears to reliably distinguish them, but it feels fragile.