HS 2.0 server: RADIUS server support for SIM provisioning

This adds support for hostapd-as-RADIUS-authentication-server to request
subscription remediation for SIM-based credentials. The new hostapd.conf
parameter hs20_sim_provisioning_url is used to set the URL prefix for
the remediation server for SIM provisioning. The random
hotspot2dot0-mobile-identifier-hash value will be added to the end of
this URL prefix and the same value is stored in a new SQLite database
table sim_provisioning for the subscription server implementation to
use.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
Jouni Malinen 2018-12-14 15:58:13 +02:00 committed by Jouni Malinen
parent 79fec6a92d
commit 7bd8c76a4f
8 changed files with 212 additions and 0 deletions

View file

@ -3817,6 +3817,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
} else if (os_strcmp(buf, "hs20_t_c_server_url") == 0) { } else if (os_strcmp(buf, "hs20_t_c_server_url") == 0) {
os_free(bss->t_c_server_url); os_free(bss->t_c_server_url);
bss->t_c_server_url = os_strdup(pos); bss->t_c_server_url = os_strdup(pos);
} else if (os_strcmp(buf, "hs20_sim_provisioning_url") == 0) {
os_free(bss->hs20_sim_provisioning_url);
bss->hs20_sim_provisioning_url = os_strdup(pos);
#endif /* CONFIG_HS20 */ #endif /* CONFIG_HS20 */
#ifdef CONFIG_MBO #ifdef CONFIG_MBO
} else if (os_strcmp(buf, "mbo") == 0) { } else if (os_strcmp(buf, "mbo") == 0) {

View file

@ -93,3 +93,11 @@ CREATE TABLE cert_enroll(
realm TEXT, realm TEXT,
serialnum TEXT serialnum TEXT
); );
CREATE TABLE sim_provisioning(
mobile_identifier_hash TEXT PRIMARY KEY,
imsi TEXT,
mac_addr TEXT,
eap_method TEXT,
timestamp TEXT
);

View file

@ -649,6 +649,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
os_free(conf->hs20_operator_icon); os_free(conf->hs20_operator_icon);
} }
os_free(conf->subscr_remediation_url); os_free(conf->subscr_remediation_url);
os_free(conf->hs20_sim_provisioning_url);
os_free(conf->t_c_filename); os_free(conf->t_c_filename);
os_free(conf->t_c_server_url); os_free(conf->t_c_server_url);
#endif /* CONFIG_HS20 */ #endif /* CONFIG_HS20 */

View file

@ -597,6 +597,7 @@ struct hostapd_bss_config {
unsigned int hs20_deauth_req_timeout; unsigned int hs20_deauth_req_timeout;
char *subscr_remediation_url; char *subscr_remediation_url;
u8 subscr_remediation_method; u8 subscr_remediation_method;
char *hs20_sim_provisioning_url;
char *t_c_filename; char *t_c_filename;
u32 t_c_timestamp; u32 t_c_timestamp;
char *t_c_server_url; char *t_c_server_url;

View file

@ -136,6 +136,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd)
#ifdef CONFIG_HS20 #ifdef CONFIG_HS20
srv.subscr_remediation_url = conf->subscr_remediation_url; srv.subscr_remediation_url = conf->subscr_remediation_url;
srv.subscr_remediation_method = conf->subscr_remediation_method; srv.subscr_remediation_method = conf->subscr_remediation_method;
srv.hs20_sim_provisioning_url = conf->hs20_sim_provisioning_url;
srv.t_c_server_url = conf->t_c_server_url; srv.t_c_server_url = conf->t_c_server_url;
#endif /* CONFIG_HS20 */ #endif /* CONFIG_HS20 */
srv.erp = conf->eap_server_erp; srv.erp = conf->eap_server_erp;

View file

