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:
parent
79fec6a92d
commit
7bd8c76a4f
8 changed files with 212 additions and 0 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue