hostapd: add support for authenticating with multiple PSKs via ubus helper
authorFelix Fietkau <nbd@nbd.name>
Thu, 23 May 2024 18:16:40 +0000 (20:16 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 22 Oct 2024 12:40:42 +0000 (14:40 +0200)
Also supports assigning a VLAN ID based on the PSK

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/network/services/hostapd/files/hostapd.uc
package/network/services/hostapd/files/wpad_acl.json
package/network/services/hostapd/patches/601-ucode_support.patch
package/network/services/hostapd/src/src/ap/ucode.c
package/network/services/hostapd/src/src/ap/ucode.h

index 62fb9cf777fe900cc4391679fe2c920c3766e354..e666ad4faae2cc15a95f2f9e1c06088086eb37e1 100644 (file)
@@ -890,6 +890,9 @@ let main_obj = {
                        hostapd.printf(`Set new config for phy ${phy}: ${file}`);
                        iface_set_config(phy, config);
 
+                       if (hostapd.data.auth_obj)
+                               hostapd.data.auth_obj.notify("reload", { phy });
+
                        return {
                                pid: hostapd.getpid()
                        };
@@ -954,6 +957,10 @@ let main_obj = {
 
 hostapd.data.ubus = ubus;
 hostapd.data.obj = ubus.publish("hostapd", main_obj);
+
+
+let auth_obj = {};
+hostapd.data.auth_obj = ubus.publish("hostapd-auth", auth_obj);
 hostapd.udebug_set("hostapd", hostapd.data.ubus);
 
 function bss_event(type, name, data) {
@@ -980,5 +987,25 @@ return {
        },
        bss_remove: function(phy, name, obj) {
                bss_event("remove", name);
-       }
+       },
+       sta_auth: function(iface, sta) {
+               let msg = { iface, sta };
+               let ret = {};
+               let data_cb = (type, data) => {
+                       ret = { ...ret, ...data };
+               };
+               if (hostapd.data.auth_obj)
+                       hostapd.data.auth_obj.notify("sta_auth", msg, data_cb, null, null, 1000);
+               return ret;
+       },
+       sta_connected: function(iface, sta, data) {
+               let msg = { iface, sta, ...data };
+               let ret = {};
+               let data_cb = (type, data) => {
+                       ret = { ...ret, ...data };
+               };
+               if (hostapd.data.auth_obj)
+                       hostapd.data.auth_obj.notify("sta_connected", msg, data_cb, null, null, 1000);
+               return ret;
+       },
 };
index 7532953cabda22695fabf6ce65f01da0f5718b02..755f836b67329e3453fa664cbe2e96dd9eed3cf0 100644 (file)
@@ -15,6 +15,6 @@
                }
        },
        "subscribe": [ "udebug" ],
-       "publish": [ "hostapd", "hostapd.*", "wpa_supplicant", "wpa_supplicant.*" ],
+       "publish": [ "hostapd", "hostapd.*", "wpa_supplicant", "wpa_supplicant.*", "hostapd-auth" ],
        "send": [ "bss.*", "wps_credentials" ]
 }
index 4288e0f78984bc0e3e4e20404f36df577b5a05ce..a3c03bf3599d7266f51f1a6da29f92ce25624fd8 100644 (file)
@@ -704,3 +704,138 @@ as adding/removing interfaces.
  #ifdef CONFIG_MATCH_IFACE
        int matched;
  #endif /* CONFIG_MATCH_IFACE */
