Blob Blame History Raw
From 5649511243126866c97df0892c51227ab9257347 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Fri, 25 Aug 2017 12:51:09 +0200
Subject: [PATCH 43/79] PAM: handled multiple certs in the responder
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch refactors the handling of the certificate and the attributes
to address the certificate on the Smartcard (module name, token name and
key id). Instead of using individual variables the values are put into a
new struct cert_auth_info. Since the new struct can be used as a list
the PAM responder can now handle multiple certificates on the Smartcard
and can send the needed data to pam_sss with multiple SSS_PAM_CERT_INFO
messages.

Unit tests are added to confirm the expected behavior.

Related to https://pagure.io/SSSD/sssd/issue/3560

Reviewed-by: Fabiano FidĂȘncio <fidencio@redhat.com>
Tested-by: Scott Poore <spoore@redhat.com>
---
 src/responder/pam/pamsrv.h      |  25 ++--
 src/responder/pam/pamsrv_cmd.c  | 257 ++++++++++++++++++++++++++--------------
 src/responder/pam/pamsrv_p11.c  | 181 ++++++++++++++++++++--------
 src/tests/cmocka/test_pam_srv.c | 167 +++++++++++++++++++++++++-
 src/tests/whitespace_test       |   2 +-
 5 files changed, 483 insertions(+), 149 deletions(-)

diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 896f71befbc9947a53b5eb20cba0bb3d104c4cf2..f15f7f19f1f38626288416c9f2038371c6f58b47 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -73,10 +73,8 @@ struct pam_auth_req {
     struct pam_auth_dp_req *dpreq_spy;
 
     struct ldb_message *user_obj;
-    struct ldb_result *cert_user_objs;
-    char *token_name;
-    char *module_name;
-    char *key_id;
+    struct cert_auth_info *cert_list;
+    struct cert_auth_info *current_cert;
     bool cert_auth_local;
 };
 
@@ -89,6 +87,16 @@ int LOCAL_pam_handler(struct pam_auth_req *preq);
 errno_t p11_child_init(struct pam_ctx *pctx);
 
 struct cert_auth_info;
+const char *sss_cai_get_cert(struct cert_auth_info *i);
+const char *sss_cai_get_token_name(struct cert_auth_info *i);
+const char *sss_cai_get_module_name(struct cert_auth_info *i);
+const char *sss_cai_get_key_id(struct cert_auth_info *i);
+struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i);
+struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i);
+void sss_cai_set_cert_user_objs(struct cert_auth_info *i,
+                                struct ldb_result *cert_user_objs);
+void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count,
+                         size_t *_cert_user_count);
 
 struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
                                        struct tevent_context *ev,
@@ -98,12 +106,11 @@ struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
                                        const char *verify_opts,
                                        struct pam_data *pd);
 errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
-                            char **cert, char **token_name, char **module_name,
-                            char **key_id);
+                            struct cert_auth_info **cert_list);
 
-errno_t add_pam_cert_response(struct pam_data *pd, const char *user,
-                              const char *token_name, const char *module_name,
-                              const char *key_id, enum response_type type);
+errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
+                              struct cert_auth_info *cert_info,
+                              enum response_type type);
 
 bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd);
 
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 51d8185650cf823da289a3398b10133065d82ae4..8b2c086e206796ad4c977495be957c56b3255e7f 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1389,21 +1389,17 @@ done:
     return pam_check_user_done(preq, ret);
 }
 
+static errno_t pam_user_by_cert_step(struct pam_auth_req *preq);
 static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req);
 static void pam_forwarder_cert_cb(struct tevent_req *req)
 {
     struct pam_auth_req *preq = tevent_req_callback_data(req,
                                                          struct pam_auth_req);
-    struct cli_ctx *cctx = preq->cctx;
     struct pam_data *pd;
     errno_t ret = EOK;
-    char *cert;
-    struct pam_ctx *pctx =
-            talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+    const char *cert;
 
-    ret = pam_check_cert_recv(req, preq, &cert, &preq->token_name,
-                                                &preq->module_name,
-                                                &preq->key_id);
+    ret = pam_check_cert_recv(req, preq, &preq->cert_list);
     talloc_free(req);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n");
@@ -1412,6 +1408,8 @@ static void pam_forwarder_cert_cb(struct tevent_req *req)
 
     pd = preq->pd;
 
+    cert = sss_cai_get_cert(preq->cert_list);
+
     if (cert == NULL) {
         if (pd->logon_name == NULL) {
             DEBUG(SSSDBG_CRIT_FAILURE,
@@ -1431,21 +1429,42 @@ static void pam_forwarder_cert_cb(struct tevent_req *req)
         goto done;
     }
 
+    preq->current_cert = preq->cert_list;
+    ret = pam_user_by_cert_step(preq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n");
+        goto done;
+    }
+
+    return;
+
+done:
+    pam_check_user_done(preq, ret);
+}
+
+static errno_t pam_user_by_cert_step(struct pam_auth_req *preq)
+{
+    struct cli_ctx *cctx = preq->cctx;
+    struct tevent_req *req;
+    struct pam_ctx *pctx =
+            talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+
+    if (preq->current_cert == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n");
+        return EINVAL;
+    }
 
     req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx,
                                       pctx->rctx->ncache, 0,
                                       preq->req_dom_type, NULL,
-                                      cert);
+                                      sss_cai_get_cert(preq->current_cert));
     if (req == NULL) {
         DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n");
-        ret = ENOMEM;
-        goto done;
+        return ENOMEM;
     }
+
     tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq);
