From patchwork Tue Dec 6 17:21:35 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [RFC,PATCHv8] Use radius supplied Passphrase for WPA-PSK From: michael-dev@fami-braun.de X-Patchwork-Id: 129750 Message-Id: <20111206172135.GA11480@dynamic.fami-braun.de> To: hostap@lists.shmoo.com Cc: rei@fem.tu-ilmenau.de Date: Tue, 6 Dec 2011 18:21:35 +0100 Hi, I wanted to use the per-device-PSK (WPA) feature in conjunction with a radius server that does the authorization checking and should supply the psk. This patch addresses the previous comments by - using Tunnel-Password instead of a plaintext, hostapd specific radius vendor attribute, - moving the psk generation out of radius.c, - removing the superflous = 0 assignment, - removing direkt psk supply for simplicity. Please find a patch against git head attached that compiles fine and has been tested on x86. To use this, one needs to enable the macaddr_acl=2 setting and have wpa_psk_radius=1 in hostapd.conf. The radius server then just needs to supply the Tunnel-Password attribute on the access requests. The Service-Type radius attribute is used to easily differentiate between PSK reqests and EAP requests. Regards, M. Braun commit 04460f68f9c567453ed392dc7e4b3eb2bcfb5cc6 Author: Michael Braun Date: Tue Dec 6 17:41:23 2011 +0100 Enabled hostapd to fetch WPA Passphrase using RADIUS Tunnel-Password attribute. Signed-hostap: Michael Braun diff --git a/hostapd/README b/hostapd/README index a211cdd..a94bc2a 100644 --- a/hostapd/README +++ b/hostapd/README @@ -350,6 +350,11 @@ TODO #wpa_psk=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef #wpa_passphrase=secret passphrase +# Optionally, WPA PSKs can be read from RADIUS (to be used with macaddr_acl set to RADIUS) +# 0 no +# 1 may (falls back to the above if no radius attribute is found) +#wpa_psk_radius=0 + # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The # entries are separated with a space. #wpa_key_mgmt=WPA-PSK WPA-EAP diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 5eb7b49..7e226c6 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1052,7 +1052,7 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, 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 == 0) { wpa_printf(MSG_ERROR, "WPA-PSK enabled, but PSK or passphrase " "is not configured."); return -1; @@ -1629,6 +1629,8 @@ 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); } 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..3af8ac6 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -700,6 +700,11 @@ own_ip_addr=127.0.0.1 # configuration reloads. #wpa_psk_file=/etc/hostapd.wpa_psk +# Optionally, WPA PSKs can be read from RADIUS (to be used with macaddr_acl set to RADIUS) +# 0 no +# 1 may (falls back to the above if no radius attribute is found) +wpa_psk_radius=1 + # 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 2b201df..c1b6c73 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -219,6 +219,7 @@ struct hostapd_bss_config { /* dot11AssociationSAQueryRetryTimeout (in TUs) */ int assoc_sa_query_retry_timeout; #endif /* CONFIG_IEEE80211W */ + int 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..ade5c00 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,8 @@ 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)); @@ -412,6 +415,12 @@ static void handle_auth(struct hostapd_data *hapd, hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id); } + if (has_psk && hapd->conf->wpa_psk_radius == 1) { + sta->psk = os_zalloc(PMK_LEN); + os_memcpy(sta->psk, psk, PMK_LEN); + } else { + sta->psk = 0; + } 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..f5672fb 100644 --- a/src/ap/ieee802_11_auth.c +++ b/src/ap/ieee802_11_auth.c @@ -28,6 +28,7 @@ #include "ap_drv_ops.h" #include "ieee802_11.h" #include "ieee802_11_auth.h" +#include "crypto/sha1.h" #define RADIUS_ACL_TIMEOUT 30 @@ -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,7 @@ 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 +92,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; } @@ -184,6 +191,12 @@ static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr, goto fail; } + if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_SERVICE_TYPE, + RADIUS_SERVICE_TYPE_OUTBOUND)) { + wpa_printf(MSG_DEBUG, "Could not add Service-Type"); + goto fail; + } + os_snprintf(buf, sizeof(buf), "CONNECT 11Mbps 802.11b"); if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO, (u8 *) buf, os_strlen(buf))) { @@ -214,7 +227,7 @@ static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr, */ 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 +235,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 +263,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; @@ -456,6 +473,21 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req, } cache->vlan_id = radius_msg_get_vlanid(msg); + + int passphraselen; + char* 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); + os_memcpy(strpassphrase, passphrase, passphraselen); + pbkdf2_sha1(strpassphrase, hapd->conf->ssid.ssid, hapd->conf->ssid.ssid_len, 4096, cache->psk, PMK_LEN); + os_free(passphrase); passphrase = NULL; + os_free(strpassphrase); strpassphrase = NULL; + } + + if (hapd->conf->wpa_psk_radius == 1 && 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..ee451f9 100644 --- a/src/ap/ieee802_11_auth.h +++ b/src/ap/ieee802_11_auth.h @@ -24,7 +24,7 @@ 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/ieee802_1x.c b/src/ap/ieee802_1x.c index 18ee747..45509b4 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -509,6 +509,12 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd, goto fail; } + if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_SERVICE_TYPE, + RADIUS_SERVICE_TYPE_FRAMED)) { + printf("Could not add Service-Type\n"); + goto fail; + } + if (sta->flags & WLAN_STA_PREAUTH) { os_strlcpy(buf, "IEEE 802.11i Pre-Authentication", sizeof(buf)); diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index a9981cc..bcea28e 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..7514af6 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; struct ieee80211_ht_capabilities *ht_capabilities; diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index ce2751e..9af82dd 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -132,6 +132,7 @@ struct wpa_auth_config { int wpa; int wpa_key_mgmt; int wpa_pairwise; + int wpa_psk_radius; int wpa_group; int wpa_group_rekey; int wpa_strict_rekey; diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 1e9d422..7ef0c07 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -39,6 +39,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, wconf->wpa = conf->wpa; wconf->wpa_key_mgmt = conf->wpa_key_mgmt; wconf->wpa_pairwise = conf->wpa_pairwise; + wconf->wpa_psk_radius = conf->wpa_psk_radius; wconf->wpa_group = conf->wpa_group; wconf->wpa_group_rekey = conf->wpa_group_rekey; wconf->wpa_strict_rekey = conf->wpa_strict_rekey; @@ -186,7 +187,10 @@ static const u8 * hostapd_wpa_auth_get_psk(void *ctx, const u8 *addr, const u8 *prev_psk) { struct hostapd_data *hapd = ctx; - return hostapd_get_psk(hapd->conf, addr, prev_psk); + struct sta_info *sta = ap_get_sta(hapd, addr); + if (sta == NULL || sta->psk == NULL) + return hostapd_get_psk(hapd->conf, addr, prev_psk); + return sta->psk; } diff --git a/src/radius/radius.c b/src/radius/radius.c index fb03a25..5f4f03e 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -173,6 +173,7 @@ static struct radius_attr_type radius_attrs[] = { RADIUS_ATTR_USER_PASSWORD, "User-Password", RADIUS_ATTR_UNDIST }, { RADIUS_ATTR_NAS_IP_ADDRESS, "NAS-IP-Address", RADIUS_ATTR_IP }, { RADIUS_ATTR_NAS_PORT, "NAS-Port", RADIUS_ATTR_INT32 }, + { RADIUS_ATTR_SERVICE_TYPE, "Service-Type", RADIUS_ATTR_INT32 }, { RADIUS_ATTR_FRAMED_MTU, "Framed-MTU", RADIUS_ATTR_INT32 }, { RADIUS_ATTR_REPLY_MESSAGE, "Reply-Message", RADIUS_ATTR_TEXT }, { RADIUS_ATTR_STATE, "State", RADIUS_ATTR_UNDIST }, @@ -218,6 +219,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 +1278,92 @@ int radius_msg_get_vlanid(struct radius_msg *msg) } +/** + * radius_msg_get_tunnel_password - Parse RADIUS attribute Tunnel-Password + * @msg: RADIUS message + * @passwordlen: length of returned password + * 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) +{ + /* generic radius fields */ + u8 *buf; + unsigned int buflen; + + /* fields of tunnel-password */ + u8 *tag; + u8 *salt; + u8* str; + + /* helper vars for md5 */ + const u8 *addr[3]; + size_t len[3]; + u8 hash[16]; + /* helper var for iteration blocks */ + u8* pos; + int i; + + /* check packet */ + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_TUNNEL_PASSWORD, &buf, &buflen, NULL) < 0) return NULL; + if ((buflen <= 3) || (buflen % 16 != 3)) { + return NULL; + } + + /* init pointers */ + tag = buf; + 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) { + return NULL; + } + 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 + return NULL; + } + if (*keylen == 0) { + // empty password + return NULL; + } + + /* copy passphrase into new buffer */ + char* ret = os_zalloc(*keylen); + os_memcpy(ret, str + 1, *keylen); + + /* return new buffer */ + 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..464eb1d 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -15,6 +15,8 @@ #ifndef RADIUS_H #define RADIUS_H +struct hostapd_ssid; + /* RFC 2865 - RADIUS */ #ifdef _MSC_VER @@ -52,6 +54,7 @@ enum { RADIUS_ATTR_USER_NAME = 1, RADIUS_ATTR_USER_PASSWORD = 2, RADIUS_ATTR_NAS_IP_ADDRESS = 4, RADIUS_ATTR_NAS_PORT = 5, + RADIUS_ATTR_SERVICE_TYPE = 6, RADIUS_ATTR_FRAMED_MTU = 12, RADIUS_ATTR_REPLY_MESSAGE = 18, RADIUS_ATTR_STATE = 24, @@ -82,6 +85,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, @@ -145,6 +149,19 @@ enum { RADIUS_ATTR_USER_NAME = 1, #define RADIUS_TUNNEL_MEDIUM_TYPE_IPV6 2 #define RADIUS_TUNNEL_MEDIUM_TYPE_802 6 +/* Service-Type */ +#define RADIUS_SERVICE_TYPE_LOGIN 1 +#define RADIUS_SERVICE_TYPE_FRAMED 2 +#define RADIUS_SERVICE_TYPE_CALLBACK_LOGIN 3 +#define RADIUS_SERVICE_TYPE_CALLBACK_FRAMED 4 +#define RADIUS_SERVICE_TYPE_OUTBOUND 5 +#define RADIUS_SERVICE_TYPE_ADMINISTRATIVE 6 +#define RADIUS_SERVICE_TYPE_NAS_PROMPT 7 +#define RADIUS_SERVICE_TYPE_AUTHENTICATE_ONLY 8 +#define RADIUS_SERVICE_TYPE_CALLBACK_NAS_PROMPT 9 +#define RADIUS_SERVICE_TYPE_CALL_CHECK 10 +#define RADIUS_SERVICE_TYPE_CALLBACK ADMINISTRATIVE 11 + struct radius_attr_vendor { u8 vendor_type; @@ -231,6 +248,7 @@ 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)