Blame SOURCES/0038-pam_sss-refactoring-use-struct-cert_auth_info.patch

9f2ebf
From cee84ed12721092bc40bc02dc66ce3efbb2bac74 Mon Sep 17 00:00:00 2001
9f2ebf
From: Sumit Bose <sbose@redhat.com>
9f2ebf
Date: Mon, 16 Oct 2017 14:13:10 +0200
9f2ebf
Subject: [PATCH 38/46] pam_sss: refactoring, use struct cert_auth_info
9f2ebf
MIME-Version: 1.0
9f2ebf
Content-Type: text/plain; charset=UTF-8
9f2ebf
Content-Transfer-Encoding: 8bit
9f2ebf
9f2ebf
Similar as in the PAM responder this patch replaces the individual
9f2ebf
certificate authentication related attributes by a struct which can be
9f2ebf
used as a list. With the pam_sss can handle multiple SSS_PAM_CERT_INFO
9f2ebf
message and place the data in individual list items.
9f2ebf
9f2ebf
If multiple certificates are returned before prompting for the PIN a
9f2ebf
dialog to select a certificate is shown to the users. If available a GDM
9f2ebf
PAM extension is used to let the user choose from a list. All coded
9f2ebf
needed at runtime to check if the extension is available and handle the
9f2ebf
data is provided by GDM as macros. This means that there are no
9f2ebf
additional run-time requirements.
9f2ebf
9f2ebf
Related to https://pagure.io/SSSD/sssd/issue/3560
9f2ebf
9f2ebf
Reviewed-by: Fabiano Fidêncio <fidencio@redhat.com>
9f2ebf
Tested-by: Scott Poore <spoore@redhat.com>
9f2ebf
(cherry picked from commit 122830e67472390b41edc73f0cfcd5c5705b726b)
9f2ebf
---
9f2ebf
 contrib/sssd.spec.in         |   9 +
9f2ebf
 src/external/pam.m4          |  12 ++
9f2ebf
 src/sss_client/pam_message.h |   8 +-
9f2ebf
 src/sss_client/pam_sss.c     | 439 ++++++++++++++++++++++++++++++++++---------
9f2ebf
 4 files changed, 370 insertions(+), 98 deletions(-)
9f2ebf
9f2ebf
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
9f2ebf
index 1ee64d5a2a64635984260fceced779f4804e8b31..d9323bf1a2d84f4219f8ab11886e5ce87b401c15 100644
9f2ebf
--- a/contrib/sssd.spec.in
9f2ebf
+++ b/contrib/sssd.spec.in
9f2ebf
@@ -121,6 +121,12 @@
9f2ebf
     %global with_kcm_option --without-kcm
9f2ebf
 %endif
9f2ebf
 
9f2ebf
+%if (0%{?fedora} >= 27 || (0%{?rhel} >= 7 && 0%{?rhel7_minor} > 4))
9f2ebf
+    %global with_gdm_pam_extensions 1
9f2ebf
+%else
9f2ebf
+    %global with_gdm_pam_extensions 0
9f2ebf
+%endif
9f2ebf
+
9f2ebf
 Name: @PACKAGE_NAME@
9f2ebf
 Version: @PACKAGE_VERSION@
9f2ebf
 Release: 0@PRERELEASE_VERSION@%{?dist}
9f2ebf
@@ -233,6 +239,9 @@ BuildRequires: libuuid-devel
9f2ebf
 BuildRequires: jansson-devel
9f2ebf
 BuildRequires: libcurl-devel
9f2ebf
 %endif
9f2ebf
+%if (0%{?with_gdm_pam_extensions} == 1)
9f2ebf
+BuildRequires: gdm-devel
9f2ebf
+%endif
9f2ebf
 
9f2ebf
 %description
9f2ebf
 Provides a set of daemons to manage access to remote directories and