-    return;
-
-done:
-    pam_check_user_done(preq, ret);
+    return EOK;
 }
 
 static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx,
@@ -1511,6 +1530,9 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
     struct pam_auth_req *preq = tevent_req_callback_data(req,
                                                          struct pam_auth_req);
     const char *cert_user = NULL;
+    size_t cert_count = 0;
+    size_t cert_user_count = 0;
+    struct ldb_result *cert_user_objs;
 
     ret = cache_req_recv(preq, req, &results);
     talloc_zfree(req);
@@ -1521,12 +1543,39 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
 
     if (ret == EOK) {
         ret = get_results_from_all_domains(preq, results,
-                                           &preq->cert_user_objs);
+                                           &cert_user_objs);
         if (ret != EOK) {
             DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n");
             goto done;
         }
 
+        sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs);
+    }
+
+    preq->current_cert = sss_cai_get_next(preq->current_cert);
+    if (preq->current_cert != NULL) {
+        ret = pam_user_by_cert_step(preq);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n");
+            goto done;
+        }
+        return;
+    }
+
+    sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count);
+    DEBUG(SSSDBG_TRACE_ALL,
+          "Found [%zu] certificates and [%zu] related users.\n",
+          cert_count, cert_user_count);
+
+    if (cert_user_count == 0) {
+        if (preq->pd->logon_name == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Missing logon name and no certificate user found.\n");
+            ret = ENOENT;
+            goto done;
+        }
+    } else {
+
         if (preq->pd->logon_name == NULL) {
             if (preq->pd->cmd != SSS_PAM_PREAUTH) {
                 DEBUG(SSSDBG_CRIT_FAILURE,
@@ -1535,9 +1584,39 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
                 goto done;
             }
 
-            if (preq->cert_user_objs->count == 1) {
+            if (cert_count > 1) {
+                for (preq->current_cert = preq->cert_list;
+                     preq->current_cert != NULL;
+                     preq->current_cert = sss_cai_get_next(preq->current_cert)) {
+
+                    ret = add_pam_cert_response(preq->pd, "",
+                                       preq->current_cert,
+                                       preq->cctx->rctx->domains->user_name_hint
+                                            ? SSS_PAM_CERT_INFO_WITH_HINT
+                                            : SSS_PAM_CERT_INFO);
+                    if (ret != EOK) {
+                        DEBUG(SSSDBG_OP_FAILURE,
+                              "add_pam_cert_response failed.\n");
+                        preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+                    }
+                }
+
+                ret = EOK;
+                preq->pd->pam_status = PAM_SUCCESS;
+                pam_reply(preq);
+                goto done;
+            }
+
+            if (cert_user_count == 1) {
+                cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list);
+                if (cert_user_objs == NULL) {
+                    DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n");
+                    ret = ENOENT;
+                    goto done;
+                }
+
                 cert_user = ldb_msg_find_attr_as_string(
-                                                  preq->cert_user_objs->msgs[0],
+                                                  cert_user_objs->msgs[0],
                                                   SYSDB_NAME, NULL);
                 if (cert_user == NULL) {
                     DEBUG(SSSDBG_CRIT_FAILURE,
@@ -1564,9 +1643,7 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
 
             if (preq->cctx->rctx->domains->user_name_hint) {
                 ret = add_pam_cert_response(preq->pd, cert_user,
-                                            preq->token_name,
-                                            preq->module_name,
-                                            preq->key_id,
+                                            preq->cert_list,
                                             SSS_PAM_CERT_INFO_WITH_HINT);
                 preq->pd->pam_status = PAM_SUCCESS;
                 if (ret != EOK) {
@@ -1596,13 +1673,6 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
                 goto done;
             }
         }
-    } else {
-        if (preq->pd->logon_name == NULL) {
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "Missing logon name and no certificate user found.\n");
-            ret = ENOENT;
-            goto done;
-        }
     }
 
     if (preq->user_obj == NULL) {
@@ -1884,7 +1954,9 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
     struct pam_ctx *pctx =
             talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
     const char *cert_user;
+    struct ldb_result *cert_user_objs;
     size_t c;
+    bool found = false;
 
     if (!preq->pd->domain) {
         preq->pd->domain = preq->domain->name;
@@ -1921,76 +1993,87 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
         return;
     }
 
-    if (may_do_cert_auth(pctx, preq->pd) && preq->cert_user_objs != NULL) {
+    if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) {
         /* Check if user matches certificate user */
-        for (c = 0; c < preq->cert_user_objs->count; c++) {
-            cert_user = ldb_msg_find_attr_as_string(
-                                                  preq->cert_user_objs->msgs[c],
-                                                  SYSDB_NAME,
-                                                  NULL);
-            if (cert_user == NULL) {
-                /* Even if there might be other users mapped to the
-                 * certificate a missing SYSDB_NAME indicates some critical
-                 * condition which justifies that the whole request is aborted
-                 * */
-                DEBUG(SSSDBG_CRIT_FAILURE,
-                      "Certificate user object has no name.\n");
-                preq->pd->pam_status = PAM_USER_UNKNOWN;
-                pam_reply(preq);
-                return;
+        found = false;
+        for (preq->current_cert = preq->cert_list;
+             preq->current_cert != NULL;
+             preq->current_cert = sss_cai_get_next(preq->current_cert)) {
+
+            cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert);
+            if (cert_user_objs == NULL) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "Unexpteced missing certificate user, "
+                      "trying next certificate.\n");
+                continue;
             }
 
-            /* pam_check_user_search() calls pd_set_primary_name() is the search
-             * was successful, so pd->user contains the canonical sysdb name
-             * as well */
-            if (ldb_dn_compare(preq->cert_user_objs->msgs[c]->dn,
-                               preq->user_obj->dn) == 0) {
-
-                if (preq->pd->cmd == SSS_PAM_PREAUTH) {
-                    ret = sss_authtok_set_sc(preq->pd->authtok,
-                                             SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0,
-                                             preq->token_name, 0,
-                                             preq->module_name, 0,
-                                             preq->key_id, 0);
-                    if (ret != EOK) {
-                        DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_set_sc failed, "
-                                                 "Smartcard authentication "
-                                                 "detection might fail in the "
-                                                 "backend.\n");
-                    }
-
-                    ret = add_pam_cert_response(preq->pd, cert_user,
-                                                preq->token_name,
-                                                preq->module_name,
-                                                preq->key_id,
-                                                SSS_PAM_CERT_INFO);
-                    if (ret != EOK) {
-                        DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
-                        preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
-                    }
-                }
-
-                /* We are done if we do not have to call the backend */
-                if (preq->pd->cmd == SSS_PAM_AUTHENTICATE
-                        && preq->cert_auth_local) {
-                    preq->pd->pam_status = PAM_SUCCESS;
-                    preq->callback = pam_reply;
+            for (c = 0; c < cert_user_objs->count; c++) {
+                cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c],
+                                                        SYSDB_NAME, NULL);
+                if (cert_user == NULL) {
+                    /* Even if there might be other users mapped to the
+                     * certificate a missing SYSDB_NAME indicates some critical
+                     * condition which justifies that the whole request is aborted
+                     * */
+                    DEBUG(SSSDBG_CRIT_FAILURE,
+                          "Certificate user object has no name.\n");
+                    preq->pd->pam_status = PAM_USER_UNKNOWN;
                     pam_reply(preq);
                     return;
                 }
+
+                if (ldb_dn_compare(cert_user_objs->msgs[c]->dn,
+                                   preq->user_obj->dn) == 0) {
+                    found = true;
+                    if (preq->pd->cmd == SSS_PAM_PREAUTH) {
+                        ret = sss_authtok_set_sc(preq->pd->authtok,
+                                 SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0,
+                                 sss_cai_get_token_name(preq->current_cert), 0,
+                                 sss_cai_get_module_name(preq->current_cert), 0,
+                                 sss_cai_get_key_id(preq->current_cert), 0);
+                        if (ret != EOK) {
+                            DEBUG(SSSDBG_OP_FAILURE,
+                                  "sss_authtok_set_sc failed, Smartcard "
+                                  "authentication detection might fail in "
+                                  "the backend.\n");
+                        }
+
+                        /* FIXME: use the right cert info */
+                        ret = add_pam_cert_response(preq->pd, cert_user,
+                                                    preq->current_cert,
+                                                    SSS_PAM_CERT_INFO);
+                        if (ret != EOK) {
+                            DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
+                            preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+                        }
+                    }
+
+                }
             }
         }
 
-        if (preq->pd->cmd == SSS_PAM_PREAUTH) {
-            DEBUG(SSSDBG_TRACE_FUNC,
-                  "User and certificate user do not match, "
-                  "continue with other authentication methods.\n");
+        if (found) {
+            /* We are done if we do not have to call the backend */
+            if (preq->pd->cmd == SSS_PAM_AUTHENTICATE
+                    && preq->cert_auth_local) {
+                preq->pd->pam_status = PAM_SUCCESS;
+                preq->callback = pam_reply;
+                pam_reply(preq);
+                return;
+            }
         } else {
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "User and certificate user do not match.\n");
-            preq->pd->pam_status = PAM_AUTH_ERR;
-            pam_reply(preq);
-            return;
+            if (preq->pd->cmd == SSS_PAM_PREAUTH) {
+                DEBUG(SSSDBG_TRACE_FUNC,
+                      "User and certificate user do not match, "
+                      "continue with other authentication methods.\n");
+            } else {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "User and certificate user do not match.\n");
+                preq->pd->pam_status = PAM_AUTH_ERR;
+                pam_reply(preq);
+                return;
+            }
         }
     }
 
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
index ff32d1e726808caa36ca7cca557220866ef1a9ab..57c8e1e464f4262f2d78f869c52ca48bd469d90a 100644
--- a/src/responder/pam/pamsrv_p11.c
+++ b/src/responder/pam/pamsrv_p11.c
@@ -40,10 +40,80 @@ struct cert_auth_info {
     char *token_name;
     char *module_name;
     char *key_id;
+    struct ldb_result *cert_user_objs;
     struct cert_auth_info *prev;
     struct cert_auth_info *next;
 };
 
+const char *sss_cai_get_cert(struct cert_auth_info *i)
+{
+    return i != NULL ? i->cert : NULL;
+}
+
+const char *sss_cai_get_token_name(struct cert_auth_info *i)
+{
+    return i != NULL ? i->token_name : NULL;
+}
+
+const char *sss_cai_get_module_name(struct cert_auth_info *i)
+{
+    return i != NULL ? i->module_name : NULL;
+}
+
+const char *sss_cai_get_key_id(struct cert_auth_info *i)
+{
+    return i != NULL ? i->key_id : NULL;
+}
+
+struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i)
+{
+    return i != NULL ? i->next : NULL;
+}
+
+struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i)
+{
+    return i != NULL ? i->cert_user_objs : NULL;
+}
+
+void sss_cai_set_cert_user_objs(struct cert_auth_info *i,
+                                struct ldb_result *cert_user_objs)
+{
+    if (i->cert_user_objs != NULL) {
+        talloc_free(i->cert_user_objs);
+    }
+    i->cert_user_objs = talloc_steal(i, cert_user_objs);
+}
+
+void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count,
+                         size_t *_cert_user_count)
+{
+    struct cert_auth_info *c;
+    struct cert_auth_info *tmp;
+    size_t cert_count = 0;
+    size_t cert_user_count = 0;
+    struct ldb_result *user_objs;
+
+    DLIST_FOR_EACH_SAFE(c, tmp, *list) {
+        user_objs = sss_cai_get_cert_user_objs(c);
+        if (user_objs != NULL) {
+            cert_count++;
+            cert_user_count += user_objs->count;
+        } else {
+            DLIST_REMOVE(*list, c);
+        }
+    }
+
+    if (_cert_count != NULL) {
+        *_cert_count = cert_count;
+    }
+
+    if (_cert_user_count != NULL) {
+        *_cert_user_count = cert_user_count;
+    }
+
+    return;
+}
+
 errno_t p11_child_init(struct pam_ctx *pctx)
 {
     return child_debug_init(P11_CHILD_LOG_FILE, &pctx->p11_child_debug_fd);
@@ -566,39 +636,71 @@ static void p11_child_timeout(struct tevent_context *ev,
 }
 
 errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
-                            char **cert, char **token_name, char **module_name,
-                            char **key_id)
+                            struct cert_auth_info **cert_list)
 {
+    struct cert_auth_info *tmp_cert_auth_info;
     struct pam_check_cert_state *state =
                               tevent_req_data(req, struct pam_check_cert_state);
 
     TEVENT_REQ_RETURN_ON_ERROR(req);
 
-    if (state->cert_list == NULL) {
-        *token_name = NULL;
-        *cert = NULL;
-        *module_name = NULL;
-        *key_id = NULL;
+    if (cert_list != NULL) {
+        DLIST_FOR_EACH(tmp_cert_auth_info, state->cert_list) {
+            talloc_steal(mem_ctx, tmp_cert_auth_info);
+        }
+
+        *cert_list = state->cert_list;
     }
 
-    if (cert != NULL) {
-        *cert = (state->cert_list == NULL) ? NULL
-                                : talloc_steal(mem_ctx, state->cert_list->cert);
+    return EOK;
+}
+
+static errno_t pack_cert_data(TALLOC_CTX *mem_ctx, const char *sysdb_username,
+                              struct cert_auth_info *cert_info,
+                              uint8_t **_msg, size_t *_msg_len)
+{
+    uint8_t *msg = NULL;
+    size_t msg_len;
+    const char *token_name;
+    const char *module_name;
+    const char *key_id;
+    size_t user_len;
+    size_t token_len;
+    size_t module_len;
+    size_t key_id_len;
+    const char *username = "";
+
+    if (sysdb_username != NULL) {
+        username = sysdb_username;
     }
 
-    if (token_name != NULL) {
-        *token_name = (state->cert_list == NULL) ? NULL
-                          : talloc_steal(mem_ctx, state->cert_list->token_name);
+    token_name = sss_cai_get_token_name(cert_info);
+    module_name = sss_cai_get_module_name(cert_info);
+    key_id = sss_cai_get_key_id(cert_info);
+
+    user_len = strlen(username) + 1;
+    token_len = strlen(token_name) + 1;
+    module_len = strlen(module_name) + 1;
+    key_id_len = strlen(key_id) + 1;
+    msg_len = user_len + token_len + module_len + key_id_len;
+
+    msg = talloc_zero_size(mem_ctx, msg_len);
+    if (msg == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n");
+        return ENOMEM;
     }
 
-    if (module_name != NULL) {
-        *module_name = (state->cert_list == NULL) ? NULL
-                         : talloc_steal(mem_ctx, state->cert_list->module_name);
+    memcpy(msg, username, user_len);
+    memcpy(msg + user_len, token_name, token_len);
+    memcpy(msg + user_len + token_len, module_name, module_len);
+    memcpy(msg + user_len + token_len + module_len, key_id, key_id_len);
+
+    if (_msg != NULL) {
+        *_msg = msg;
     }
 
-    if (key_id != NULL) {
-        *key_id = (state->cert_list == NULL) ? NULL
-                              : talloc_steal(mem_ctx, state->cert_list->key_id);
+    if (_msg_len != NULL) {
+        *_msg_len = msg_len;
     }
 
     return EOK;
@@ -613,18 +715,13 @@ errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
 #define PKCS11_LOGIN_TOKEN_ENV_NAME "PKCS11_LOGIN_TOKEN_NAME"
 
 errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
-                              const char *token_name, const char *module_name,
-                              const char *key_id, enum response_type type)
+                              struct cert_auth_info *cert_info,
+                              enum response_type type)
 {
     uint8_t *msg = NULL;
     char *env = NULL;
-    size_t user_len;
     size_t msg_len;
-    size_t slot_len;
-    size_t module_len;
-    size_t key_id_len;
     int ret;
-    const char *username = "";
 
     if (type != SSS_PAM_CERT_INFO && type != SSS_PAM_CERT_INFO_WITH_HINT) {
         DEBUG(SSSDBG_CRIT_FAILURE, "Invalid response type [%d].\n", type);
@@ -632,26 +729,14 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
     }
 
     if ((type == SSS_PAM_CERT_INFO && sysdb_username == NULL)
-            || token_name == NULL || module_name == NULL || key_id == NULL) {
+            || cert_info == NULL
+            || sss_cai_get_token_name(cert_info) == NULL
+            || sss_cai_get_module_name(cert_info) == NULL
+            || sss_cai_get_key_id(cert_info) == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n");
         return EINVAL;
     }
 
-    if (sysdb_username != NULL) {
-        username = sysdb_username;
-    }
-    user_len = strlen(username) + 1;
-    slot_len = strlen(token_name) + 1;
-    module_len = strlen(module_name) + 1;
-    key_id_len = strlen(key_id) + 1;
-    msg_len = user_len + slot_len + module_len + key_id_len;
-
-    msg = talloc_zero_size(pd, msg_len);
-    if (msg == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n");
-        return ENOMEM;
-    }
-
     /* sysdb_username is a fully-qualified name which is used by pam_sss when
      * prompting the user for the PIN and as login name if it wasn't set by
      * the PAM caller but has to be determined based on the inserted
@@ -659,10 +744,12 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
      * re_expression config option was set in a way that user@domain cannot be
      * handled anymore some more logic has to be added here. But for the time
      * being I think using sysdb_username is fine. */
-    memcpy(msg, username, user_len);
-    memcpy(msg + user_len, token_name, slot_len);
-    memcpy(msg + user_len + slot_len, module_name, module_len);
-    memcpy(msg + user_len + slot_len + module_len, key_id, key_id_len);
+
+    ret = pack_cert_data(pd, sysdb_username, cert_info, &msg, &msg_len);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "pack_cert_data failed.\n");
+        return ret;
+    }
 
     ret = pam_add_response(pd, type, msg_len, msg);
     talloc_free(msg);
@@ -674,7 +761,7 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
 
     if (strcmp(pd->service, "gdm-smartcard") == 0) {
         env = talloc_asprintf(pd, "%s=%s", PKCS11_LOGIN_TOKEN_ENV_NAME,
-                              token_name);
+                              sss_cai_get_token_name(cert_info));
         if (env == NULL) {
             DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
             return ENOMEM;
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
index 351067eb664431cda159f73590de772920504380..6adbc15f580997c41a02819d0d6aa253f2d5a64b 100644
--- a/src/tests/cmocka/test_pam_srv.c
+++ b/src/tests/cmocka/test_pam_srv.c
@@ -47,6 +47,9 @@
 #define NSS_DB_PATH TESTS_PATH
 #define NSS_DB "sql:"NSS_DB_PATH
 
+#define NSS_DB_PATH_2CERTS TESTS_PATH "_2certs"
+#define NSS_DB_2CERTS "sql:"NSS_DB_PATH_2CERTS
+
 #define TEST_TOKEN_NAME "SSSD Test Token"
 #define TEST_MODULE_NAME "NSS-Internal"
 #define TEST_KEY_ID "A5EF7DEE625CA5996C8D1BA7D036708161FD49E7"
@@ -74,6 +77,28 @@
 "8Z+9gqZhCa7FEKJOPNR9RVtJs0qUUutMZrp1zpyx0GTmXQBA7LbgPxy8L68uymEQ" \
 "XyQBwOYRORlnfGyu+Yc9c3E0Wx8Tlznz0lqPR9g="
 
+#define TEST2_KEY_ID "C8D60E009EB195D01A7083EE1D5419251AA87C2C"
+#define TEST_TOKEN_2ND_CERT \
+"MIIDazCCAlOgAwIBAgIBBzANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQKDAlJUEEu" \
+"REVWRUwxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNjA1MjMx" \
+"NDEzMDFaFw0xODA1MTMxNDEzMDFaMCUxEjAQBgNVBAoMCUlQQS5ERVZFTDEPMA0G" \
+"A1UEAwwGSVBBIFJBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3abE" \
+"8LmIc6QN16VVxsMlN/rrCOoZKyyJolSzpP4+K66t+KZUiW/1j1MZogjyYyD39U1F" \
+"zpa2H+pID74XYrdiqP7sp+uE9/k2XOv/nN3FobXDt+fSINLDriCmxNhUZqpgo2uq" \
+"Mmka+yx2iJZwkntEoJTcd3aynoa2Sa2ZZbkMBy5p6/pUQKwnD6scOwe6mUDppIBK" \
+"+ZZRm+u/NDdIRFI5wfKLRR1r/ONaJA9nz1TxSEsgLsjG/1m+Zbb6lGG4pePIFkQ9" \
+"Iotpi64obBh93oIxzQR29lBG/FMjQVHlPIbx+xuGx11Vtp5pAomgFz0HRrj0leI7" \
+"bROE+jnC/VGPLQD2aQIDAQABo4GWMIGTMB8GA1UdIwQYMBaAFPci/0Km5D/L5z7Y" \
+"qwEc7E1/GwgcMEEGCCsGAQUFBwEBBDUwMzAxBggrBgEFBQcwAYYlaHR0cDovL2lw" \
+"YS1kZXZlbC5pcGEuZGV2ZWw6ODAvY2Evb2NzcDAOBgNVHQ8BAf8EBAMCBPAwHQYD" \
+"VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBg" \
+"4Sppx2C3eXPJ4Pd9XElkQPOaBReXf1vV0uk/GlK+rG+aAqAkA2Lryx5PK/iAuzAU" \
+"M6JUpELuQYgqugoCgBXMgsMlpAO/0C3CFq4ZH3KgIsRlRngKPrt6RG0UPMRD1CE2" \
+"tSVkwUWvyK83lDiu2BbWDXyMyz5eZOlp7uHusf5BKvob8jEndHj1YzaNTmVSsDM5" \
+"kiIwf8qgFhsO1HCq08PtAnbVHhqkcvnmIJN98eNWNfTKodDmFVbN8gB0wK+WB5ii" \
+"WVOw7+3/zF1QgqnYX3t+kPLRryip/wvTZkzXWwMNj/W6UHgjNF/4gWGoBgCHu+u3" \
+"EvjMmbVSrEkesibpGQS5"
+
 
 static char CACHED_AUTH_TIMEOUT_STR[] = "4";
 static const int CACHED_AUTH_TIMEOUT = 4;
@@ -111,6 +136,13 @@ static errno_t setup_nss_db(void)
         return ret;
     }
 
+    ret = mkdir(NSS_DB_PATH_2CERTS, 0775);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to create " NSS_DB_PATH_2CERTS ".\n");
+        return ret;
+    }
+
     child_pid = fork();
     if (child_pid == 0) { /* child */
         ret = execlp("certutil", "certutil", "-N", "--empty-password", "-d",
@@ -127,6 +159,22 @@ static errno_t setup_nss_db(void)
         return ret;
     }
 
+    child_pid = fork();
+    if (child_pid == 0) { /* child */
+        ret = execlp("certutil", "certutil", "-N", "--empty-password", "-d",
+                     NSS_DB_2CERTS, NULL);
+        if (ret == -1) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "execl() failed.\n");
+            exit(-1);
+        }
+    } else if (child_pid > 0) {
+        wait(&status);
+    } else {
+        ret = errno;
+        DEBUG(SSSDBG_FATAL_FAILURE, "fork() failed\n");
+        return ret;
+    }
+
     fp = fopen(NSS_DB_PATH"/pkcs11.txt", "w");
     if (fp == NULL) {
         DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n");
@@ -148,6 +196,27 @@ static errno_t setup_nss_db(void)
         return ret;
     }
 
+    fp = fopen(NSS_DB_PATH_2CERTS"/pkcs11.txt", "w");
+    if (fp == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n");
+        return ret;
+    }
+    ret = fprintf(fp, "library=libsoftokn3.so\nname=soft\n");
+    if (ret < 0) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n");
+        return ret;
+    }
+    ret = fprintf(fp, "parameters=configdir='sql:%s/src/tests/cmocka/p11_nssdb_2certs' dbSlotDescription='SSSD Test Slot' dbTokenDescription='SSSD Test Token' secmod='secmod.db' flags=readOnly \n\n", ABS_SRC_DIR);
+    if (ret < 0) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n");
+        return ret;
+    }
+    ret = fclose(fp);
+    if (ret != 0) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fclose() failed.\n");
+        return ret;
+    }
+
     return EOK;
 }
 
