Blame SOURCES/0041-PAM-allow-missing-logon_name-during-certificate-auth.patch

ced1f5
From f6e57537cbeaf6e3f313e700f08e0022a32a7d6c Mon Sep 17 00:00:00 2001
ced1f5
From: Sumit Bose <sbose@redhat.com>
ced1f5
Date: Mon, 30 Oct 2017 17:11:56 +0100
ced1f5
Subject: [PATCH 41/46] PAM: allow missing logon_name during certificate
ced1f5
 authentication
ced1f5
MIME-Version: 1.0
ced1f5
Content-Type: text/plain; charset=UTF-8
ced1f5
Content-Transfer-Encoding: 8bit
ced1f5
ced1f5
If only one certificate is available and the logon_name is the user is
ced1f5
not given the PAM responder already tried to find the name during the
ced1f5
pre-auth step. With multiple certificates this might cause useless extra
ced1f5
effort and the name should be determined after the certificate is
ced1f5
selected in the authentication step. This might currently only happen
ced1f5
with GDM because all other PAM clients will prompt for the user name
ced1f5
unconditionally.
ced1f5
ced1f5
New unit tests are added to cover this new case.
ced1f5
ced1f5
Related to https://pagure.io/SSSD/sssd/issue/3560
ced1f5
ced1f5
Reviewed-by: Fabiano FidĂȘncio <fidencio@redhat.com>
ced1f5
Tested-by: Scott Poore <spoore@redhat.com>
ced1f5
(cherry picked from commit fd6f4047b58686bd4057c9859c3c804a77b136d8)
ced1f5
---
ced1f5
 src/responder/pam/pamsrv_cmd.c  | 63 ++++++++++++++++++++++++++-----
ced1f5
 src/tests/cmocka/test_pam_srv.c | 82 +++++++++++++++++++++++++++++++++++++++++
ced1f5
 2 files changed, 135 insertions(+), 10 deletions(-)
ced1f5
ced1f5
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
ced1f5
index 8b2c086e206796ad4c977495be957c56b3255e7f..caf6c99489b8378d2e850473191223709920cd79 100644
ced1f5
--- a/src/responder/pam/pamsrv_cmd.c
ced1f5
+++ b/src/responder/pam/pamsrv_cmd.c
ced1f5
@@ -1151,6 +1151,7 @@ static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *p
ced1f5
     size_t blen;
ced1f5
     errno_t ret;
ced1f5
     uint32_t terminator;
ced1f5
+    const char *key_id;
ced1f5
 
ced1f5
     prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol);
ced1f5
 
ced1f5
@@ -1191,9 +1192,33 @@ static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *p
ced1f5
                                          pd->logon_name,
ced1f5
                                          &pd->domain, &pd->user);
ced1f5
     } else {
ced1f5
-        /* Only SSS_PAM_PREAUTH request may have a missing name, e.g. if the
ced1f5
-         * name is determined with the help of a certificate */
ced1f5
-        if (pd->cmd == SSS_PAM_PREAUTH
ced1f5
+        /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the
ced1f5
+         * name is determined with the help of a certificate. During
ced1f5
+         * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the
ced1f5
+         * selected certificate. */
ced1f5
+        if (pd->cmd == SSS_PAM_AUTHENTICATE
ced1f5
+                && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx,
ced1f5
+                                                    struct pam_ctx), pd)
ced1f5
+                && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN
ced1f5
+                    || sss_authtok_get_type(pd->authtok)
ced1f5
+                                               == SSS_AUTHTOK_TYPE_SC_KEYPAD)) {
ced1f5
+            ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL,
ced1f5
+                                     NULL, &key_id, NULL);
ced1f5
+            if (ret != EOK) {
ced1f5
+                DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n");
ced1f5
+                goto done;
ced1f5
+            }
ced1f5
+
ced1f5
+            if (key_id == NULL || *key_id == '\0') {
ced1f5
+                DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
+                      "Missing logon and Smartcard key ID during "
ced1f5
+                      "authentication.\n");
ced1f5
+                ret = ERR_NO_CREDS;
ced1f5
+                goto done;
ced1f5
+            }
ced1f5
+
ced1f5
+            ret = EOK;
ced1f5
+        } else if (pd->cmd == SSS_PAM_PREAUTH
ced1f5
                 && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx,
ced1f5
                                                     struct pam_ctx), pd)) {
ced1f5
             ret = EOK;
ced1f5
@@ -1375,9 +1400,12 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd)
ced1f5
     /* Determine what domain type to contact */
ced1f5
     preq->req_dom_type = get_domain_request_type(preq, pctx);
ced1f5
 
