HS 2.0 server: SIM provisioning exchange

Support SIM provisioning exchange with SPP. This uses the
hotspot2dot0-mobile-identifier-hash value from the AAA server to allow
subscription registration through subscription remediation exchange.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
Jouni Malinen 2018-12-15 18:00:12 +02:00 committed by Jouni Malinen
parent 4992582187
commit 89ae35833b
5 changed files with 264 additions and 6 deletions

View File

@ -89,6 +89,18 @@ static int process(struct hs20_svc *ctx)
return -1; return -1;
} }
ctx->imsi = getenv("HS20IMSI");
if (ctx->imsi)
debug_print(ctx, 1, "IMSI %s", ctx->imsi);
ctx->eap_method = getenv("HS20EAPMETHOD");
if (ctx->eap_method)
debug_print(ctx, 1, "EAP method %s", ctx->eap_method);
ctx->id_hash = getenv("HS20IDHASH");
if (ctx->id_hash)
debug_print(ctx, 1, "ID-HASH %s", ctx->id_hash);
soap = xml_node_from_buf(ctx->xml, post); soap = xml_node_from_buf(ctx->xml, post);
if (soap == NULL) { if (soap == NULL) {
debug_print(ctx, 1, "Could not parse SOAP data"); debug_print(ctx, 1, "Could not parse SOAP data");

View File

@ -211,6 +211,61 @@ static void db_add_session_devdetail(struct hs20_svc *ctx,
} }
static void db_add_session_dmacc(struct hs20_svc *ctx, const char *sessionid,
const char *username, const char *password)
{
char *sql;
sql = sqlite3_mprintf("UPDATE sessions SET osu_user=%Q, osu_password=%Q WHERE id=%Q",
username, password, sessionid);
if (!sql)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session DMAcc: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_add_session_eap_method(struct hs20_svc *ctx,
const char *sessionid,
const char *method)
{
char *sql;
sql = sqlite3_mprintf("UPDATE sessions SET eap_method=%Q WHERE id=%Q",
method, sessionid);
if (!sql)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session EAP method: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_add_session_id_hash(struct hs20_svc *ctx, const char *sessionid,
const char *id_hash)
{
char *sql;
sql = sqlite3_mprintf("UPDATE sessions SET mobile_identifier_hash=%Q WHERE id=%Q",
id_hash, sessionid);
if (!sql)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session ID hash: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_remove_session(struct hs20_svc *ctx, static void db_remove_session(struct hs20_svc *ctx,
const char *user, const char *realm, const char *user, const char *realm,
const char *sessionid) const char *sessionid)
@ -570,6 +625,7 @@ static xml_node_t * build_username_password(struct hs20_svc *ctx,
{ {
xml_node_t *node; xml_node_t *node;
char *b64; char *b64;
size_t len;
node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword"); node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword");
if (node == NULL) if (node == NULL)
@ -580,6 +636,9 @@ static xml_node_t * build_username_password(struct hs20_svc *ctx,
b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL); b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL);
if (b64 == NULL) if (b64 == NULL)
return NULL; return NULL;
len = os_strlen(b64);
if (len > 0 && b64[len - 1] == '\n')
b64[len - 1] = '\0';
add_text_node(ctx, node, "Password", b64); add_text_node(ctx, node, "Password", b64);
free(b64); free(b64);
@ -1309,7 +1368,9 @@ static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm,
static xml_node_t * build_pps(struct hs20_svc *ctx, static xml_node_t * build_pps(struct hs20_svc *ctx,
const char *user, const char *realm, const char *user, const char *realm,
const char *pw, const char *cert, const char *pw, const char *cert,
int machine_managed, const char *test) int machine_managed, const char *test,
const char *imsi, const char *dmacc_username,
const char *dmacc_password)
{ {
xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp, *p; xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp, *p;
xml_node_t *cred, *eap, *userpw; xml_node_t *cred, *eap, *userpw;
@ -1325,6 +1386,8 @@ static xml_node_t * build_pps(struct hs20_svc *ctx,
add_text_node(ctx, c, "CredentialPriority", "1"); add_text_node(ctx, c, "CredentialPriority", "1");
if (imsi)
goto skip_aaa_trust_root;
aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot"); aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot");
aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1"); aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1");
add_text_node_conf(ctx, realm, aaa1, "CertURL", add_text_node_conf(ctx, realm, aaa1, "CertURL",
@ -1356,6 +1419,7 @@ static xml_node_t * build_pps(struct hs20_svc *ctx,
"CertSHA256Fingerprint", "CertSHA256Fingerprint",
"policy_trust_root_cert_fingerprint"); "policy_trust_root_cert_fingerprint");
} }
skip_aaa_trust_root:
upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate"); upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate");
add_text_node(ctx, upd, "UpdateInterval", "4294967295"); add_text_node(ctx, upd, "UpdateInterval", "4294967295");
@ -1375,6 +1439,13 @@ static xml_node_t * build_pps(struct hs20_svc *ctx,
"trust_root_cert_fingerprint"); "trust_root_cert_fingerprint");
} }
if (dmacc_username &&
!build_username_password(ctx, upd, dmacc_username,
dmacc_password)) {
xml_node_free(ctx->xml, pps);
return NULL;
}
homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP"); homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP");
add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name"); add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name");
add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn"); add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn");
@ -1383,7 +1454,19 @@ static xml_node_t * build_pps(struct hs20_svc *ctx,
cred = xml_node_create(ctx->xml, c, NULL, "Credential"); cred = xml_node_create(ctx->xml, c, NULL, "Credential");
add_creation_date(ctx, cred); add_creation_date(ctx, cred);
if (cert) { if (imsi) {
xml_node_t *sim;
const char *type = "18"; /* default to EAP-SIM */
sim = xml_node_create(ctx->xml, cred, NULL, "SIM");
add_text_node(ctx, sim, "IMSI", imsi);
if (ctx->eap_method && os_strcmp(ctx->eap_method, "AKA") == 0)
type = "23";
else if (ctx->eap_method &&
os_strcmp(ctx->eap_method, "AKA'") == 0)
type = "50";
add_text_node(ctx, sim, "EAPType", type);
} else if (cert) {
xml_node_t *dc; xml_node_t *dc;
dc = xml_node_create(ctx->xml, cred, NULL, dc = xml_node_create(ctx->xml, cred, NULL,
"DigitalCertificate"); "DigitalCertificate");
@ -1527,7 +1610,7 @@ static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx,
test); test);
pps = build_pps(ctx, user, realm, pw, pps = build_pps(ctx, user, realm, pw,
fingerprint ? fingerprint : NULL, machine_managed, fingerprint ? fingerprint : NULL, machine_managed,
test); test, NULL, NULL, NULL);
free(fingerprint); free(fingerprint);
free(test); free(test);
if (!pps) { if (!pps) {
@ -1802,6 +1885,85 @@ static xml_node_t * hs20_cert_enroll_failed(struct hs20_svc *ctx,
} }
static xml_node_t * hs20_sim_provisioning(struct hs20_svc *ctx,
const char *user,
const char *realm, int dmacc,
const char *session_id)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *node = NULL;
xml_node_t *pps, *tnds;
char buf[400];
char *str;
const char *status;
char dmacc_username[32];
char dmacc_password[32];
if (!ctx->imsi) {
debug_print(ctx, 1, "IMSI not available for SIM provisioning");
return NULL;
}
if (new_password(dmacc_username, sizeof(dmacc_username)) < 0 ||
new_password(dmacc_password, sizeof(dmacc_password)) < 0) {
debug_print(ctx, 1,
"Failed to generate DMAcc username/password");
return NULL;
}
status = "Provisioning complete, request sppUpdateResponse";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (!spp_node)
return NULL;
pps = build_pps(ctx, NULL, realm, NULL, NULL, 0, NULL, ctx->imsi,
dmacc_username, dmacc_password);
if (!pps) {
xml_node_free(ctx->xml, spp_node);
return NULL;
}
debug_print(ctx, 1,
"Request DB subscription registration on success notification");
if (!user || !user[0])
user = ctx->imsi;
db_add_session(ctx, user, realm, session_id, NULL, NULL,
SUBSCRIPTION_REGISTRATION, NULL);
db_add_session_dmacc(ctx, session_id, dmacc_username, dmacc_password);
if (ctx->eap_method)
db_add_session_eap_method(ctx, session_id, ctx->eap_method);
if (ctx->id_hash)
db_add_session_id_hash(ctx, session_id, ctx->id_hash);
db_add_session_pps(ctx, user, realm, session_id, pps);
hs20_eventlog_node(ctx, user, realm, session_id,
"new subscription", pps);
tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL);
xml_node_free(ctx->xml, pps);
if (!tnds) {
xml_node_free(ctx->xml, spp_node);
return NULL;
}
str = xml_node_to_str(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (!str) {
xml_node_free(ctx->xml, spp_node);
return NULL;
}
node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str);
free(str);
snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm);
xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf);
xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS);
return spp_node;
}
static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx, static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx,
xml_node_t *node, xml_node_t *node,
const char *user, const char *user,
@ -2082,6 +2244,15 @@ static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx,
goto out; goto out;
} }
if (strcasecmp(req_reason, "Subscription provisioning") == 0) {
ret = hs20_sim_provisioning(ctx, user, realm, dmacc,
session_id);
hs20_eventlog_node(ctx, user, realm, session_id,
"subscription provisioning response",
ret);
goto out;
}
debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'", debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'",
req_reason, user); req_reason, user);
out: out:
@ -2124,6 +2295,7 @@ static xml_node_t * build_spp_exchange_complete(struct hs20_svc *ctx,
static int add_subscription(struct hs20_svc *ctx, const char *session_id) static int add_subscription(struct hs20_svc *ctx, const char *session_id)
{ {
char *user, *realm, *pw, *pw_mm, *pps, *str; char *user, *realm, *pw, *pw_mm, *pps, *str;
char *osu_user, *osu_password, *eap_method;
char *sql; char *sql;
int ret = -1; int ret = -1;
char *free_account; char *free_account;
@ -2131,6 +2303,7 @@ static int add_subscription(struct hs20_svc *ctx, const char *session_id)
char *type; char *type;
int cert = 0; int cert = 0;
char *cert_pem, *fingerprint; char *cert_pem, *fingerprint;
const char *method;
user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); user = db_get_session_val(ctx, NULL, NULL, session_id, "user");
realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm");
@ -2144,6 +2317,11 @@ static int add_subscription(struct hs20_svc *ctx, const char *session_id)
if (type && strcmp(type, "cert") == 0) if (type && strcmp(type, "cert") == 0)
cert = 1; cert = 1;
free(type); free(type);
osu_user = db_get_session_val(ctx, NULL, NULL, session_id, "osu_user");
osu_password = db_get_session_val(ctx, NULL, NULL, session_id,
"osu_password");
eap_method = db_get_session_val(ctx, NULL, NULL, session_id,
"eap_method");
if (!user || !realm || !pw) { if (!user || !realm || !pw) {
debug_print(ctx, 1, "Could not find session info from DB for " debug_print(ctx, 1, "Could not find session info from DB for "
@ -2183,13 +2361,19 @@ static int add_subscription(struct hs20_svc *ctx, const char *session_id)
str = db_get_session_val(ctx, NULL, NULL, session_id, "mac_addr"); str = db_get_session_val(ctx, NULL, NULL, session_id, "mac_addr");
sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr) VALUES (%Q,%Q,%d,%Q,%Q,%Q,%d,%Q)", if (eap_method && eap_method[0])
method = eap_method;
else
method = cert ? "TLS" : "TTLS-MSCHAPV2";
sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr,osu_user,osu_password) VALUES (%Q,%Q,%d,%Q,%Q,%Q,%d,%Q,%Q,%Q)",
user, realm, cert ? 0 : 1, user, realm, cert ? 0 : 1,
cert ? "TLS" : "TTLS-MSCHAPV2", method,
fingerprint ? fingerprint : "", fingerprint ? fingerprint : "",
cert_pem ? cert_pem : "", cert_pem ? cert_pem : "",
pw_mm && atoi(pw_mm) ? 1 : 0, pw_mm && atoi(pw_mm) ? 1 : 0,
str ? str : ""); str ? str : "",
osu_user ? osu_user : "",
osu_password ? osu_password : "");
free(str); free(str);
if (sql == NULL) if (sql == NULL)
goto out; goto out;
@ -2257,6 +2441,24 @@ static int add_subscription(struct hs20_svc *ctx, const char *session_id)
} }
} }
str = db_get_session_val(ctx, NULL, NULL, session_id,
"mobile_identifier_hash");
if (str) {
sql = sqlite3_mprintf("DELETE FROM sim_provisioning WHERE mobile_identifier_hash=%Q",
str);
if (sql) {
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) !=
SQLITE_OK) {
debug_print(ctx, 1,
"Failed to delete pending sim_provisioning entry: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
os_free(str);
}
if (ret == 0) { if (ret == 0) {
hs20_eventlog(ctx, user, realm, session_id, hs20_eventlog(ctx, user, realm, session_id,
"completed subscription registration", NULL); "completed subscription registration", NULL);
@ -2270,6 +2472,9 @@ out:
free(pps); free(pps);
free(cert_pem); free(cert_pem);
free(fingerprint); free(fingerprint);
free(osu_user);
free(osu_password);
free(eap_method);
return ret; return ret;
} }

View File

@ -17,6 +17,9 @@ struct hs20_svc {
sqlite3 *db; sqlite3 *db;
const char *addr; const char *addr;
const char *test; const char *test;
const char *imsi;
const char *eap_method;
const char *id_hash;
}; };