@@ -174,6 +243,26 @@ static void cleanup_nss_db(void)
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n");
     }
+
+    ret = unlink(NSS_DB_PATH_2CERTS"/cert9.db");
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove cert9.db.\n");
+    }
+
+    ret = unlink(NSS_DB_PATH_2CERTS"/key4.db");
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove key4.db.\n");
+    }
+
+    ret = unlink(NSS_DB_PATH_2CERTS"/pkcs11.txt");
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove pkcs11.db.\n");
+    }
+
+    ret = rmdir(NSS_DB_PATH_2CERTS);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n");
+    }
 }
 
 struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx)
@@ -749,7 +838,8 @@ static int test_pam_cert_check_gdm_smartcard(uint32_t status, uint8_t *body,
 }
 
 static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
-                                  enum response_type type, const char *name)
+                                  enum response_type type, const char *name,
+                                  const char *name2)
 {
     size_t rp = 0;
     uint32_t val;
@@ -763,7 +853,11 @@ static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
     if (name == NULL || *name == '\0') {
         assert_int_equal(val, 1);
     } else {
-        assert_int_equal(val, 2);
+        if (name2 == NULL || *name2 == '\0') {
+            assert_int_equal(val, 2);
+        } else {
+            assert_int_equal(val, 3);
+        }
 
         SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
         assert_int_equal(val, SSS_PAM_DOMAIN_NAME);
@@ -801,6 +895,33 @@ static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
     assert_string_equal(body + rp, TEST_KEY_ID);
     rp += sizeof(TEST_KEY_ID);
 
+    if (name2 != NULL && *name2 != '\0') {
+        SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+        assert_int_equal(val, type);
+
+        SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+        assert_int_equal(val, (strlen(name) + 1
+                                    + sizeof(TEST_TOKEN_NAME)
+                                    + sizeof(TEST_MODULE_NAME)
+                                    + sizeof(TEST2_KEY_ID)));
+
+        assert_int_equal(*(body + rp + strlen(name)), 0);
+        assert_string_equal(body + rp, name);
+        rp += strlen(name) + 1;
+
+        assert_int_equal(*(body + rp + sizeof(TEST_TOKEN_NAME) - 1), 0);
+        assert_string_equal(body + rp, TEST_TOKEN_NAME);
+        rp += sizeof(TEST_TOKEN_NAME);
+
+        assert_int_equal(*(body + rp + sizeof(TEST_MODULE_NAME) - 1), 0);
+        assert_string_equal(body + rp, TEST_MODULE_NAME);
+        rp += sizeof(TEST_MODULE_NAME);
+
+        assert_int_equal(*(body + rp + sizeof(TEST2_KEY_ID) - 1), 0);
+        assert_string_equal(body + rp, TEST2_KEY_ID);
+        rp += sizeof(TEST2_KEY_ID);
+    }
+
     assert_int_equal(rp, blen);
 
     return EOK;
@@ -809,7 +930,8 @@ static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
 static int test_pam_cert_check(uint32_t status, uint8_t *body, size_t blen)
 {
     return test_pam_cert_check_ex(status, body, blen,
-                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME);
+                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME,
+                                  NULL);
 }
 
 static int test_pam_cert_check_with_hint(uint32_t status, uint8_t *body,
@@ -817,14 +939,22 @@ static int test_pam_cert_check_with_hint(uint32_t status, uint8_t *body,
 {
     return test_pam_cert_check_ex(status, body, blen,
                                   SSS_PAM_CERT_INFO_WITH_HINT,
-                                  "pamuser@"TEST_DOM_NAME);
+                                  "pamuser@"TEST_DOM_NAME, NULL);
 }
 
 static int test_pam_cert_check_with_hint_no_user(uint32_t status, uint8_t *body,
                                                  size_t blen)
 {
     return test_pam_cert_check_ex(status, body, blen,
-                                  SSS_PAM_CERT_INFO_WITH_HINT, "");
+                                  SSS_PAM_CERT_INFO_WITH_HINT, "", NULL);
+}
+
+int test_pam_cert_check_2certs(uint32_t status, uint8_t *body,
+                                      size_t blen)
+{
+    return test_pam_cert_check_ex(status, body, blen,
+                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME,
+                                  "pamuser@"TEST_DOM_NAME);
 }
 
 static int test_pam_offline_chauthtok_check(uint32_t status,
@@ -1737,6 +1867,33 @@ static int test_lookup_by_cert_cb(void *pvt)
 
     return EOK;
 }
+int test_lookup_by_cert_cb_2nd_cert_same_user(void *pvt)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+    unsigned char *der = NULL;
+    size_t der_size;
+
+    test_lookup_by_cert_cb(pvt);
+
+    attrs = sysdb_new_attrs(pam_test_ctx);
+    assert_non_null(attrs);
+
+    der = sss_base64_decode(pam_test_ctx, TEST_TOKEN_2ND_CERT, &der_size);
+    assert_non_null(der);
+
+    ret = sysdb_attrs_add_mem(attrs, SYSDB_USER_MAPPED_CERT, der, der_size);
+    talloc_free(der);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_set_user_attr(pam_test_ctx->tctx->dom,
+                              pam_test_ctx->pam_user_fqdn,
+                              attrs,
+                              LDB_FLAG_MOD_ADD);
+    assert_int_equal(ret, EOK);
+
+    return EOK;
+}
 
 static int test_lookup_by_cert_double_cb(void *pvt)
 {
diff --git a/src/tests/whitespace_test b/src/tests/whitespace_test
index 799e35358b1d5ae4b10c4405068fb507cb234b6f..f055ed4c255db4001194844f45a9df7cda774b38 100755
--- a/src/tests/whitespace_test
+++ b/src/tests/whitespace_test
@@ -39,7 +39,7 @@ fi
 declare found_file=false
 while read file; do
     [[ $file == "src/config/testconfigs/noparse.api.conf" ]] && continue
-    [[ $file =~ ^src/tests/cmocka/p11_nssdb/.*db ]] && continue
+    [[ $file =~ ^src/tests/cmocka/p11_nssdb.*/.*db ]] && continue
     test `tail -c 1 $ABS_TOP_SRCDIR/$file` && \
         echo "Missing new line at the eof: $file" && \
         found_file=true
-- 
2.15.1