ced1f5
-    /* try backend first for authentication before doing local Smartcard
ced1f5
-     * authentication */
ced1f5
-    if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) {
ced1f5
+    /* Try backend first for authentication before doing local Smartcard
ced1f5
+     * authentication if a logon name is available. Otherwise try to derive
ced1f5
+     * the logon name from the certificate first. */
ced1f5
+    if ((pd->cmd != SSS_PAM_AUTHENTICATE
ced1f5
+                || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL))
ced1f5
+            && may_do_cert_auth(pctx, pd)) {
ced1f5
         ret = check_cert(cctx, cctx->ev, pctx, preq, pd);
ced1f5
         /* Finish here */
ced1f5
         goto done;
ced1f5
@@ -1577,9 +1605,10 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
     } else {
ced1f5
 
ced1f5
         if (preq->pd->logon_name == NULL) {
ced1f5
-            if (preq->pd->cmd != SSS_PAM_PREAUTH) {
ced1f5
+            if (preq->pd->cmd != SSS_PAM_PREAUTH
ced1f5
+                    && preq->pd->cmd != SSS_PAM_AUTHENTICATE) {
ced1f5
                 DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
-                      "Missing logon name only allowed during pre-auth.\n");
ced1f5
+                      "Missing logon name only allowed during (pre-)auth.\n");
ced1f5
                 ret = ENOENT;
ced1f5
                 goto done;
ced1f5
             }
ced1f5
@@ -1641,7 +1670,8 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
                 }
ced1f5
             }
ced1f5
 
ced1f5
-            if (preq->cctx->rctx->domains->user_name_hint) {
ced1f5
+            if (preq->cctx->rctx->domains->user_name_hint
ced1f5
+                    && preq->pd->cmd == SSS_PAM_PREAUTH) {
ced1f5
                 ret = add_pam_cert_response(preq->pd, cert_user,
ced1f5
                                             preq->cert_list,
ced1f5
                                             SSS_PAM_CERT_INFO_WITH_HINT);
ced1f5
@@ -1664,6 +1694,20 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
                 goto done;
ced1f5
             }
ced1f5
 
ced1f5
+            /* If logon_name was not given during authentication add a
ced1f5
+             * SSS_PAM_CERT_INFO message to send the name to the caller. */
ced1f5
+            if (preq->pd->cmd == SSS_PAM_AUTHENTICATE
ced1f5
+                    && preq->pd->logon_name == NULL) {
ced1f5
+                ret = add_pam_cert_response(preq->pd, cert_user,
ced1f5
+                                            preq->cert_list,
ced1f5
+                                            SSS_PAM_CERT_INFO);
ced1f5
+                if (ret != EOK) {
ced1f5
+                    DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
ced1f5
+                    preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
ced1f5
+                    goto done;
ced1f5
+                }
ced1f5
+            }
ced1f5
+
ced1f5
             /* cert_user will be returned to the PAM client as user name, so
ced1f5
              * we can use it here already e.g. to set in initgroups timeout */
ced1f5
             preq->pd->logon_name = talloc_strdup(preq->pd, cert_user);
ced1f5
@@ -2039,7 +2083,6 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
ced1f5
                                   "the backend.\n");
ced1f5
                         }
ced1f5
 
ced1f5
-                        /* FIXME: use the right cert info */
ced1f5
                         ret = add_pam_cert_response(preq->pd, cert_user,
ced1f5
                                                     preq->current_cert,
ced1f5
                                                     SSS_PAM_CERT_INFO);
ced1f5
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
ced1f5
index 50d3ed005468375ff02c60bebd1c61047ca1c6d4..b6845320ca41d6933280aa2836a3d984dacfcc5e 100644
ced1f5
--- a/src/tests/cmocka/test_pam_srv.c
ced1f5
+++ b/src/tests/cmocka/test_pam_srv.c
ced1f5
@@ -967,6 +967,16 @@ static int test_pam_cert_check(uint32_t status, uint8_t *body, size_t blen)
ced1f5
                                   NULL);
ced1f5
 }
ced1f5
 
ced1f5
+static int test_pam_cert_check_auth_success(uint32_t status, uint8_t *body,
ced1f5
+                                            size_t blen)
ced1f5
+{
ced1f5
+    assert_int_equal(pam_test_ctx->exp_pam_status, PAM_BAD_ITEM);
ced1f5
+    pam_test_ctx->exp_pam_status = PAM_SUCCESS;
ced1f5
+    return test_pam_cert_check_ex(status, body, blen,
ced1f5
+                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME,
ced1f5
+                                  NULL);
ced1f5
+}
ced1f5
+
ced1f5
 static int test_pam_cert_check_with_hint(uint32_t status, uint8_t *body,
ced1f5
                                          size_t blen)
