From: Michael Braun Date: Sun, 11 Dec 2011 11:01:57 +0000 (+0200) Subject: Allow WPA passphrase to be fetched with RADIUS Tunnel-Password attribute X-Git-Tag: aosp-jb-start~155 X-Git-Url: http://www.project-moonshot.org/gitweb/?a=commitdiff_plain;h=05ab9712b9977192b713f01f07c3b14ca4d1ba78;p=mech_eap.git Allow WPA passphrase to be fetched with RADIUS Tunnel-Password attribute This allows per-device PSK to be configured for WPA-Personal using a RADIUS authentication server. This uses RADIUS-based MAC address ACL (macaddr_acl=2), i.e., Access-Request uses the MAC address of the station as the User-Name and User-Password. The WPA passphrase is returned in Tunnel-Password attribute in Access-Accept. This functionality can be enabled with the new hostapd.conf parameter, wpa_psk_radius. Signed-hostap: Michael Braun --- diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 9d39b48..ca79695 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1050,9 +1050,18 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, return -1; } + if (bss->wpa && bss->wpa_psk_radius != PSK_RADIUS_IGNORED && + bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) { + wpa_printf(MSG_ERROR, "WPA-PSK using RADIUS enabled, but no " + "RADIUS checking (macaddr_acl=2) enabled."); + return -1; + } + if (bss->wpa && (bss->wpa_key_mgmt & WPA_KEY_MGMT_PSK) && bss->ssid.wpa_psk == NULL && bss->ssid.wpa_passphrase == NULL && - bss->ssid.wpa_psk_file == NULL) { + bss->ssid.wpa_psk_file == NULL && + (bss->wpa_psk_radius != PSK_RADIUS_REQUIRED || + bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH)) { wpa_printf(MSG_ERROR, "WPA-PSK enabled, but PSK or passphrase " "is not configured."); return -1; @@ -1629,6 +1638,16 @@ struct hostapd_config * hostapd_config_read(const char *fname) hostapd_config_parse_key_mgmt(line, pos); if (bss->wpa_key_mgmt == -1) errors++; + } else if (os_strcmp(buf, "wpa_psk_radius") == 0) { + bss->wpa_psk_radius = atoi(pos); + if (bss->wpa_psk_radius != PSK_RADIUS_IGNORED && + bss->wpa_psk_radius != PSK_RADIUS_ACCEPTED && + bss->wpa_psk_radius != PSK_RADIUS_REQUIRED) { + wpa_printf(MSG_ERROR, "Line %d: unknown " + "wpa_psk_radius %d", + line, bss->wpa_psk_radius); + errors++; + } } else if (os_strcmp(buf, "wpa_pairwise") == 0) { bss->wpa_pairwise = hostapd_config_parse_cipher(line, pos); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 2a54518..5272d58 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -676,6 +676,7 @@ own_ip_addr=127.0.0.1 # Enable WPA. Setting this variable configures the AP to require WPA (either # WPA-PSK or WPA-RADIUS/EAP based on other configuration). For WPA-PSK, either # wpa_psk or wpa_passphrase must be set and wpa_key_mgmt must include WPA-PSK. +# Instead of wpa_psk / wpa_passphrase, wpa_psk_radius might suffice. # For WPA-RADIUS/EAP, ieee8021x must be set (but without dynamic WEP keys), # RADIUS authentication server must be configured, and WPA-EAP must be included # in wpa_key_mgmt. @@ -700,6 +701,15 @@ own_ip_addr=127.0.0.1 # configuration reloads. #wpa_psk_file=/etc/hostapd.wpa_psk +# Optionally, WPA passphrase can be received from RADIUS authentication server +# This requires macaddr_acl to be set to 2 (RADIUS) +# 0 = disabled (default) +# 1 = optional; use default passphrase/psk if RADIUS server does not include +# Tunnel-Password +# 2 = required; reject authentication if RADIUS server does not include +# Tunnel-Password +#wpa_psk_radius=0 + # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The # entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be # added to enable SHA256-based stronger algorithms. diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 637c929..cc7122c 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -219,6 +219,11 @@ struct hostapd_bss_config { /* dot11AssociationSAQueryRetryTimeout (in TUs) */ int assoc_sa_query_retry_timeout; #endif /* CONFIG_IEEE80211W */ + enum { + PSK_RADIUS_IGNORED = 0, + PSK_RADIUS_ACCEPTED = 1, + PSK_RADIUS_REQUIRED = 2 + } wpa_psk_radius; int wpa_pairwise; int wpa_group; int wpa_group_rekey; diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 0352599..617a035 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -313,6 +313,8 @@ static void handle_auth(struct hostapd_data *hapd, const u8 *challenge = NULL; u32 session_timeout, acct_interim_interval; int vlan_id = 0; + u8 psk[PMK_LEN]; + int has_psk = 0; u8 resp_ies[2 + WLAN_AUTH_CHALLENGE_LEN]; size_t resp_ies_len = 0; @@ -375,7 +377,9 @@ static void handle_auth(struct hostapd_data *hapd, res = hostapd_allowed_address(hapd, mgmt->sa, (u8 *) mgmt, len, &session_timeout, - &acct_interim_interval, &vlan_id); + &acct_interim_interval, &vlan_id, + psk, &has_psk); + if (res == HOSTAPD_ACL_REJECT) { printf("Station " MACSTR " not allowed to authenticate.\n", MAC2STR(mgmt->sa)); @@ -413,6 +417,16 @@ static void handle_auth(struct hostapd_data *hapd, HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id); } + if (has_psk && hapd->conf->wpa_psk_radius != PSK_RADIUS_IGNORED) { + os_free(sta->psk); + sta->psk = os_malloc(PMK_LEN); + if (sta->psk) + os_memcpy(sta->psk, psk, PMK_LEN); + } else { + os_free(sta->psk); + sta->psk = NULL; + } + sta->flags &= ~WLAN_STA_PREAUTH; ieee802_1x_notify_pre_auth(sta->eapol_sm, 0); diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c index 9b558fa..f3f313d 100644 --- a/src/ap/ieee802_11_auth.c +++ b/src/ap/ieee802_11_auth.c @@ -21,6 +21,7 @@ #include "utils/common.h" #include "utils/eloop.h" +#include "crypto/sha1.h" #include "radius/radius.h" #include "radius/radius_client.h" #include "hostapd.h" @@ -40,6 +41,8 @@ struct hostapd_cached_radius_acl { u32 session_timeout; u32 acct_interim_interval; int vlan_id; + int has_psk; + u8 psk[PMK_LEN]; }; @@ -68,7 +71,8 @@ static void hostapd_acl_cache_free(struct hostapd_cached_radius_acl *acl_cache) static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr, u32 *session_timeout, - u32 *acct_interim_interval, int *vlan_id) + u32 *acct_interim_interval, int *vlan_id, + u8 *psk, int *has_psk) { struct hostapd_cached_radius_acl *entry; struct os_time now; @@ -89,6 +93,10 @@ static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr, entry->acct_interim_interval; if (vlan_id) *vlan_id = entry->vlan_id; + if (psk) + os_memcpy(psk, entry->psk, PMK_LEN); + if (has_psk) + *has_psk = entry->has_psk; return entry->accepted; } @@ -210,11 +218,14 @@ static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr, * @session_timeout: Buffer for returning session timeout (from RADIUS) * @acct_interim_interval: Buffer for returning account interval (from RADIUS) * @vlan_id: Buffer for returning VLAN ID + * @psk: Buffer for returning WPA PSK + * @has_psk: Buffer for indicating whether psk was filled * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING */ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, const u8 *msg, size_t len, u32 *session_timeout, - u32 *acct_interim_interval, int *vlan_id) + u32 *acct_interim_interval, int *vlan_id, + u8 *psk, int *has_psk) { if (session_timeout) *session_timeout = 0; @@ -222,6 +233,10 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, *acct_interim_interval = 0; if (vlan_id) *vlan_id = 0; + if (has_psk) + *has_psk = 0; + if (psk) + os_memset(psk, 0, PMK_LEN); if (hostapd_maclist_found(hapd->conf->accept_mac, hapd->conf->num_accept_mac, addr, vlan_id)) @@ -246,7 +261,7 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, /* Check whether ACL cache has an entry for this station */ int res = hostapd_acl_cache_get(hapd, addr, session_timeout, acct_interim_interval, - vlan_id); + vlan_id, psk, has_psk); if (res == HOSTAPD_ACL_ACCEPT || res == HOSTAPD_ACL_ACCEPT_TIMEOUT) return res; @@ -438,6 +453,9 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req, cache->timestamp = t.sec; os_memcpy(cache->addr, query->addr, sizeof(cache->addr)); if (hdr->code == RADIUS_CODE_ACCESS_ACCEPT) { + int passphraselen; + char *passphrase; + if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_SESSION_TIMEOUT, &cache->session_timeout) == 0) cache->accepted = HOSTAPD_ACL_ACCEPT_TIMEOUT; @@ -456,6 +474,32 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req, } cache->vlan_id = radius_msg_get_vlanid(msg); + + passphrase = radius_msg_get_tunnel_password( + msg, &passphraselen, + hapd->conf->radius->auth_server->shared_secret, + hapd->conf->radius->auth_server->shared_secret_len, + req); + cache->has_psk = passphrase != NULL; + if (passphrase != NULL) { + /* passphrase does not contain the NULL termination. + * Add it here as pbkdf2_sha1 requires it. */ + char *strpassphrase = os_zalloc(passphraselen + 1); + if (strpassphrase) { + os_memcpy(strpassphrase, passphrase, + passphraselen); + pbkdf2_sha1(strpassphrase, + hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len, 4096, + cache->psk, PMK_LEN); + os_free(strpassphrase); + } + os_free(passphrase); + } + + if (hapd->conf->wpa_psk_radius == PSK_RADIUS_REQUIRED && + cache->psk == NULL) + cache->accepted = HOSTAPD_ACL_REJECT; } else cache->accepted = HOSTAPD_ACL_REJECT; cache->next = hapd->acl_cache; diff --git a/src/ap/ieee802_11_auth.h b/src/ap/ieee802_11_auth.h index b2971e5..a90571f 100644 --- a/src/ap/ieee802_11_auth.h +++ b/src/ap/ieee802_11_auth.h @@ -24,7 +24,8 @@ enum { int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, const u8 *msg, size_t len, u32 *session_timeout, - u32 *acct_interim_interval, int *vlan_id); + u32 *acct_interim_interval, int *vlan_id, + u8 *psk, int *has_psk); int hostapd_acl_init(struct hostapd_data *hapd); void hostapd_acl_deinit(struct hostapd_data *hapd); diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 61cb9f1..d9c348e 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -228,6 +228,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) wpabuf_free(sta->p2p_ie); os_free(sta->ht_capabilities); + os_free(sta->psk); os_free(sta); } diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 3ad00e2..4d3b5e9 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -98,6 +98,7 @@ struct sta_info { struct hostapd_ssid *ssid_probe; /* SSID selection based on ProbeReq */ int vlan_id; + u8 *psk; /* PSK from RADIUS authentication server */ struct ieee80211_ht_capabilities *ht_capabilities; diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 1e9d422..56bab23 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -186,6 +186,9 @@ static const u8 * hostapd_wpa_auth_get_psk(void *ctx, const u8 *addr, const u8 *prev_psk) { struct hostapd_data *hapd = ctx; + struct sta_info *sta = ap_get_sta(hapd, addr); + if (sta && sta->psk) + return sta->psk; return hostapd_get_psk(hapd->conf, addr, prev_psk); } diff --git a/src/radius/radius.c b/src/radius/radius.c index fb03a25..3ead847 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -218,6 +218,8 @@ static struct radius_attr_type radius_attrs[] = { RADIUS_ATTR_TUNNEL_TYPE, "Tunnel-Type", RADIUS_ATTR_HEXDUMP }, { RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, "Tunnel-Medium-Type", RADIUS_ATTR_HEXDUMP }, + { RADIUS_ATTR_TUNNEL_PASSWORD, "Tunnel-Password", + RADIUS_ATTR_UNDIST }, { RADIUS_ATTR_CONNECT_INFO, "Connect-Info", RADIUS_ATTR_TEXT }, { RADIUS_ATTR_EAP_MESSAGE, "EAP-Message", RADIUS_ATTR_UNDIST }, { RADIUS_ATTR_MESSAGE_AUTHENTICATOR, "Message-Authenticator", @@ -1275,6 +1277,120 @@ int radius_msg_get_vlanid(struct radius_msg *msg) } +/** + * radius_msg_get_tunnel_password - Parse RADIUS attribute Tunnel-Password + * @msg: Received RADIUS message + * @keylen: Length of returned password + * @secret: RADIUS shared secret + * @secret_len: Length of secret + * @sent_msg: Sent RADIUS message + * Returns: pointer to password (free with os_free) or %NULL + */ +char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen, + const u8 *secret, size_t secret_len, + struct radius_msg *sent_msg) +{ + u8 *buf = NULL; + size_t buflen; + const u8 *salt; + u8 *str; + const u8 *addr[3]; + size_t len[3]; + u8 hash[16]; + u8 *pos; + size_t i; + struct radius_attr_hdr *attr; + const u8 *data; + size_t dlen; + const u8 *fdata = NULL; /* points to found item */ + size_t fdlen = -1; + char *ret = NULL; + + /* find attribute with lowest tag and check it */ + for (i = 0; i < msg->attr_used; i++) { + attr = radius_get_attr_hdr(msg, i); + if (attr == NULL || + attr->type != RADIUS_ATTR_TUNNEL_PASSWORD) { + continue; + } + if (attr->length <= 5) + continue; + data = (const u8 *) (attr + 1); + dlen = attr->length - sizeof(*attr); + if (dlen <= 3 || dlen % 16 != 3) + continue; + if (fdata != NULL && fdata[0] <= data[0]) + continue; + + fdata = data; + fdlen = dlen; + } + if (fdata == NULL) + goto out; + + /* alloc writable memory for decryption */ + buf = os_malloc(fdlen); + if (buf == NULL) + goto out; + os_memcpy(buf, fdata, fdlen); + buflen = fdlen; + + /* init pointers */ + salt = buf + 1; + str = buf + 3; + + /* decrypt blocks */ + pos = buf + buflen - 16; /* last block */ + while (pos >= str + 16) { /* all but the first block */ + addr[0] = secret; + len[0] = secret_len; + addr[1] = pos - 16; + len[1] = 16; + md5_vector(2, addr, len, hash); + + for (i = 0; i < 16; i++) + pos[i] ^= hash[i]; + + pos -= 16; + } + + /* decrypt first block */ + if (str != pos) + goto out; + addr[0] = secret; + len[0] = secret_len; + addr[1] = sent_msg->hdr->authenticator; + len[1] = 16; + addr[2] = salt; + len[2] = 2; + md5_vector(3, addr, len, hash); + + for (i = 0; i < 16; i++) + pos[i] ^= hash[i]; + + /* derive plaintext length from first subfield */ + *keylen = (unsigned char) str[0]; + if ((u8 *) (str + *keylen) >= (u8 *) (buf + buflen)) { + /* decryption error - invalid key length */ + goto out; + } + if (*keylen == 0) { + /* empty password */ + goto out; + } + + /* copy passphrase into new buffer */ + ret = os_malloc(*keylen); + if (ret) + os_memcpy(ret, str + 1, *keylen); + +out: + /* return new buffer */ + os_free(buf); + return ret; +} + + void radius_free_class(struct radius_class_data *c) { size_t i; diff --git a/src/radius/radius.h b/src/radius/radius.h index a3cdac0..e69a047 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -82,6 +82,7 @@ enum { RADIUS_ATTR_USER_NAME = 1, RADIUS_ATTR_NAS_PORT_TYPE = 61, RADIUS_ATTR_TUNNEL_TYPE = 64, RADIUS_ATTR_TUNNEL_MEDIUM_TYPE = 65, + RADIUS_ATTR_TUNNEL_PASSWORD = 69, RADIUS_ATTR_CONNECT_INFO = 77, RADIUS_ATTR_EAP_MESSAGE = 79, RADIUS_ATTR_MESSAGE_AUTHENTICATOR = 80, @@ -231,6 +232,9 @@ radius_msg_add_attr_user_password(struct radius_msg *msg, const u8 *secret, size_t secret_len); int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len); int radius_msg_get_vlanid(struct radius_msg *msg); +char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen, + const u8 *secret, size_t secret_len, + struct radius_msg *sent_msg); static inline int radius_msg_add_attr_int32(struct radius_msg *msg, u8 type, u32 value)