]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
HS 2.0 server: Client certificate reenrollment
authorJouni Malinen <jouni@codeaurora.org>
Tue, 4 Dec 2018 12:11:39 +0000 (14:11 +0200)
committerJouni Malinen <j@w1.fi>
Tue, 4 Dec 2018 12:11:39 +0000 (14:11 +0200)
This adds support for the SPP server to request certificate reenrollment
and for the EST server to support the simplereenroll version.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
hs20/server/spp_server.c
hs20/server/www/est.php
hs20/server/www/users.php

index 3600f571fe71376f1790348fd45962fdf3975280..81cd6ed294f4e2bc809f49b6bdac73ff4e2e2fb6 100644 (file)
@@ -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;
        }
 
index 6983ec9e2c235f84c1cfe9a0e21ff948325d42b5..b7fb260d56c45d0c5acbb365b72c586a09ee680b 100644 (file)
@@ -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))
index ea5995c4ff9419f3cb737e7c798e5fdfa65a669c..2bd555275ddab342d8cf859a0698f72b03856b54 100644 (file)
@@ -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</a>]";
        echo " [<a href=\"users.php?cmd=subrem-add-machine&id=" .
                   $row['rowid'] . "\">add:machine</a>]";
+       if ($row['methods'] == 'TLS') {
+               echo " [<a href=\"users.php?cmd=subrem-add-reenroll&id=" .
+                          $row['rowid'] . "\">add:reenroll</a>]";
+       }
        echo " [<a href=\"users.php?cmd=subrem-add-policy&id=" .
                   $row['rowid'] . "\">add:policy</a>]";
        echo " [<a href=\"users.php?cmd=subrem-add-free&id=" .
@@ -185,6 +192,9 @@ if ($rem == "") {
 } else if ($rem == "free") {
        echo "Free [<a href=\"users.php?cmd=subrem-clear&id=" .
                       $row['rowid'] . "\">clear</a>]";
+} else if ($rem == "reenroll") {
+       echo "Reenroll [<a href=\"users.php?cmd=subrem-clear&id=" .
+                      $row['rowid'] . "\">clear</a>]";
 } else  {
        echo "Machine [<a href=\"users.php?cmd=subrem-clear&id=" .
                          $row['rowid'] . "\">clear</a>]";
@@ -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";
        }