sssd/0044-pam_sss-refactoring-us...

680 lines
21 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 55deead9f2a98c3ba1fd5754bd38203b6c02b6a1 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 16 Oct 2017 14:13:10 +0200
Subject: [PATCH 44/79] 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>
---
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 4aafd1832b67161ff1c25a4e9ad689586a227a25..c716efdce05ab7b9178be66f34d09124c78071b5 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.15.1