@ -174,6 +174,7 @@ eap_user_sqlite_get(struct hostapd_data *hapd, const u8 *identity,
if (hapd->tmp_eap_user.identity == NULL) if (hapd->tmp_eap_user.identity == NULL)
return NULL; return NULL;
os_memcpy(hapd->tmp_eap_user.identity, identity, identity_len); os_memcpy(hapd->tmp_eap_user.identity, identity, identity_len);
hapd->tmp_eap_user.identity_len = identity_len;
if (sqlite3_open(hapd->conf->eap_user_sqlite, &db)) { if (sqlite3_open(hapd->conf->eap_user_sqlite, &db)) {
wpa_printf(MSG_INFO, "DB: Failed to open database %s: %s", wpa_printf(MSG_INFO, "DB: Failed to open database %s: %s",

View file

@ -357,6 +357,7 @@ struct radius_server_data {
char *subscr_remediation_url; char *subscr_remediation_url;
u8 subscr_remediation_method; u8 subscr_remediation_method;
char *hs20_sim_provisioning_url;
char *t_c_server_url; char *t_c_server_url;
@ -380,6 +381,44 @@ static void radius_server_session_timeout(void *eloop_ctx, void *timeout_ctx);
static void radius_server_session_remove_timeout(void *eloop_ctx, static void radius_server_session_remove_timeout(void *eloop_ctx,
void *timeout_ctx); void *timeout_ctx);
#ifdef CONFIG_SQLITE
#ifdef CONFIG_HS20
static int db_table_exists(sqlite3 *db, const char *name)
{
char cmd[128];
os_snprintf(cmd, sizeof(cmd), "SELECT 1 FROM %s;", name);
return sqlite3_exec(db, cmd, NULL, NULL, NULL) == SQLITE_OK;
}
static int db_table_create_sim_provisioning(sqlite3 *db)
{
char *err = NULL;
const char *sql =
"CREATE TABLE sim_provisioning("
" mobile_identifier_hash TEXT PRIMARY KEY,"
" imsi TEXT,"
" mac_addr TEXT,"
" eap_method TEXT,"
" timestamp TEXT"
");";
RADIUS_DEBUG("Adding database table for SIM provisioning information");
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
RADIUS_ERROR("SQLite error: %s", err);
sqlite3_free(err);
return -1;
}
return 0;
}
#endif /* CONFIG_HS20 */
#endif /* CONFIG_SQLITE */
void srv_log(struct radius_session *sess, const char *fmt, ...) void srv_log(struct radius_session *sess, const char *fmt, ...)
PRINTF_FORMAT(2, 3); PRINTF_FORMAT(2, 3);
@ -866,6 +905,117 @@ static void db_update_last_msk(struct radius_session *sess, const char *msk)
} }
#ifdef CONFIG_HS20
static int radius_server_is_sim_method(struct radius_session *sess)
{
const char *name;
name = eap_get_method(sess->eap);
return name &&
(os_strcmp(name, "SIM") == 0 ||
os_strcmp(name, "AKA") == 0 ||
os_strcmp(name, "AKA'") == 0);
}
static int radius_server_hs20_missing_sim_pps(struct radius_msg *request)
{
u8 *buf, *pos, *end, type, sublen;
size_t len;
buf = NULL;
for (;;) {
if (radius_msg_get_attr_ptr(request,
RADIUS_ATTR_VENDOR_SPECIFIC,
&buf, &len, buf) < 0)
return 0;
if (len < 6)
continue;
pos = buf;
end = buf + len;
if (WPA_GET_BE32(pos) != RADIUS_VENDOR_ID_WFA)
continue;
pos += 4;
type = *pos++;
sublen = *pos++;
if (sublen < 2)
continue; /* invalid length */
sublen -= 2; /* skip header */
if (pos + sublen > end)
continue; /* invalid WFA VSA */
if (type != RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION)
continue;
RADIUS_DUMP("HS2.0 mobile device version", pos, sublen);
if (sublen < 1 + 2)
continue;
if (pos[0] == 0)
continue; /* Release 1 STA does not support provisioning
*/
/* UpdateIdentifier 0 indicates no PPS MO */
return WPA_GET_BE16(pos + 1) == 0;
}
}
#define HS20_MOBILE_ID_HASH_LEN 16
static int radius_server_sim_provisioning_session(struct radius_session *sess,
const u8 *hash)
{
#ifdef CONFIG_SQLITE
char *sql;
char addr_txt[ETH_ALEN * 3];
char hash_txt[2 * HS20_MOBILE_ID_HASH_LEN + 1];
struct os_time now;
int res;
const char *imsi, *eap_method;
if (!sess->server->db ||
(!db_table_exists(sess->server->db, "sim_provisioning") &&
db_table_create_sim_provisioning(sess->server->db) < 0))
return -1;
imsi = eap_get_imsi(sess->eap);
if (!imsi)
return -1;
eap_method = eap_get_method(sess->eap);
if (!eap_method)
return -1;
os_snprintf(addr_txt, sizeof(addr_txt), MACSTR,
MAC2STR(sess->mac_addr));
wpa_snprintf_hex(hash_txt, sizeof(hash_txt), hash,
HS20_MOBILE_ID_HASH_LEN);
os_get_time(&now);
sql = sqlite3_mprintf("INSERT INTO sim_provisioning(mobile_identifier_hash,imsi,mac_addr,eap_method,timestamp) VALUES (%Q,%Q,%Q,%Q,%u)",
hash_txt, imsi, addr_txt, eap_method, now.sec);
if (!sql)
return -1;
if (sqlite3_exec(sess->server->db, sql, NULL, NULL, NULL) !=
SQLITE_OK) {
RADIUS_ERROR("Failed to add SIM provisioning entry into sqlite database: %s",
sqlite3_errmsg(sess->server->db));
res = -1;
} else {
res = 0;
}
sqlite3_free(sql);
return res;
#endif /* CONFIG_SQLITE */
return -1;
}
#endif /* CONFIG_HS20 */
static struct radius_msg * static struct radius_msg *
radius_server_encapsulate_eap(struct radius_server_data *data, radius_server_encapsulate_eap(struct radius_server_data *data,
struct radius_client *client, struct radius_client *client,
@ -979,6 +1129,48 @@ radius_server_encapsulate_eap(struct radius_server_data *data,
buf, 0)) { buf, 0)) {
RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem");
} }
} else if (code == RADIUS_CODE_ACCESS_ACCEPT &&
data->hs20_sim_provisioning_url &&
radius_server_is_sim_method(sess) &&
radius_server_hs20_missing_sim_pps(request)) {
u8 *buf, *pos, hash[HS20_MOBILE_ID_HASH_LEN];
size_t prefix_len, url_len;
RADIUS_DEBUG("Device needs HS 2.0 SIM provisioning");
if (os_get_random(hash, HS20_MOBILE_ID_HASH_LEN) < 0) {
radius_msg_free(msg);
return NULL;
}
RADIUS_DUMP("hotspot2dot0-mobile-identifier-hash",
hash, HS20_MOBILE_ID_HASH_LEN);
if (radius_server_sim_provisioning_session(sess, hash) < 0) {
radius_msg_free(msg);
return NULL;
}
prefix_len = os_strlen(data->hs20_sim_provisioning_url);
url_len = prefix_len + 2 * HS20_MOBILE_ID_HASH_LEN;
buf = os_malloc(1 + url_len + 1);
if (!buf) {
radius_msg_free(msg);
return NULL;
}
pos = buf;
*pos++ = data->subscr_remediation_method;
os_memcpy(pos, data->hs20_sim_provisioning_url, prefix_len);
pos += prefix_len;
wpa_snprintf_hex((char *) pos, 2 * HS20_MOBILE_ID_HASH_LEN + 1,
hash, HS20_MOBILE_ID_HASH_LEN);
RADIUS_DEBUG("HS 2.0 subscription remediation URL: %s",
(char *) &buf[1]);
if (!radius_msg_add_wfa(
msg, RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION,
buf, 1 + url_len)) {
RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem");
}
os_free(buf);
} }
if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->t_c_filtering) { if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->t_c_filtering) {
@ -2173,6 +2365,9 @@ radius_server_init(struct radius_server_conf *conf)
os_strdup(conf->subscr_remediation_url); os_strdup(conf->subscr_remediation_url);
} }
data->subscr_remediation_method = conf->subscr_remediation_method; data->subscr_remediation_method = conf->subscr_remediation_method;
if (conf->hs20_sim_provisioning_url)
data->hs20_sim_provisioning_url =
os_strdup(conf->hs20_sim_provisioning_url);
if (conf->t_c_server_url) if (conf->t_c_server_url)
data->t_c_server_url = os_strdup(conf->t_c_server_url); data->t_c_server_url = os_strdup(conf->t_c_server_url);
@ -2293,6 +2488,7 @@ void radius_server_deinit(struct radius_server_data *data)
os_free(data->dump_msk_file); os_free(data->dump_msk_file);
#endif /* CONFIG_RADIUS_TEST */ #endif /* CONFIG_RADIUS_TEST */
os_free(data->subscr_remediation_url); os_free(data->subscr_remediation_url);
os_free(data->hs20_sim_provisioning_url);
os_free(data->t_c_server_url); os_free(data->t_c_server_url);
#ifdef CONFIG_SQLITE #ifdef CONFIG_SQLITE

View file

@ -233,6 +233,7 @@ struct radius_server_conf {
char *subscr_remediation_url; char *subscr_remediation_url;
u8 subscr_remediation_method; u8 subscr_remediation_method;
char *hs20_sim_provisioning_url;
char *t_c_server_url; char *t_c_server_url;
}; };