9f2ebf
diff --git a/src/external/pam.m4 b/src/external/pam.m4
9f2ebf
index 4776b6ae338409f0a2729dfc4cf5962463a40dfd..0dc7f19d0df6a4588cf893ecff6e518111462433 100644
9f2ebf
--- a/src/external/pam.m4
9f2ebf
+++ b/src/external/pam.m4
9f2ebf
@@ -27,3 +27,15 @@ AC_CHECK_FUNCS(pam_modutil_getlogin pam_vsyslog)
9f2ebf
 
9f2ebf
 dnl restore LIBS
9f2ebf
 LIBS="$save_LIBS"
9f2ebf
+
9f2ebf
+PKG_CHECK_MODULES([GDM_PAM_EXTENSIONS], [gdm-pam-extensions],
9f2ebf
+                  [found_gdm_pam_extensions=yes],
9f2ebf
+                  [AC_MSG_NOTICE([gdm-pam-extensions were not found. gdm support
9f2ebf
+for multiple certificates will not be build.
9f2ebf
+])])
9f2ebf
+
9f2ebf
+AC_SUBST(GDM_PAM_EXTENSIONS_CFLAGS)
9f2ebf
+
9f2ebf
+AS_IF([test x"$found_gdm_pam_extensions" = xyes],
9f2ebf
+      [AC_DEFINE_UNQUOTED(HAVE_GDM_PAM_EXTENSIONS, 1,
9f2ebf
+                          [Build with gdm-pam-extensions support])])
9f2ebf
diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
9f2ebf
index f215392f6879f01a0ca12abc8807bac5fc1f1cbb..11526a80a767ff5602b194d14765ff261e8f9707 100644
9f2ebf
--- a/src/sss_client/pam_message.h
9f2ebf
+++ b/src/sss_client/pam_message.h
9f2ebf
@@ -29,6 +29,8 @@
9f2ebf
 
9f2ebf
 #include "sss_client/sss_cli.h"
9f2ebf
 
9f2ebf
+struct cert_auth_info;
9f2ebf
+
9f2ebf
 struct pam_items {
9f2ebf
     const char *pam_service;
9f2ebf
     const char *pam_user;
9f2ebf
@@ -59,11 +61,9 @@ struct pam_items {
9f2ebf
     char *first_factor;
9f2ebf
     bool password_prompting;
9f2ebf
 
9f2ebf
-    char *cert_user;
9f2ebf
-    char *token_name;
9f2ebf
-    char *module_name;
9f2ebf
-    char *key_id;
9f2ebf
     bool user_name_hint;
9f2ebf
+    struct cert_auth_info *cert_list;
9f2ebf
+    struct cert_auth_info *selected_cert;
9f2ebf
 };
9f2ebf
 
9f2ebf
 int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer);
9f2ebf
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
9f2ebf
index 303809b9ea05b5a8709c05ae230d5f289b57de31..c147d4b3d76443d69e27eb2da042f8eebd1ae6ab 100644
9f2ebf
--- a/src/sss_client/pam_sss.c
9f2ebf
+++ b/src/sss_client/pam_sss.c
9f2ebf
@@ -36,6 +36,10 @@
9f2ebf
 #include <security/pam_modules.h>
9f2ebf
 #include <security/pam_appl.h>
9f2ebf
 
9f2ebf
+#ifdef HAVE_GDM_PAM_EXTENSIONS
9f2ebf
+#include <gdm/gdm-pam-extensions.h>
9f2ebf
+#endif
9f2ebf
+
9f2ebf
 #include "sss_pam_compat.h"
9f2ebf
 #include "sss_pam_macros.h"
9f2ebf
 
9f2ebf
@@ -43,6 +47,7 @@
9f2ebf
 #include "pam_message.h"
9f2ebf
 #include "util/atomic_io.h"
9f2ebf
 #include "util/authtok-utils.h"
9f2ebf
+#include "util/dlinklist.h"
9f2ebf
 
9f2ebf
 #include <libintl.h>
9f2ebf
 #define _(STRING) dgettext (PACKAGE, STRING)