ced1f5
 {
ced1f5
@@ -2265,6 +2275,74 @@ void test_pam_cert_auth(void **state)
ced1f5
     assert_int_equal(ret, EOK);
ced1f5
 }
ced1f5
 
ced1f5
+void test_pam_cert_auth_no_logon_name(void **state)
ced1f5
+{
ced1f5
+    int ret;
ced1f5
+
ced1f5
+    set_cert_auth_param(pam_test_ctx->pctx, NSS_DB);
ced1f5
+
ced1f5
+    /* Here the last option must be set to true because the backend is only
ced1f5
+     * connected once. During authentication the backend is connected first to
ced1f5
+     * see if it can handle Smartcard authentication, but before that the user
ced1f5
+     * is looked up. Since the first mocked reply already adds the certificate
ced1f5
+     * to the user entry the lookup by certificate will already find the user
ced1f5
+     * in the cache and no second request to the backend is needed. */
ced1f5
+    mock_input_pam_cert(pam_test_ctx, NULL, "123456", "SSSD Test Token",
ced1f5
+                        "NSS-Internal",
ced1f5
+                        "A5EF7DEE625CA5996C8D1BA7D036708161FD49E7", NULL,
ced1f5
+                        test_lookup_by_cert_cb, TEST_TOKEN_CERT, true);
ced1f5
+
ced1f5
+    mock_account_recv_simple();
ced1f5
+    mock_parse_inp("pamuser", NULL, EOK);
ced1f5
+
ced1f5
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
ced1f5
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
ced1f5
+
ced1f5
+    /* Assume backend cannot handle Smartcard credentials */
ced1f5
+    pam_test_ctx->exp_pam_status = PAM_BAD_ITEM;
ced1f5
+
ced1f5
+    set_cmd_cb(test_pam_cert_check_auth_success);
ced1f5
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
ced1f5
+                          pam_test_ctx->pam_cmds);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    /* Wait until the test finishes with EOK */
ced1f5
+    ret = test_ev_loop(pam_test_ctx->tctx);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+}
ced1f5
+
ced1f5
+void test_pam_cert_auth_no_logon_name_no_key_id(void **state)
ced1f5
+{
ced1f5
+    int ret;
ced1f5
+
ced1f5
+    set_cert_auth_param(pam_test_ctx->pctx, NSS_DB);
ced1f5
+
ced1f5
+    /* Here the last option must be set to true because the backend is only
ced1f5
+     * connected once. During authentication the backend is connected first to
ced1f5
+     * see if it can handle Smartcard authentication, but before that the user
ced1f5
+     * is looked up. Since the first mocked reply already adds the certificate
ced1f5
+     * to the user entry the lookup by certificate will already find the user
ced1f5
+     * in the cache and no second request to the backend is needed. */
ced1f5
+    mock_input_pam_cert(pam_test_ctx, NULL, "123456", "SSSD Test Token",
ced1f5
+                        "NSS-Internal", NULL, NULL,
ced1f5
+                        NULL, NULL, false);
ced1f5
+
ced1f5
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
ced1f5
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
ced1f5
+
ced1f5
+    /* Assume backend cannot handle Smartcard credentials */
ced1f5
+    pam_test_ctx->exp_pam_status = PAM_BAD_ITEM;
ced1f5
+
ced1f5
+    set_cmd_cb(test_pam_creds_insufficient_check);
ced1f5
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
ced1f5
+                          pam_test_ctx->pam_cmds);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    /* Wait until the test finishes with EOK */
ced1f5
+    ret = test_ev_loop(pam_test_ctx->tctx);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+}
ced1f5
+
ced1f5
 void test_pam_cert_auth_double_cert(void **state)
ced1f5
 {
ced1f5
     int ret;
ced1f5
@@ -2770,6 +2848,10 @@ int main(int argc, const char *argv[])
ced1f5
                                         pam_test_setup, pam_test_teardown),
ced1f5
         cmocka_unit_test_setup_teardown(test_pam_cert_preauth_2certs_two_mappings,
ced1f5
                                         pam_test_setup, pam_test_teardown),
ced1f5
+        cmocka_unit_test_setup_teardown(test_pam_cert_auth_no_logon_name,
ced1f5
+                                        pam_test_setup, pam_test_teardown),
ced1f5
+        cmocka_unit_test_setup_teardown(test_pam_cert_auth_no_logon_name_no_key_id,
ced1f5
+                                        pam_test_setup, pam_test_teardown),
ced1f5
 #endif /* HAVE_NSS */
ced1f5
 
ced1f5
         cmocka_unit_test_setup_teardown(test_filter_response,
ced1f5
-- 
ced1f5
2.13.6
ced1f5