+--- a/src/ap/ieee802_11.c
++++ b/src/ap/ieee802_11.c
+@@ -555,12 +555,17 @@ const char * sae_get_password(struct hos
+                             struct sae_pt **s_pt,
+                             const struct sae_pk **s_pk)
+ {
++      struct hostapd_bss_config *conf = hapd->conf;
++      struct hostapd_ssid *ssid = &conf->ssid;
+       const char *password = NULL;
+-      struct sae_password_entry *pw;
++      struct sae_password_entry *pw = NULL;
+       struct sae_pt *pt = NULL;
+       const struct sae_pk *pk = NULL;
+       struct hostapd_sta_wpa_psk_short *psk = NULL;
++      if (sta && sta->use_sta_psk)
++              goto use_sta_psk;
++
+       for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
+               if (!is_broadcast_ether_addr(pw->peer_addr) &&
+                   (!sta ||
+@@ -582,12 +587,28 @@ const char * sae_get_password(struct hos
+               pt = hapd->conf->ssid.pt;
+       }
++use_sta_psk:
+       if (!password && sta) {
+               for (psk = sta->psk; psk; psk = psk->next) {
+-                      if (psk->is_passphrase) {
+-                              password = psk->passphrase;
++                      if (!psk->is_passphrase)
++                              continue;
++
++                      password = psk->passphrase;
++                      if (!sta->use_sta_psk)
++                              break;
++
++                      if (sta->sae_pt) {
++                              pt = sta->sae_pt;
+                               break;
+                       }
++
++                      pt = sae_derive_pt(conf->sae_groups, ssid->ssid,
++                                         ssid->ssid_len,
++                                         (const u8 *) password,
++                                         os_strlen(password),
++                                         NULL);
++                      sta->sae_pt = pt;
++                      break;
+               }
+       }
+@@ -3229,6 +3250,12 @@ static void handle_auth(struct hostapd_d
+               goto fail;
+       }
++      res = hostapd_ucode_sta_auth(hapd, sta);
++      if (res) {
++              resp = res;
++              goto fail;
++      }
++
+       sta->flags &= ~WLAN_STA_PREAUTH;
+       ieee802_1x_notify_pre_auth(sta->eapol_sm, 0);
+--- a/src/ap/sta_info.c
++++ b/src/ap/sta_info.c
+@@ -474,6 +474,9 @@ void ap_free_sta(struct hostapd_data *ha
+       forced_memzero(sta->last_tk, WPA_TK_MAX_LEN);
+ #endif /* CONFIG_TESTING_OPTIONS */
++      if (sta->sae_pt)
++              sae_deinit_pt(sta->sae_pt);
++
+       os_free(sta);
+ }
+@@ -1507,6 +1510,8 @@ void ap_sta_set_authorized_event(struct
+ #endif /* CONFIG_P2P */
+       const u8 *ip_ptr = NULL;
++      if (authorized)
++              hostapd_ucode_sta_connected(hapd, sta);
+ #ifdef CONFIG_P2P
+       if (hapd->p2p_group == NULL) {
+               if (sta->p2p_ie != NULL &&
+--- a/src/ap/sta_info.h
++++ b/src/ap/sta_info.h
+@@ -180,6 +180,9 @@ struct sta_info {
+       int vlan_id_bound; /* updated by ap_sta_bind_vlan() */
+        /* PSKs from RADIUS authentication server */
+       struct hostapd_sta_wpa_psk_short *psk;
++      struct sae_pt *sae_pt;
++      int use_sta_psk;
++      int psk_idx;
+       char *identity; /* User-Name from RADIUS */
+       char *radius_cui; /* Chargeable-User-Identity from RADIUS */
+--- a/src/ap/wpa_auth_glue.c
++++ b/src/ap/wpa_auth_glue.c
+@@ -400,6 +400,7 @@ static const u8 * hostapd_wpa_auth_get_p
+       struct sta_info *sta = ap_get_sta(hapd, addr);
+       const u8 *psk;
++      sta->psk_idx = 0;
+       if (vlan_id)
+               *vlan_id = 0;
+       if (psk_len)
+@@ -446,13 +447,16 @@ static const u8 * hostapd_wpa_auth_get_p
+        * returned psk which should not be returned again.
+        * logic list (all hostapd_get_psk; all sta->psk)
+        */
++      if (sta && sta->use_sta_psk)
++              psk = NULL;
+       if (sta && sta->psk && !psk) {
+               struct hostapd_sta_wpa_psk_short *pos;
++              int psk_idx = 1;
+               if (vlan_id)
+                       *vlan_id = 0;
+               psk = sta->psk->psk;
+-              for (pos = sta->psk; pos; pos = pos->next) {
++              for (pos = sta->psk; pos; pos = pos->next, psk_idx++) {
+                       if (pos->is_passphrase) {
+                               if (pbkdf2_sha1(pos->passphrase,
+                                               hapd->conf->ssid.ssid,
+@@ -469,6 +473,8 @@ static const u8 * hostapd_wpa_auth_get_p
+                               break;
+                       }
+               }
++              if (psk)
++                      sta->psk_idx = psk_idx;
+       }
+       return psk;
+ }
index a5630d7eaac3540692e868819903c58d63062e4e..2da2b4dc9387384122fa9eec566f7dca9ac1e3d1 100644 (file)
@@ -9,6 +9,7 @@
 #include "ap_drv_ops.h"
 #include "dfs.h"
 #include "acs.h"
+#include "ieee802_11_auth.h"
 #include <libubox/uloop.h>
 
 static uc_resource_type_t *global_type, *bss_type, *iface_type;
@@ -711,6 +712,110 @@ out:
        return ret ? NULL : ucv_boolean_new(true);
 }
 
+int hostapd_ucode_sta_auth(struct hostapd_data *hapd, struct sta_info *sta)
+{
+       char addr[sizeof(MACSTR)];
+       uc_value_t *val, *cur;
+       int ret = 0;
+
+       if (wpa_ucode_call_prepare("sta_auth"))
+               return 0;
+
+       uc_value_push(ucv_get(ucv_string_new(hapd->conf->iface)));
+
+       snprintf(addr, sizeof(addr), MACSTR, MAC2STR(sta->addr));
+       val = ucv_string_new(addr);
+       uc_value_push(ucv_get(val));
+
+       val = wpa_ucode_call(2);
+
+       cur = ucv_object_get(val, "psk", NULL);
+       if (ucv_type(cur) == UC_ARRAY) {
+               struct hostapd_sta_wpa_psk_short *p, **next;
+               size_t len = ucv_array_length(cur);
+
+               next = &sta->psk;
+               hostapd_free_psk_list(*next);
+               *next = NULL;
+
+               for (size_t i = 0; i < len; i++) {
+                       uc_value_t *cur_psk;
+                       const char *str;
+                       size_t str_len;
+
+                       cur_psk = ucv_array_get(cur, i);
+                       str = ucv_string_get(cur_psk);
+                       str_len = strlen(str);
+                       if (!str || str_len < 8 || str_len > 64)
+                               continue;
+
+                       p = os_zalloc(sizeof(*p));
+                       if (len == 64) {
+                               if (hexstr2bin(str, p->psk, PMK_LEN) < 0) {
+                                       free(p);
+                                       continue;
+                               }
+                       } else {
+                               p->is_passphrase = 1;
+                               memcpy(p->passphrase, str, str_len + 1);
+                       }
+
+                       *next = p;
+                       next = &p->next;
+               }
+       }
+
+       cur = ucv_object_get(val, "force_psk", NULL);
+       sta->use_sta_psk = ucv_is_truish(cur);
+
+       cur = ucv_object_get(val, "status", NULL);
+       if (ucv_type(cur) == UC_INTEGER)
+               ret = ucv_int64_get(cur);
+
+       ucv_put(val);
+       ucv_gc(vm);
+
+       return ret;
+}
+
+void hostapd_ucode_sta_connected(struct hostapd_data *hapd, struct sta_info *sta)
+{
+       char addr[sizeof(MACSTR)];
+       uc_value_t *val, *cur;
+       int ret = 0;
+
+       if (wpa_ucode_call_prepare("sta_connected"))
+               return;
+
+       uc_value_push(ucv_get(ucv_string_new(hapd->conf->iface)));
+
+       snprintf(addr, sizeof(addr), MACSTR, MAC2STR(sta->addr));
+       val = ucv_string_new(addr);
+       uc_value_push(ucv_get(val));
+
+       val = ucv_object_new(vm);
+       if (sta->psk_idx)
+               ucv_object_add(val, "psk_idx", ucv_int64_new(sta->psk_idx - 1));
+       uc_value_push(ucv_get(val));
+
+       val = wpa_ucode_call(3);
+       if (ucv_type(val) != UC_OBJECT)
+               goto out;
+
+       cur = ucv_object_get(val, "vlan", NULL);
+       if (ucv_type(cur) == UC_INTEGER) {
+               struct vlan_description vdesc = {
+                       .notempty = 1,
+                       .untagged = ucv_int64_get(cur),
+               };
+
+               ap_sta_set_vlan(hapd, sta, &vdesc);
+               ap_sta_bind_vlan(hapd, sta);
+       }
+
+out:
+       ucv_put(val);
+}
 
 int hostapd_ucode_init(struct hapd_interfaces *ifaces)
 {
index 2f7bcd6542a2362e8134c3d040091a4e5b9d46e2..d0b00e59652a06973f14ed989493ced8e34b81ba 100644 (file)
@@ -25,6 +25,8 @@ void hostapd_ucode_free(void);
 void hostapd_ucode_free_iface(struct hostapd_iface *iface);
 void hostapd_ucode_free_bss(struct hostapd_data *hapd);
 void hostapd_ucode_bss_cb(struct hostapd_data *hapd, const char *type);
+int hostapd_ucode_sta_auth(struct hostapd_data *hapd, struct sta_info *sta);
+void hostapd_ucode_sta_connected(struct hostapd_data *hapd, struct sta_info *sta);
 
 #ifdef CONFIG_APUP
 void hostapd_ucode_apup_newpeer(struct hostapd_data *hapd, const char *ifname);
@@ -45,6 +47,13 @@ static inline void hostapd_ucode_free_iface(struct hostapd_iface *iface)
 static inline void hostapd_ucode_bss_cb(struct hostapd_data *hapd, const char *type)
 {
 }
+static inline int hostapd_ucode_sta_auth(struct hostapd_data *hapd, struct sta_info *sta)
+{
+       return 0;
+}
+static inline void hostapd_ucode_sta_connected(struct hostapd_data *hapd, struct sta_info *sta)
+{
+}
 static inline void hostapd_ucode_free_bss(struct hostapd_data *hapd)
 {
 }