diff --git a/package/network/services/hostapd/files/hostapd.uc b/package/network/services/hostapd/files/hostapd.uc index 053f08cb96b..e345a678f41 100644 --- a/package/network/services/hostapd/files/hostapd.uc +++ b/package/network/services/hostapd/files/hostapd.uc @@ -11,6 +11,7 @@ hostapd.data.file_fields = { vlan_file: true, wpa_psk_file: true, sae_password_file: true, + rxkh_file: true, accept_mac_file: true, deny_mac_file: true, eap_user_file: true, @@ -351,6 +352,64 @@ function bss_reload_psk(bss, config, old_config) hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`); } +function normalize_rxkhs(txt) +{ + const pat = { + sep: "\x20", + mac: "([[:xdigit:]]{2}:?){5}[[:xdigit:]]{2}", + r0kh_id: "[\x21-\x7e]{1,48}", + r1kh_id: "([[:xdigit:]]{2}:?){5}[[:xdigit:]]{2}", + key: "[[:xdigit:]]{32,}", + r0kh: function() { + return "r0kh=" + this.mac + this.sep + this.r0kh_id; + }, + r1kh: function() { + return "r1kh=" + this.mac + this.sep + this.r1kh_id; + }, + rxkh: function() { + return "(" + this.r0kh() + "|" + this.r1kh() + ")" + this.sep + this.key; + }, + }; + + let rxkhs = filter( + split(txt, "\n"), (line) => match(line, regexp("^" + pat.rxkh() + "$")) + ) ?? []; + + rxkhs = map(rxkhs, function(k) { + k = split(k, " ", 3); + k[0] = lc(k[0]); + if(match(k[0], /^r1kh/)) { + k[1] = lc(k[1]); + } + if(!k[2] = hostapd.rkh_derive_key(k[2])) { + return; + } + return join(" ", k); + }); + + return join("\n", sort(filter(rxkhs, length))); +} + +function bss_reload_rxkhs(bss, config, old_config) +{ + let bss_rxkhs = join("\n", sort(split(bss.ctrl("GET_RXKHS"), "\n"))); + let bss_rxkhs_hash = hostapd.sha1(bss_rxkhs); + + if (is_equal(config.hash.rxkh_file, bss_rxkhs_hash)) { + if (is_equal(old_config.hash.rxkh_file, config.hash.rxkh_file)) + return; + } + + old_config.hash.rxkh_file = config.hash.rxkh_file; + if (!is_equal(old_config, config)) + return; + + let ret = bss.ctrl("RELOAD_RXKHS"); + ret ??= "failed"; + + hostapd.printf(`Reload RxKH file for bss ${config.ifname}: ${ret}`); +} + function remove_file_fields(config) { return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]); @@ -652,6 +711,7 @@ function iface_reload_config(name, phydev, config, old_config) } bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]); + bss_reload_rxkhs(bss, config.bss[i], bss_list_cfg[i]); if (is_equal(config.bss[i], bss_list_cfg[i])) continue; @@ -780,8 +840,13 @@ function iface_load_config(phy, radio, filename) continue; } - if (hostapd.data.file_fields[val[0]]) - bss.hash[val[0]] = hostapd.sha1(readfile(val[1])); + if (hostapd.data.file_fields[val[0]]) { + if (val[0] == "rxkh_file") { + bss.hash[val[0]] = hostapd.sha1(normalize_rxkhs(readfile(val[1]))); + } else { + bss.hash[val[0]] = hostapd.sha1(readfile(val[1])); + } + } push(bss.data, line); } diff --git a/package/network/services/hostapd/src/src/ap/ucode.c b/package/network/services/hostapd/src/src/ap/ucode.c index 2da2b4dc938..adc7c419148 100644 --- a/package/network/services/hostapd/src/src/ap/ucode.c +++ b/package/network/services/hostapd/src/src/ap/ucode.c @@ -823,6 +823,7 @@ int hostapd_ucode_init(struct hapd_interfaces *ifaces) { "printf", uc_wpa_printf }, { "getpid", uc_wpa_getpid }, { "sha1", uc_wpa_sha1 }, + { "rkh_derive_key", uc_wpa_rkh_derive_key }, { "freq_info", uc_wpa_freq_info }, { "add_iface", uc_hostapd_add_iface }, { "remove_iface", uc_hostapd_remove_iface }, diff --git a/package/network/services/hostapd/src/src/utils/ucode.c b/package/network/services/hostapd/src/src/utils/ucode.c index 29c753c3269..50b87982cee 100644 --- a/package/network/services/hostapd/src/src/utils/ucode.c +++ b/package/network/services/hostapd/src/src/utils/ucode.c @@ -3,6 +3,7 @@ #include "utils/eloop.h" #include "crypto/crypto.h" #include "crypto/sha1.h" +#include "crypto/sha256.h" #include "common/ieee802_11_common.h" #include #include @@ -236,6 +237,40 @@ uc_value_t *uc_wpa_sha1(uc_vm_t *vm, size_t nargs) return ucv_string_new_length(hash_hex, 2 * ARRAY_SIZE(hash)); } +uc_value_t *uc_wpa_rkh_derive_key(uc_vm_t *vm, size_t nargs) +{ + u8 oldkey[16]; + char *oldkey_hex; + u8 key[SHA256_MAC_LEN]; + size_t key_len = sizeof(key); + char key_hex[2 * ARRAY_SIZE(key) + 1]; + uc_value_t *val = uc_fn_arg(0); + int i; + + if (ucv_type(val) != UC_STRING) + return NULL; + + oldkey_hex = ucv_string_get(val); + + if (!hexstr2bin(oldkey_hex, key, key_len)) + return ucv_string_new_length(oldkey_hex, 2 * ARRAY_SIZE(key)); + + if (hexstr2bin(oldkey_hex, oldkey, sizeof(oldkey))) { + wpa_printf(MSG_ERROR, "Invalid RxKH key: '%s'", oldkey_hex); + return NULL; + } + + if (hmac_sha256_kdf(oldkey, sizeof(oldkey), "FT OLDKEY", NULL, 0, key, key_len) < 0) { + wpa_printf(MSG_ERROR, "Invalid RxKH key: '%s'", oldkey_hex); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(key); i++) + sprintf(key_hex + 2 * i, "%02x", key[i]); + + return ucv_string_new_length(key_hex, 2 * ARRAY_SIZE(key)); +} + uc_vm_t *wpa_ucode_create_vm(void) { static uc_parse_config_t config = { diff --git a/package/network/services/hostapd/src/src/utils/ucode.h b/package/network/services/hostapd/src/src/utils/ucode.h index c083241e079..a273c19b7bc 100644 --- a/package/network/services/hostapd/src/src/utils/ucode.h +++ b/package/network/services/hostapd/src/src/utils/ucode.h @@ -25,6 +25,7 @@ uc_value_t *uc_wpa_udebug_set(uc_vm_t *vm, size_t nargs); uc_value_t *uc_wpa_printf(uc_vm_t *vm, size_t nargs); uc_value_t *uc_wpa_getpid(uc_vm_t *vm, size_t nargs); uc_value_t *uc_wpa_sha1(uc_vm_t *vm, size_t nargs); +uc_value_t *uc_wpa_rkh_derive_key(uc_vm_t *vm, size_t nargs); uc_value_t *uc_wpa_freq_info(uc_vm_t *vm, size_t nargs); #endif