Allow WPA passphrase to be fetched with RADIUS Tunnel-Password attribute
authorMichael Braun <michael-dev@fami-braun.de>
Sun, 11 Dec 2011 11:01:57 +0000 (13:01 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 11 Dec 2011 11:01:57 +0000 (13:01 +0200)
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 <michael-dev@fami-braun.de>

hostapd/config_file.c
hostapd/hostapd.conf
src/ap/ap_config.h
src/ap/ieee802_11.c
src/ap/ieee802_11_auth.c
src/ap/ieee802_11_auth.h
src/ap/sta_info.c
src/ap/sta_info.h
src/ap/wpa_auth_glue.c
src/radius/radius.c
src/radius/radius.h

index 9d39b48..ca79695 100644 (file)
@@ -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);
index 2a54518..5272d58 100644 (file)
@@ -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.
index 637c929..cc7122c 100644 (file)
@@ -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;
index 0352599..617a035 100644 (file)
@@ -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);
 
index 9b558fa..f3f313d 100644 (file)
@@ -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;
index b2971e5..a90571f 100644 (file)
@@ -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);
 
index 61cb9f1..d9c348e 100644 (file)
@@ -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);
 }
index 3ad00e2..4d3b5e9 100644 (file)
@@ -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;
 
index 1e9d422..56bab23 100644 (file)
@@ -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);
 }
 
index fb03a25..3ead847 100644 (file)
@@ -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;
index a3cdac0..e69a047 100644 (file)
@@ -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)