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