[PATCH 2/2] hostapd: Add configurable PEAP cryptobinding policy
github at singe.za.net
github at singe.za.net
Mon Oct 5 08:35:36 PDT 2026
From: Dominic White <singe-github at singe.za.net>
Add eap_peap_crypto_binding to control the integrated EAP server policy: 0
disables PEAPv0 cryptobinding, 1 keeps the existing optional behavior, and 2
requires a valid response. Preserve optional behavior by default and validate
configured values.
Add hwsim coverage for off, optional, and required policies.
Signed-off-by: Dominic White <singe-github at singe.za.net>
---
eap_example/eap_example_server.c | 1 +
hostapd/config_file.c | 11 ++++++++
hostapd/hostapd.conf | 6 +++++
src/ap/ap_config.c | 1 +
src/ap/ap_config.h | 1 +
src/ap/authsrv.c | 1 +
src/eap_server/eap.h | 1 +
src/eap_server/eap_server_peap.c | 13 +++++++++-
tests/hwsim/test_ap_eap.py | 44 ++++++++++++++++++++++++++++++++
9 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/eap_example/eap_example_server.c b/eap_example/eap_example_server.c
index 3a132bb34..617031358 100644
--- a/eap_example/eap_example_server.c
+++ b/eap_example/eap_example_server.c
@@ -224,6 +224,7 @@ int eap_example_server_init(void)
os_memset(&eap_conf, 0, sizeof(eap_conf));
eap_conf.eap_server = 1;
eap_conf.ssl_ctx = eap_ctx.tls_ctx;
+ eap_conf.peap_crypto_binding = 1;
os_memset(&eap_sess, 0, sizeof(eap_sess));
eap_ctx.eap = eap_server_sm_init(&eap_ctx, &eap_cb, &eap_conf,
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 59827966e..4a1b241f8 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2600,6 +2600,17 @@ static int hostapd_config_fill(struct hostapd_config *conf,
bss->crl_reload_interval = atoi(pos);
} else if (os_strcmp(buf, "tls_session_lifetime") == 0) {
bss->tls_session_lifetime = atoi(pos);
+ } else if (os_strcmp(buf, "eap_peap_crypto_binding") == 0) {
+ char *end;
+ long int val = strtol(pos, &end, 10);
+
+ if (end == pos || *end || val < 0 || val > 2) {
+ wpa_printf(MSG_ERROR,
+ "Line %d: invalid eap_peap_crypto_binding '%s'",
+ line, pos);
+ return 1;
+ }
+ bss->eap_peap_crypto_binding = val;
} else if (os_strcmp(buf, "tls_flags") == 0) {
bss->tls_flags = parse_tls_flags(pos);
} else if (os_strcmp(buf, "max_auth_rounds") == 0) {
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 0e9001221..b4cc0d6f4 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1344,6 +1344,12 @@ eap_server=0
# to use SQLite database instead of a text file.
#eap_user_file=/etc/hostapd.eap_user
+# PEAPv0 cryptobinding policy for the integrated EAP server
+# 0 = do not use cryptobinding
+# 1 = use cryptobinding when the peer supports it (default)
+# 2 = require cryptobinding
+#eap_peap_crypto_binding=1
+
# CA certificate (PEM or DER file) for EAP-TLS/PEAP/TTLS
#ca_cert=/etc/hostapd.ca.pem
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index d00c2c580..38f29aa40 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -157,6 +157,7 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
* This can be enabled by default once the implementation has been fully
* completed and tested with other implementations. */
bss->tls_flags = TLS_CONN_DISABLE_TLSv1_3;
+ bss->eap_peap_crypto_binding = 1;
bss->max_auth_rounds = 100;
bss->max_auth_rounds_short = 50;
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index c96481df7..62dcb1165 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -444,6 +444,7 @@ struct hostapd_bss_config {
int check_crl_strict;
unsigned int crl_reload_interval;
unsigned int tls_session_lifetime;
+ int eap_peap_crypto_binding; /* 0=off, 1=optional, 2=required */
unsigned int tls_flags;
unsigned int max_auth_rounds;
unsigned int max_auth_rounds_short;
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 3e8031810..1cc691bbe 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -314,6 +314,7 @@ static struct eap_config * authsrv_eap_config(struct hostapd_data *hapd)
cfg->eap_sim_db_priv = hapd->eap_sim_db_priv;
cfg->tls_session_lifetime = hapd->conf->tls_session_lifetime;
cfg->tls_flags = hapd->conf->tls_flags;
+ cfg->peap_crypto_binding = hapd->conf->eap_peap_crypto_binding;
cfg->max_auth_rounds = hapd->conf->max_auth_rounds;
cfg->max_auth_rounds_short = hapd->conf->max_auth_rounds_short;
if (hapd->conf->pac_opaque_encr_key)
diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h
index dd02b0cdb..f379022df 100644
--- a/src/eap_server/eap.h
+++ b/src/eap_server/eap.h
@@ -261,6 +261,7 @@ struct eap_config {
int erp;
unsigned int tls_session_lifetime;
unsigned int tls_flags;
+ int peap_crypto_binding; /* 0=off, 1=optional, 2=required */
unsigned int max_auth_rounds;
unsigned int max_auth_rounds_short;
diff --git a/src/eap_server/eap_server_peap.c b/src/eap_server/eap_server_peap.c
index a4d4e4331..c13f500c7 100644
--- a/src/eap_server/eap_server_peap.c
+++ b/src/eap_server/eap_server_peap.c
@@ -184,7 +184,18 @@ static void * eap_peap_init(struct eap_sm *sm)
data->peap_version = data->force_version;
}
data->state = START;
- data->crypto_binding = OPTIONAL_BINDING;
+ switch (sm->cfg->peap_crypto_binding) {
+ case NO_BINDING:
+ data->crypto_binding = NO_BINDING;
+ break;
+ case REQUIRE_BINDING:
+ data->crypto_binding = REQUIRE_BINDING;
+ break;
+ case OPTIONAL_BINDING:
+ default:
+ data->crypto_binding = OPTIONAL_BINDING;
+ break;
+ }
if (eap_server_tls_ssl_init(sm, &data->ssl, 0, EAP_TYPE_PEAP)) {
wpa_printf(MSG_INFO, "EAP-PEAP: Failed to initialize SSL.");
diff --git a/tests/hwsim/test_ap_eap.py b/tests/hwsim/test_ap_eap.py
index 29c702ff7..0f6b2b091 100644
--- a/tests/hwsim/test_ap_eap.py
+++ b/tests/hwsim/test_ap_eap.py
@@ -2448,6 +2448,50 @@ def test_ap_wpa2_eap_peap_crypto_binding(dev, apdev):
phase1="peapver=0 crypto_binding=0",
phase2="auth=MSCHAPV2")
+def test_ap_wpa2_eap_peap_crypto_binding_config(dev, apdev):
+ """PEAP cryptobinding policy configuration on the server"""
+ check_eap_capa(dev[0], "MSCHAPV2")
+
+ tests = [(0, 2, True), (1, 2, False), (2, 0, True), (2, 2, False)]
+ for server_policy, peer_policy, expect_failure in tests:
+ params = int_eap_server_params()
+ params["eap_peap_crypto_binding"] = str(server_policy)
+ hapd = hostapd.add_ap(apdev[0], params)
+ phase1 = f"peapver=0 crypto_binding={peer_policy}"
+ if server_policy == 2 and peer_policy == 0:
+ # This supplicant does not return cryptobinding. The server
+ # rejects the exchange with an outer EAP-Failure; wpa_supplicant
+ # reports that as a disconnection rather than EAP-FAILURE.
+ dev[0].connect("test-wpa2-eap",
+ key_mgmt="WPA-EAP WPA-EAP-SHA256 WPA-EAP-SHA384",
+ eap="PEAP", identity="user", password="password",
+ ca_cert="auth_serv/ca.pem", phase1=phase1,
+ phase2="auth=MSCHAPV2", wait_connect=False,
+ scan_freq="2412", ieee80211w="1")
+ ev = dev[0].wait_event(["CTRL-EVENT-EAP-STARTED"], timeout=16)
+ if ev is None:
+ raise Exception("Association and EAP start timed out")
+ ev = dev[0].wait_event(["CTRL-EVENT-EAP-METHOD",
+ "CTRL-EVENT-EAP-FAILURE"], timeout=10)
+ if ev is None or "PEAP" not in ev:
+ raise Exception("PEAP method selection timed out")
+ ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED",
+ "CTRL-EVENT-DISCONNECTED"], timeout=10)
+ if ev is None or "CTRL-EVENT-CONNECTED" in ev:
+ raise Exception("Required server cryptobinding was not enforced")
+ if "reason=23" not in ev:
+ raise Exception("Unexpected disconnect after required "
+ "cryptobinding rejection: " + ev)
+ else:
+ eap_connect(dev[0], hapd, "PEAP", "user", password="password",
+ ca_cert="auth_serv/ca.pem", phase1=phase1,
+ phase2="auth=MSCHAPV2",
+ expect_failure=expect_failure,
+ local_error_report=expect_failure)
+ dev[0].request("REMOVE_NETWORK all")
+ if not expect_failure:
+ dev[0].wait_disconnected()
+
def test_ap_wpa2_eap_peap_crypto_binding_server_oom(dev, apdev):
"""WPA2-Enterprise connection using EAP-PEAPv0/EAP-MSCHAPv2 and crypto binding with server OOM"""
check_eap_capa(dev[0], "MSCHAPV2")
--
2.54.0 (Apple Git-157)
More information about the Hostap
mailing list