View File

@ -24,6 +24,10 @@ CREATE TABLE sessions(
cert TEXT, cert TEXT,
cert_pem TEXT, cert_pem TEXT,
mac_addr TEXT, mac_addr TEXT,
osu_user TEXT,
osu_password TEXT,
eap_method TEXT,
mobile_identifier_hash TEXT,
test TEXT test TEXT
); );

View File

@ -85,6 +85,40 @@ if (!empty($_SERVER['PHP_AUTH_DIGEST'])) {
isset($_SERVER["SSL_CLIENT_M_SERIAL"])) { isset($_SERVER["SSL_CLIENT_M_SERIAL"])) {
$user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"]; $user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"];
putenv("HS20CERT=yes"); putenv("HS20CERT=yes");
} else if (isset($_GET["hotspot2dot0-mobile-identifier-hash"])) {
$id_hash = $_GET["hotspot2dot0-mobile-identifier-hash"];
$id_hash = PREG_REPLACE("/[^0-9a-h]/i", '', $id_hash);
$db = new PDO($osu_db);
if (!$db) {
error_log("spp.php - Could not access database");
die("Could not access database");
}
$row = $db->query("SELECT * FROM sim_provisioning " .
"WHERE mobile_identifier_hash='$id_hash'")->fetch();
if (!$row) {
error_log("spp.php - SIM provisioning failed - mobile_identifier_hash not found");
die('SIM provisioning failed - mobile_identifier_hash not found');
}
$imsi = $row['imsi'];
$mac_addr = $row['mac_addr'];
$eap_method = $row['eap_method'];
$row = $db->query("SELECT COUNT(*) FROM osu_config " .
"WHERE realm='$realm'")->fetch();
if (!$row || intval($row[0]) < 1) {
error_log("spp.php - SIM provisioning failed - realm $realm not found");
die('SIM provisioning failed');
}
error_log("spp.php - SIM provisioning for IMSI $imsi");
putenv("HS20SIMPROV=yes");
putenv("HS20IMSI=$imsi");
putenv("HS20MACADDR=$mac_addr");
putenv("HS20EAPMETHOD=$eap_method");
putenv("HS20IDHASH=$id_hash");
} else if (!isset($_SERVER["PATH_INFO"]) || } else if (!isset($_SERVER["PATH_INFO"]) ||
$_SERVER["PATH_INFO"] != "/signup") { $_SERVER["PATH_INFO"] != "/signup") {
header('HTTP/1.1 401 Unauthorized'); header('HTTP/1.1 401 Unauthorized');