9f2ebf
@@ -118,6 +123,40 @@ static void close_fd(pam_handle_t *pamh, void *ptr, int err)
9f2ebf
     sss_pam_close_fd();
9f2ebf
 }
9f2ebf
 
9f2ebf
+struct cert_auth_info {
9f2ebf
+    char *cert_user;
9f2ebf
+    char *cert;
9f2ebf
+    char *token_name;
9f2ebf
+    char *module_name;
9f2ebf
+    char *key_id;
9f2ebf
+    struct cert_auth_info *prev;
9f2ebf
+    struct cert_auth_info *next;
9f2ebf
+};
9f2ebf
+
9f2ebf
+static void free_cai(struct cert_auth_info *cai)
9f2ebf
+{
9f2ebf
+    if (cai != NULL) {
9f2ebf
+        free(cai->cert_user);
9f2ebf
+        free(cai->cert);
9f2ebf
+        free(cai->token_name);
9f2ebf
+        free(cai->key_id);
9f2ebf
+        free(cai);
9f2ebf
+    }
9f2ebf
+}
9f2ebf
+
9f2ebf
+static void free_cert_list(struct cert_auth_info *list)
9f2ebf
+{
9f2ebf
+    struct cert_auth_info *cai;
9f2ebf
+    struct cert_auth_info *cai_next;
9f2ebf
+
9f2ebf
+    if (list != NULL) {
9f2ebf
+        DLIST_FOR_EACH_SAFE(cai, cai_next, list) {
9f2ebf
+            DLIST_REMOVE(list, cai);
9f2ebf
+            free_cai(cai);
9f2ebf
+        }
9f2ebf
+    }
9f2ebf
+}
9f2ebf
+
9f2ebf
 static void overwrite_and_free_authtoks(struct pam_items *pi)
