diff --git a/hs20/server/spp_server.c b/hs20/server/spp_server.c index 3600f571f..81cd6ed29 100644 --- a/hs20/server/spp_server.c +++ b/hs20/server/spp_server.c @@ -42,6 +42,7 @@ enum hs20_session_operation { POLICY_UPDATE, FREE_REMEDIATION, CLEAR_REMEDIATION, + CERT_REENROLL, }; @@ -52,6 +53,11 @@ static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, const char *field); static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, const char *realm, int use_dmacc); +static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, + const char *session_id, + const char *user, + const char *realm, + int add_est_user); static int db_add_session(struct hs20_svc *ctx, @@ -825,6 +831,17 @@ static xml_node_t * machine_remediation(struct hs20_svc *ctx, } +static xml_node_t * cert_reenroll(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id) +{ + db_add_session(ctx, user, realm, session_id, NULL, NULL, + CERT_REENROLL, NULL); + return spp_exec_get_certificate(ctx, session_id, user, realm, 0); +} + + static xml_node_t * policy_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, int dmacc) @@ -1009,6 +1026,8 @@ static xml_node_t * hs20_subscription_remediation(struct hs20_svc *ctx, ret = policy_remediation(ctx, user, realm, session_id, dmacc); else if (type && strcmp(type, "machine") == 0) ret = machine_remediation(ctx, user, realm, session_id, dmacc); + else if (type && strcmp(type, "reenroll") == 0) + ret = cert_reenroll(ctx, user, realm, session_id); else ret = no_sub_rem(ctx, user, realm, session_id); free(type); @@ -1381,7 +1400,8 @@ static xml_node_t * build_pps(struct hs20_svc *ctx, static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, const char *session_id, const char *user, - const char *realm) + const char *realm, + int add_est_user) { xml_namespace_t *ns; xml_node_t *spp_node, *enroll, *exec_node; @@ -1389,7 +1409,7 @@ static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, char password[11]; char *b64; - if (new_password(password, sizeof(password)) < 0) + if (add_est_user && new_password(password, sizeof(password)) < 0) return NULL; spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", @@ -1406,6 +1426,10 @@ static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI", val ? val : ""); os_free(val); + + if (!add_est_user) + return spp_node; + xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user); b64 = (char *) base64_encode((unsigned char *) password, @@ -1465,7 +1489,7 @@ static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx, xml_node_t *ret; hs20_eventlog(ctx, user, realm, session_id, "request client certificate enrollment", NULL); - ret = spp_exec_get_certificate(ctx, session_id, user, realm); + ret = spp_exec_get_certificate(ctx, session_id, user, realm, 1); free(user); free(realm); free(pw); @@ -1638,6 +1662,72 @@ static xml_node_t * hs20_user_input_complete(struct hs20_svc *ctx, } +static xml_node_t * hs20_cert_reenroll_complete(struct hs20_svc *ctx, + const char *session_id) +{ + char *user, *realm, *cert; + char *status; + xml_namespace_t *ns; + xml_node_t *spp_node, *cred; + char buf[400]; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + cert = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + if (!user || !realm || !cert) { + debug_print(ctx, 1, + "Could not find session info from DB for certificate reenrollment"); + free(user); + free(realm); + free(cert); + return NULL; + } + + cred = build_credential_cert(ctx, user, realm, cert); + if (!cred) { + debug_print(ctx, 1, "Could not build credential"); + free(user); + free(realm); + free(cert); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) { + debug_print(ctx, 1, "Could not build sppPostDevDataResponse"); + free(user); + free(realm); + free(cert); + xml_node_free(ctx->xml, cred); + return NULL; + } + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + debug_print(ctx, 1, "Could not add update node"); + xml_node_free(ctx->xml, spp_node); + free(user); + free(realm); + free(cert); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate reenrollment", cred); + xml_node_free(ctx->xml, cred); + + free(user); + free(realm); + free(cert); + return spp_node; +} + + static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, const char *user, const char *realm, int dmacc, @@ -1646,7 +1736,7 @@ static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, char *val; enum hs20_session_operation oper; - val = db_get_session_val(ctx, user, realm, session_id, "operation"); + val = db_get_session_val(ctx, NULL, NULL, session_id, "operation"); if (val == NULL) { debug_print(ctx, 1, "No session %s found to continue", session_id); @@ -1657,6 +1747,8 @@ static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, if (oper == SUBSCRIPTION_REGISTRATION) return hs20_user_input_registration(ctx, session_id, 1); + if (oper == CERT_REENROLL) + return hs20_cert_reenroll_complete(ctx, session_id); debug_print(ctx, 1, "User session %s not in state for certificate " "enrollment completion", session_id); @@ -2198,11 +2290,11 @@ static xml_node_t * hs20_spp_update_response(struct hs20_svc *ctx, debug_print(ctx, 1, "sppUpdateResponse: sppStatus: %s sessionID: %s", status, session_id); - val = db_get_session_val(ctx, user, realm, session_id, "operation"); + val = db_get_session_val(ctx, NULL, NULL, session_id, "operation"); if (!val) { debug_print(ctx, 1, - "No session active for user: %s sessionID: %s", - user, session_id); + "No session active for sessionID: %s", + session_id); oper = NO_OPERATION; } else oper = atoi(val); @@ -2308,12 +2400,55 @@ static xml_node_t * hs20_spp_update_response(struct hs20_svc *ctx, if (oper == POLICY_UPDATE) db_update_val(ctx, user, realm, "polupd_done", "1", dmacc); + if (oper == CERT_REENROLL) { + char *new_user; + + new_user = db_get_session_val(ctx, NULL, NULL, + session_id, "user"); + if (!new_user) { + debug_print(ctx, 1, + "Failed to find new user name (cert-serialnum)"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, + "Failed to find new user name (cert reenroll)", + ret); + db_remove_session(ctx, NULL, NULL, session_id); + return ret; + } + + debug_print(ctx, 1, + "Update certificate user entry to use the new serial number (old=%s new=%s)", + user, new_user); + + if (db_update_val(ctx, user, realm, "identity", + new_user, 0) < 0 || + db_update_val(ctx, new_user, realm, "remediation", + "", 0) < 0) { + debug_print(ctx, 1, + "Failed to update user name (cert-serialnum)"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, + "Failed to update user name (cert reenroll)", + ret); + db_remove_session(ctx, NULL, NULL, session_id); + os_free(new_user); + return ret; + } + + os_free(new_user); + } ret = build_spp_exchange_complete( ctx, session_id, "Exchange complete, release TLS connection", NULL); hs20_eventlog_node(ctx, user, realm, session_id, "Exchange completed", ret); - db_remove_session(ctx, user, realm, session_id); + db_remove_session(ctx, NULL, NULL, session_id); return ret; } diff --git a/hs20/server/www/est.php b/hs20/server/www/est.php index 6983ec9e2..b7fb260d5 100644 --- a/hs20/server/www/est.php +++ b/hs20/server/www/est.php @@ -10,6 +10,12 @@ $method = $_SERVER["REQUEST_METHOD"]; unset($user); unset($rowid); +$db = new PDO($osu_db); +if (!$db) { + error_log("EST: Could not access database"); + die("Could not access database"); +} + if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { $needed = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); @@ -31,12 +37,6 @@ if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { die('Authentication failed'); } - $db = new PDO($osu_db); - if (!$db) { - error_log("EST: Could not access database"); - die("Could not access database"); - } - $sql = "SELECT rowid,password,operation FROM sessions " . "WHERE user='$user' AND realm='$realm'"; $q = $db->query($sql); @@ -70,6 +70,29 @@ if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { error_log("EST: Incorrect authentication response for user=$user realm=$realm"); die('Authentication failed'); } +} else if (isset($_SERVER["SSL_CLIENT_VERIFY"]) && + $_SERVER["SSL_CLIENT_VERIFY"] == "SUCCESS" && + isset($_SERVER["SSL_CLIENT_M_SERIAL"])) { + $user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"]; + $sql = "SELECT rowid,password,operation FROM sessions " . + "WHERE user='$user' AND realm='$realm'"; + $q = $db->query($sql); + if (!$q) { + error_log("EST: Session not found for user=$user realm=$realm"); + die("Session not found"); + } + $row = $q->fetch(); + if (!$row) { + error_log("EST: Session fetch failed for user=$user realm=$realm"); + die('Session not found'); + } + $rowid = $row['rowid']; + + $oper = $row['operation']; + if ($oper != '10') { + error_log("EST: Unexpected operation $oper for user=$user realm=$realm"); + die("Session not found"); + } } @@ -92,14 +115,24 @@ if ($method == "GET" && $cmd == "cacerts") { header("Content-Type: application/csrattrs"); readfile("$osu_root/est/est-attrs.b64"); error_log("EST: csrattrs"); -} else if ($method == "POST" && $cmd == "simpleenroll") { - if (!isset($user) || strlen($user) == 0) { +} else if ($method == "POST" && + ($cmd == "simpleenroll" || $cmd == "simplereenroll")) { + $reenroll = $cmd == "simplereenroll"; + if (!$reenroll && (!isset($user) || strlen($user) == 0)) { header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="'.$realm. '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); error_log("EST: simpleenroll - require authentication"); die('Authentication required'); } + if ($reenroll && + (!isset($user) || + !isset($_SERVER["SSL_CLIENT_VERIFY"]) || + $_SERVER["SSL_CLIENT_VERIFY"] != "SUCCESS")) { + header('HTTP/1.1 403 Forbidden'); + error_log("EST: simplereenroll - require certificate authentication"); + die('Authentication required'); + } if (!isset($_SERVER["CONTENT_TYPE"])) { error_log("EST: simpleenroll without Content-Type"); die("Missing Content-Type"); @@ -167,6 +200,7 @@ if ($method == "GET" && $cmd == "cacerts") { } $der = file_get_contents($cert_der); $fingerprint = hash("sha256", $der); + error_log("EST: sha256(DER cert): $fingerprint"); $pkcs7 = "$cadir/tmp/est-client.pkcs7"; if (file_exists($pkcs7)) diff --git a/hs20/server/www/users.php b/hs20/server/www/users.php index ea5995c4f..2bd555275 100644 --- a/hs20/server/www/users.php +++ b/hs20/server/www/users.php @@ -69,6 +69,9 @@ if ($cmd == 'subrem-add-user' && $id > 0) { if ($cmd == 'subrem-add-machine' && $id > 0) { $db->exec("UPDATE users SET remediation='machine' WHERE rowid=$id"); } +if ($cmd == 'subrem-add-reenroll' && $id > 0) { + $db->exec("UPDATE users SET remediation='reenroll' WHERE rowid=$id"); +} if ($cmd == 'subrem-add-policy' && $id > 0) { $db->exec("UPDATE users SET remediation='policy' WHERE rowid=$id"); } @@ -172,6 +175,10 @@ if ($rem == "") { $row['rowid'] . "\">add:user]"; echo " [add:machine]"; + if ($row['methods'] == 'TLS') { + echo " [add:reenroll]"; + } echo " [add:policy]"; echo " [clear]"; +} else if ($rem == "reenroll") { + echo "Reenroll [clear]"; } else { echo "Machine [clear]"; @@ -334,6 +344,8 @@ foreach ($res as $row) { echo "Policy"; } else if ($rem == "free") { echo "Free"; + } else if ($rem == "reenroll") { + echo "Reenroll"; } else { echo "Machine"; }