diff mbox

[RFC,PATCHv5] Use radius supplied PSK / Passphrase for WPA-PSK

Message ID 20111205173854.GA18482@dynamic.fami-braun.de
State Superseded
Headers show

Commit Message

michael-dev Dec. 5, 2011, 5:38 p.m. UTC
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.                                                           
I found RouterOS to have a feature like this (Miktronik-Wireless-PSK or so radius attribute) but no source and a hint on this mailing list                                                                     
that it should not be difficult to implement.                                                                                                                                                                  
Please find a patch against git head attached that compiles fine and is currently under testing.                                                                                                               
                                                                                                                                                                                                               
To use this, one needs to enable the macaddr_acl = RADIUS setting and have wpa_psk_radius=1.                                                                                                                   
For Freeradius, one needs to add                                                                                                                                                                               
 VENDOR          Hostapd        39014                                                                                                                                                                          
 ATTRIBUTE       Hostapd-PSK          1    integer             Hostapd                                                                                                                                         
 ATTRIBUTE       Hostapd-Passphrase          2    string             Hostapd                                                                                                                                   
to the dictionary file and make sure that either Hostapd-Passphrase or Hostapd-PSK (the latter has higher priority) is in the radius reply.                                                                    
The PSK should be supplied hex encoded, the passphrase is turned into a psk by hostapd.                                                                                                                        
The Vendor ID will be changed once assigned by IANA.                                                                                                                                                           
                                                                                                                                                                                                               
Regards,                                                                                                                                                                                                       
 M. Braun                                                                                                                                                                                                      
--                                                                                                                                                                                                             
Changes since v1:                                                                                                                                                                                              
 * sent wrong file, changes only apply to documentation part                                                                                                                                                   
Changes since v2:                                                                                                                                                                                              
 * use free enterprise number                                                                                                                                                                                  
Changes since v3:                                                                                                                                                                                              
 * fix typo, make it compile with openwrt  
Changes since v4:
 * tested on x86, fixed all occuring issues
diff mbox

Patch

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);
 # This can be used to reduce the size of the hostapd considerably if debugging
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 2a54518..f62f5ef 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -700,6 +702,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.c b/src/ap/ap_config.c
index cfb6b2d..bba436a 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -57,6 +57,8 @@  void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 	bss->broadcast_key_idx_max = 2;
 	bss->eap_reauth_period = 3600;
 
+	bss->wpa_psk_radius = 0;
+
 	bss->wpa_group_rekey = 600;
 	bss->wpa_gmk_rekey = 86400;
 	bss->wpa_key_mgmt = WPA_KEY_MGMT_PSK;
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..776cd5a 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;
 
@@ -373,9 +375,13 @@  static void handle_auth(struct hostapd_data *hapd,
 		goto fail;
 	}
 
+	u8* pskbuffer = os_zalloc(PMK_LEN);
 	res = hostapd_allowed_address(hapd, mgmt->sa, (u8 *) mgmt, len,
 				      &session_timeout,
-				      &acct_interim_interval, &vlan_id);
+				      &acct_interim_interval, &vlan_id, &pskbuffer, &has_psk);
+	os_memcpy(psk, pskbuffer, PMK_LEN);
+	os_free(pskbuffer); pskbuffer=0;
+
 	if (res == HOSTAPD_ACL_REJECT) {
 		printf("Station " MACSTR " not allowed to authenticate.\n",
 		       MAC2STR(mgmt->sa));
@@ -412,6 +418,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..7402dbd 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -40,6 +40,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 +70,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 +91,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 +190,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 +226,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 +234,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 +262,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;
@@ -396,6 +412,7 @@  hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
 			void *data)
 {
 	struct hostapd_data *hapd = data;
+        struct hostapd_ssid *ssid = &(hapd->conf->ssid);
 	struct hostapd_acl_query_data *query, *prev;
 	struct hostapd_cached_radius_acl *cache;
 	struct radius_hdr *hdr = radius_msg_get_hdr(msg);
@@ -456,6 +473,10 @@  hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
 		}
 
 		cache->vlan_id = radius_msg_get_vlanid(msg);
+                cache->has_psk = radius_msg_get_psk(msg, cache->psk, ssid);
+
+		if (hapd->conf->wpa_psk_radius == 1 && !cache->has_psk)
+			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..66932a0 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..99b0f98 100644
--- a/src/radius/radius.c
+++ b/src/radius/radius.c
@@ -17,9 +17,10 @@ 
 #include "utils/common.h"
 #include "utils/wpabuf.h"
 #include "crypto/md5.h"
+#include "crypto/sha1.h"
 #include "crypto/crypto.h"
 #include "radius.h"
-
+#include "ap/ap_config.h"
 
 /**
  * struct radius_msg - RADIUS message structure for new and parsed messages
@@ -173,6 +174,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 },
@@ -1275,6 +1277,48 @@  int radius_msg_get_vlanid(struct radius_msg *msg)
 }
 
 
+/**
+ * radius_msg_get_psk - Parse RADIUS attributes for PSK/Passphrase (WPA)
+ * @msg: RADIUS message
+ * @psk: buffer for PSK (len: PMK_LEN)
+ * Returns: 1 if psk found, 0 else
+ */
+int radius_msg_get_psk(struct radius_msg *msg, u8* psk, struct hostapd_ssid *ssid)
+{
+	char* key;
+	char* strkey = 0;
+        size_t keylen;
+
+        key = (char*) radius_msg_get_vendor_attr( msg, RADIUS_VENDOR_ID_HOSTAPD, RADIUS_VENDOR_ATTR_HAP_PSK, &keylen );
+        if (key) {
+		// PSK found, expect hex encoding
+                strkey = os_zalloc(keylen+1);
+		os_memcpy(strkey,key,keylen); 
+                int len = os_strlen(strkey);
+                if (len == 64 && hexstr2bin(strkey, psk, PMK_LEN) == 0) {
+			os_free(key); key=NULL;
+			os_free(strkey); strkey=NULL;
+                	return 1;
+                }
+		// PSK had wrong length...
+		os_free(key); key=NULL;
+		os_free(strkey); strkey=NULL;
+	}
+
+        key = (char*) radius_msg_get_vendor_attr(msg, RADIUS_VENDOR_ID_HOSTAPD, RADIUS_VENDOR_ATTR_HAP_PASSPHRASE, &keylen );
+        if (key) {
+                strkey = os_zalloc(keylen+1);
+		os_memcpy(strkey,key,keylen); 
+		// Passphrase found
+		pbkdf2_sha1(strkey, ssid->ssid, ssid->ssid_len, 4096, psk, PMK_LEN);
+		os_free(key); key=NULL;
+		os_free(strkey); strkey=NULL;
+		return 1;
+	} 
+	return 0;
+}
+
+
 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..bc1980b 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,
@@ -145,6 +148,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;
@@ -172,6 +188,11 @@  struct radius_ms_mppe_keys {
 	size_t recv_len;
 };
 
+/* hostapd specific RADIUS Attributes */
+#define RADIUS_VENDOR_ID_HOSTAPD 39014
+enum {  RADIUS_VENDOR_ATTR_HAP_PSK = 1,
+        RADIUS_VENDOR_ATTR_HAP_PASSPHRASE = 2
+};
 
 struct radius_msg;
 
@@ -231,6 +252,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);
+int radius_msg_get_psk(struct radius_msg *msg, u8* psk, struct hostapd_ssid *ssid);
 
 static inline int radius_msg_add_attr_int32(struct radius_msg *msg, u8 type,
 					    u32 value)