9f2ebf
 {
9f2ebf
     if (pi->pam_authtok != NULL) {
9f2ebf
@@ -158,17 +197,9 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
9f2ebf
     free(pi->otp_challenge);
9f2ebf
     pi->otp_challenge = NULL;
9f2ebf
 
9f2ebf
-    free(pi->cert_user);
9f2ebf
-    pi->cert_user = NULL;
9f2ebf
-
9f2ebf
-    free(pi->token_name);
9f2ebf
-    pi->token_name = NULL;
9f2ebf
-
9f2ebf
-    free(pi->module_name);
9f2ebf
-    pi->module_name = NULL;
9f2ebf
-
9f2ebf
-    free(pi->key_id);
9f2ebf
-    pi->key_id = NULL;
9f2ebf
+    free_cert_list(pi->cert_list);
9f2ebf
+    pi->cert_list = NULL;
9f2ebf
+    pi->selected_cert = NULL;
9f2ebf
 }
9f2ebf
 
9f2ebf
 static int null_strcmp(const char *s1, const char *s2) {
9f2ebf
@@ -821,6 +852,90 @@ static int eval_user_info_response(pam_handle_t *pamh, size_t buflen,
9f2ebf
     return ret;
9f2ebf
 }
9f2ebf
 
9f2ebf
+static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len,
9f2ebf
+                           size_t *p, const char **cert_user)
9f2ebf
+{
9f2ebf
+    struct cert_auth_info *cai = NULL;
9f2ebf
+    size_t offset;
9f2ebf
+    int ret;
9f2ebf
+
9f2ebf
+    if (buf[*p + (len - 1)] != '\0') {
9f2ebf
+        D(("cert info does not end with \\0."));
9f2ebf
+        return EINVAL;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    cai = calloc(1, sizeof(struct cert_auth_info));
9f2ebf
+    if (cai == NULL) {
9f2ebf
+        return ENOMEM;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    cai->cert_user = strdup((char *) &buf[*p]);
9f2ebf
+    if (cai->cert_user == NULL) {
9f2ebf
+        D(("strdup failed"));
9f2ebf
+        ret = ENOMEM;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+    if (cert_user != NULL) {
9f2ebf
+        *cert_user = cai->cert_user;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    offset = strlen(cai->cert_user) + 1;
9f2ebf
+    if (offset >= len) {
9f2ebf
+        D(("Cert message size mismatch"));
9f2ebf
+        ret = EINVAL;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    cai->token_name = strdup((char *) &buf[*p + offset]);
9f2ebf
+    if (cai->token_name == NULL) {
9f2ebf
+        D(("strdup failed"));
9f2ebf
+        ret = ENOMEM;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    offset += strlen(cai->token_name) + 1;
9f2ebf
+    if (offset >= len) {
9f2ebf
+        D(("Cert message size mismatch"));
9f2ebf
+        ret = EINVAL;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    cai->module_name = strdup((char *) &buf[*p + offset]);
9f2ebf
+    if (cai->module_name == NULL) {
9f2ebf
+        D(("strdup failed"));
9f2ebf
+        ret = ENOMEM;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    offset += strlen(cai->module_name) + 1;
9f2ebf
+    if (offset >= len) {
9f2ebf
+        D(("Cert message size mismatch"));
9f2ebf
+        ret = EINVAL;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    cai->key_id = strdup((char *) &buf[*p + offset]);
9f2ebf
+    if (cai->key_id == NULL) {
9f2ebf
+        D(("strdup failed"));
9f2ebf
+        ret = ENOMEM;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s]",
9f2ebf
+       cai->cert_user, cai->token_name, cai->module_name,
9f2ebf
+       cai->key_id));
9f2ebf
+
9f2ebf
+    DLIST_ADD(pi->cert_list, cai);
9f2ebf
+    ret = 0;
9f2ebf
+
9f2ebf
+done:
9f2ebf
+    if (ret != 0) {
9f2ebf
+        free_cai(cai);
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    return ret;
9f2ebf
+}
9f2ebf
+
9f2ebf
 static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
9f2ebf
                          struct pam_items *pi)
9f2ebf
 {
9f2ebf
@@ -832,6 +947,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
9f2ebf
     int32_t len;
9f2ebf
     int32_t pam_status;
9f2ebf
     size_t offset;
9f2ebf
+    const char *cert_user;
9f2ebf
 
9f2ebf
     if (buflen < (2*sizeof(int32_t))) {
9f2ebf
         D(("response buffer is too small"));
9f2ebf
@@ -988,27 +1104,21 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
9f2ebf
                     break;
9f2ebf
                 }
9f2ebf
 
9f2ebf
-                free(pi->cert_user);
9f2ebf
-                pi->cert_user = strdup((char *) &buf[p]);
9f2ebf
-                if (pi->cert_user == NULL) {
9f2ebf
-                    D(("strdup failed"));
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-
9f2ebf
-                if (type == SSS_PAM_CERT_INFO && *pi->cert_user == '\0') {
9f2ebf
-                    D(("Invalid CERT message"));
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-
9f2ebf
                 if (type == SSS_PAM_CERT_INFO_WITH_HINT) {
9f2ebf
                     pi->user_name_hint = true;
9f2ebf
                 } else {
9f2ebf
                     pi->user_name_hint = false;
9f2ebf
                 }
9f2ebf
 
9f2ebf
+                ret = parse_cert_info(pi, buf, len, &p, &cert_user);
9f2ebf
+                if (ret != 0) {
9f2ebf
+                    D(("Failed to parse cert info"));
9f2ebf
+                    break;
9f2ebf
+                }
9f2ebf
+
9f2ebf
                 if ((pi->pam_user == NULL || *(pi->pam_user) == '\0')
9f2ebf
-                        && *pi->cert_user != '\0') {
9f2ebf
-                    ret = pam_set_item(pamh, PAM_USER, pi->cert_user);
9f2ebf
+                        && *cert_user != '\0') {
9f2ebf
+                    ret = pam_set_item(pamh, PAM_USER, cert_user);
9f2ebf
                     if (ret != PAM_SUCCESS) {
9f2ebf
                         D(("Failed to set PAM_USER during "
9f2ebf
                            "Smartcard authentication [%s]",
9f2ebf
@@ -1027,59 +1137,6 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
9f2ebf
 
9f2ebf
                     pi->pam_user_size = strlen(pi->pam_user) + 1;
9f2ebf
                 }
9f2ebf
-
9f2ebf
-                offset = strlen(pi->cert_user) + 1;
9f2ebf
-                if (offset >= len) {
9f2ebf
-                    D(("Cert message size mismatch"));
9f2ebf
-                    free(pi->cert_user);
9f2ebf
-                    pi->cert_user = NULL;
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-                free(pi->token_name);
9f2ebf
-                pi->token_name = strdup((char *) &buf[p + offset]);
9f2ebf
-                if (pi->token_name == NULL) {
9f2ebf
-                    D(("strdup failed"));
9f2ebf
-                    free(pi->cert_user);
9f2ebf
-                    pi->cert_user = NULL;
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-
9f2ebf
-                offset += strlen(pi->token_name) + 1;
9f2ebf
-                if (offset >= len) {
9f2ebf
-                    D(("Cert message size mismatch"));
9f2ebf
-                    free(pi->cert_user);
9f2ebf
-                    pi->cert_user = NULL;
9f2ebf
-                    free(pi->token_name);
9f2ebf
-                    pi->token_name = NULL;
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-                free(pi->module_name);
9f2ebf
-                pi->module_name = strdup((char *) &buf[p + offset]);
9f2ebf
-                if (pi->module_name == NULL) {
9f2ebf
-                    D(("strdup failed"));
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-
9f2ebf
-                offset += strlen(pi->module_name) + 1;
9f2ebf
-                if (offset >= len) {
9f2ebf
-                    D(("Cert message size mismatch"));
9f2ebf
-                    free(pi->cert_user);
9f2ebf
-                    pi->cert_user = NULL;
9f2ebf
-                    free(pi->token_name);
9f2ebf
-                    pi->token_name = NULL;
9f2ebf
-                    free(pi->module_name);
9f2ebf
-                    pi->module_name = NULL;
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-                free(pi->key_id);
9f2ebf
-                pi->key_id = strdup((char *) &buf[p + offset]);
9f2ebf
-                if (pi->key_id == NULL) {
9f2ebf
-                    D(("strdup failed"));
9f2ebf
-                    break;
9f2ebf
-                }
9f2ebf
-                D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s]",
9f2ebf
-                    pi->cert_user, pi->token_name, pi->module_name,
9f2ebf
-                    pi->key_id));
9f2ebf
                 break;
9f2ebf
             case SSS_PASSWORD_PROMPTING:
9f2ebf
                 D(("Password prompting available."));
9f2ebf
@@ -1175,10 +1232,8 @@ static int get_pam_items(pam_handle_t *pamh, uint32_t flags,
9f2ebf
     pi->otp_challenge = NULL;
9f2ebf
     pi->password_prompting = false;
9f2ebf
 
9f2ebf
-    pi->cert_user = NULL;
9f2ebf
-    pi->token_name = NULL;
9f2ebf
-    pi->module_name = NULL;
9f2ebf
-    pi->key_id = NULL;
9f2ebf
+    pi->cert_list = NULL;
9f2ebf
+    pi->selected_cert = NULL;
9f2ebf
 
9f2ebf
     return PAM_SUCCESS;
9f2ebf
 }
9f2ebf
@@ -1484,6 +1539,184 @@ done:
9f2ebf
 
9f2ebf
 #define SC_PROMPT_FMT "PIN for %s"
9f2ebf
 
9f2ebf
+#ifndef discard_const
9f2ebf
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
9f2ebf
+#endif
9f2ebf
+
9f2ebf
+#define CERT_SEL_PROMPT_FMT "Certificate: %s"
9f2ebf
+#define SEL_TITLE discard_const("Please select a certificate")
9f2ebf
+
9f2ebf
+static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi)
9f2ebf
+{
9f2ebf
+#ifdef HAVE_GDM_PAM_EXTENSIONS
9f2ebf
+    int ret;
9f2ebf
+    size_t cert_count = 0;
9f2ebf
+    size_t c;
9f2ebf
+    const struct pam_conv *conv;
9f2ebf
+    struct cert_auth_info *cai;
9f2ebf
+    GdmPamExtensionChoiceListRequest *request = NULL;
9f2ebf
+    GdmPamExtensionChoiceListResponse *response = NULL;
9f2ebf
+    struct pam_message prompt_message;
9f2ebf
+    const struct pam_message *prompt_messages[1];
9f2ebf
+    struct pam_response *reply = NULL;
9f2ebf
+    char *prompt;
9f2ebf
+
9f2ebf
+    if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) {
9f2ebf
+        return ENOTSUP;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    if (pi->cert_list == NULL) {
9f2ebf
+        return EINVAL;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    DLIST_FOR_EACH(cai, pi->cert_list) {
9f2ebf
+        cert_count++;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv;;
9f2ebf
+    if (ret != PAM_SUCCESS) {
9f2ebf
+        ret = EIO;
9f2ebf
+        return ret;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count));
9f2ebf
+    if (request == NULL) {
9f2ebf
+        ret = ENOMEM;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+    GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count);
9f2ebf
+
9f2ebf
+    c = 0;
9f2ebf
+    DLIST_FOR_EACH(cai, pi->cert_list) {
9f2ebf
+        ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->key_id);
9f2ebf
+        if (ret == -1) {
9f2ebf
+            ret = ENOMEM;
9f2ebf
+            goto done;
9f2ebf
+        }
9f2ebf
+        request->list.items[c].key = cai->key_id;
9f2ebf
+        request->list.items[c++].text = prompt;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request,
9f2ebf
+                                                       &prompt_message);
9f2ebf
+    prompt_messages[0] = &prompt_message;
9f2ebf
+
9f2ebf
+    ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr);
9f2ebf
+    if (ret != PAM_SUCCESS) {
9f2ebf
+        ret = EIO;
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    ret = EIO;
9f2ebf
+    response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply);
9f2ebf
+    if (response->key == NULL) {
9f2ebf
+        goto done;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    DLIST_FOR_EACH(cai, pi->cert_list) {
9f2ebf
+        if (strcmp(response->key, cai->key_id) == 0) {
9f2ebf
+            pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id);
9f2ebf
+            pi->selected_cert = cai;
9f2ebf
+            ret = 0;
9f2ebf
+            break;
9f2ebf
+        }
9f2ebf
+    }
9f2ebf
+
9f2ebf
+done:
9f2ebf
+    if (request != NULL) {
9f2ebf
+        for (c = 0; c < cert_count; c++) {
9f2ebf
+            free(discard_const(request->list.items[c++].text));
9f2ebf
+        }
9f2ebf
+        free(request);
9f2ebf
+    }
9f2ebf
+    free(response);
9f2ebf
+
9f2ebf
+    return ret;
9f2ebf
+#else
9f2ebf
+    return ENOTSUP;
9f2ebf
+#endif
9f2ebf
+}
9f2ebf
+
9f2ebf
+#define TEXT_CERT_SEL_PROMPT_FMT "%s[%zu] Certificate: %s\n"
9f2ebf
+#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \
9f2ebf
+                                     "the corresponding number\n")
9f2ebf
+static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi)
9f2ebf
+{
9f2ebf
+    int ret;
9f2ebf
+    size_t cert_count = 0;
9f2ebf
+    size_t tries = 0;
9f2ebf
+    long int resp = -1;
9f2ebf
+    struct cert_auth_info *cai;
9f2ebf
+    char *prompt;
9f2ebf
+    char *tmp;
9f2ebf
+    char *answer;
9f2ebf
+    char *ep;
9f2ebf
+
9f2ebf
+    /* First check if gdm extension is supported */
9f2ebf
+    ret = prompt_multi_cert_gdm(pamh, pi);
9f2ebf
+    if (ret != ENOTSUP) {
9f2ebf
+        return ret;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    if (pi->cert_list == NULL) {
9f2ebf
+        return EINVAL;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    prompt = strdup(TEXT_SEL_TITLE);
9f2ebf
+    if (prompt == NULL) {
9f2ebf
+        return ENOMEM;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    DLIST_FOR_EACH(cai, pi->cert_list) {
9f2ebf
+        cert_count++;
9f2ebf
+        ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count,
9f2ebf
+                                                       cai->key_id);
9f2ebf
+        free(prompt);
9f2ebf
+        if (ret == -1) {
9f2ebf
+            return ENOMEM;
9f2ebf
+        }
9f2ebf
+
9f2ebf
+        prompt = tmp;
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    do {
9f2ebf
+        ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL,
9f2ebf
+                                  &answer);
9f2ebf
+        if (ret != PAM_SUCCESS) {
9f2ebf
+            D(("do_pam_conversation failed."));
9f2ebf
+            break;
9f2ebf
+        }
9f2ebf
+
9f2ebf
+        errno = 0;
9f2ebf
+        resp = strtol(answer, &ep, 10);
9f2ebf
+        if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) {
9f2ebf
+            /* do not free answer ealier because ep is pointing to it */
9f2ebf
+            free(answer);
9f2ebf
+            break;
9f2ebf
+        }
9f2ebf
+        free(answer);
9f2ebf
+        resp = -1;
9f2ebf
+    } while (++tries < 5);
9f2ebf
+    free(prompt);
9f2ebf
+
9f2ebf
+    pi->selected_cert = NULL;
9f2ebf
+    ret = ENOENT;
9f2ebf
+    if (resp > 0 && resp <= cert_count) {
9f2ebf
+        cert_count = 0;
9f2ebf
+        DLIST_FOR_EACH(cai, pi->cert_list) {
9f2ebf
+            cert_count++;
9f2ebf
+            if (resp == cert_count) {
9f2ebf
+                pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id);
9f2ebf
+                pi->selected_cert = cai;
9f2ebf
+                ret = 0;
9f2ebf
+                break;
9f2ebf
+            }
9f2ebf
+        }
9f2ebf
+    }
9f2ebf
+
9f2ebf
+    return ret;
9f2ebf
+}
9f2ebf
+
9f2ebf
 static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
9f2ebf
 {
9f2ebf
     int ret;
9f2ebf
@@ -1495,19 +1728,20 @@ static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
9f2ebf
     const struct pam_message *mesg[2] = { NULL, NULL };
9f2ebf
     struct pam_message m[2] = { { 0 }, { 0 } };
9f2ebf
     struct pam_response *resp = NULL;
9f2ebf
+    struct cert_auth_info *cai = pi->selected_cert;
9f2ebf
 
9f2ebf
-    if (pi->token_name == NULL || *pi->token_name == '\0') {
9f2ebf
+    if (cai == NULL || cai->token_name == NULL || *cai->token_name == '\0') {
9f2ebf
         return EINVAL;
9f2ebf
     }
9f2ebf
 
9f2ebf
-    size = sizeof(SC_PROMPT_FMT) + strlen(pi->token_name);
9f2ebf
+    size = sizeof(SC_PROMPT_FMT) + strlen(cai->token_name);
9f2ebf
     prompt = malloc(size);
9f2ebf
     if (prompt == NULL) {
9f2ebf
         D(("malloc failed."));
9f2ebf
         return ENOMEM;
9f2ebf
     }
9f2ebf
 
9f2ebf
-    ret = snprintf(prompt, size, SC_PROMPT_FMT, pi->token_name);
9f2ebf
+    ret = snprintf(prompt, size, SC_PROMPT_FMT, cai->token_name);
9f2ebf
     if (ret < 0 || ret >= size) {
9f2ebf
         D(("snprintf failed."));
9f2ebf
         free(prompt);
9f2ebf
@@ -1604,9 +1838,9 @@ static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
9f2ebf
         pi->pam_authtok_size=0;
9f2ebf
     } else {
9f2ebf
 
9f2ebf
-        ret = sss_auth_pack_sc_blob(answer, 0, pi->token_name, 0,
9f2ebf
-                                    pi->module_name, 0,
9f2ebf
-                                    pi->key_id, 0,
9f2ebf
+        ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0,
9f2ebf
+                                    cai->module_name, 0,
9f2ebf
+                                    cai->key_id, 0,
9f2ebf
                                     NULL, 0, &needed_size);
9f2ebf
         if (ret != EAGAIN) {
9f2ebf
             D(("sss_auth_pack_sc_blob failed."));
9f2ebf
@@ -1621,9 +1855,9 @@ static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
9f2ebf
             goto done;
9f2ebf
         }
9f2ebf
 
9f2ebf
-        ret = sss_auth_pack_sc_blob(answer, 0, pi->token_name, 0,
9f2ebf
-                                    pi->module_name, 0,
9f2ebf
-                                    pi->key_id, 0,
9f2ebf
+        ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0,
9f2ebf
+                                    cai->module_name, 0,
9f2ebf
+                                    cai->key_id, 0,
9f2ebf
                                     (uint8_t *) pi->pam_authtok, needed_size,
9f2ebf
                                     &needed_size);
9f2ebf
         if (ret != EOK) {
9f2ebf
@@ -1786,7 +2020,17 @@ static int get_authtok_for_authentication(pam_handle_t *pamh,
9f2ebf
                 ret = prompt_2fa(pamh, pi, _("First Factor: "),
9f2ebf
                                  _("Second Factor: "));
9f2ebf
             }
9f2ebf
-        } else if (pi->token_name != NULL && *(pi->token_name) != '\0') {
9f2ebf
+        } else if (pi->cert_list != NULL) {
9f2ebf
+            if (pi->cert_list->next == NULL) {
9f2ebf
+                /* Only one certificate */
9f2ebf
+                pi->selected_cert = pi->cert_list;
9f2ebf
+            } else {
9f2ebf
+                ret = prompt_multi_cert(pamh, pi);
9f2ebf
+                if (ret != 0) {
9f2ebf
+                    D(("Failed to select certificate"));
9f2ebf
+                    return PAM_AUTHTOK_ERR;
9f2ebf
+                }
9f2ebf
+            }
9f2ebf
             ret = prompt_sc_pin(pamh, pi);
9f2ebf
         } else {
9f2ebf
             ret = prompt_password(pamh, pi, _("Password: "));
9f2ebf
@@ -1905,14 +2149,21 @@ static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi,
9f2ebf
     char *prompt = NULL;
9f2ebf
     size_t size;
9f2ebf
     char *answer = NULL;
9f2ebf
+    /* TODO: check multiple cert case */
9f2ebf
+    struct cert_auth_info *cai = pi->cert_list;
9f2ebf
+
9f2ebf
+    if (cai == NULL) {
9f2ebf
+        D(("No certificate information available"));
9f2ebf
+        return EINVAL;
9f2ebf
+    }
9f2ebf
 
9f2ebf
     login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME");
9f2ebf
     if (login_token_name == NULL) {
9f2ebf
         return PAM_SUCCESS;
9f2ebf
     }
9f2ebf
 
9f2ebf
-    while (pi->token_name == NULL
9f2ebf
-            || strcmp(login_token_name, pi->token_name) != 0) {
9f2ebf
+    while (cai->token_name == NULL
9f2ebf
+               || strcmp(login_token_name, cai->token_name) != 0) {
9f2ebf
         size = sizeof(SC_ENTER_FMT) + strlen(login_token_name);
9f2ebf
         prompt = malloc(size);
9f2ebf
         if (prompt == NULL) {
9f2ebf
-- 
9f2ebf
2.13.6
9f2ebf