sssd/0001-passwordless-gdm.patch
Iker Pedrosa 609764b7ba Rebase to sssd-2.9.9
Resolves: RHEL-173741 - SSSD Rebase for RHEL 9.9
Resolves: RHEL-173740 - CVE-2026-6245 sssd: out-of-bounds read in the sssd
Resolves: RHEL-150439 - Poor performance of `BE_REQ_INITGROUPS` handling by 'sssd_be' (LDAP RFC2307, no nested groups)
Resolves: RHEL-143416 - Performance impact with enumerate in SSSD > 2.7.3
Resolves: RHEL-152067 - [RFE] SSSD PAM: Support Microsoft AD PKINIT authentication indicator (ms-pkca) for pam_gssapi_indicators_map
Resolves: RHEL-173742 - Man page update: man sssd_krb5_localauth_plugin – Missing disable = an2ln
Resolves: RHEL-173743 - Concurrent child processes trigger unnecessarily backtrace
Resolves: RHEL-173744 - Detect foreign security principals in AD group members and silently ignore them
Resolves: RHEL-156519 - GDM Support for IdM IdP feature and MFA [SSSD]
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
2026-05-06 16:31:56 +02:00

9566 lines
349 KiB
Diff

From 7d47dd3bd38d068d848d24fa36886173ce9568bf Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Thu, 18 Jan 2024 15:46:33 +0100
Subject: [PATCH 01/36] util: implement pam_get_response_data()
This API gets the selected response type data from the response_data
linked list. Includes unit tests.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
Signed-off-by: Ray Strode <halfline@redhat.com>
---
Makefile.am | 18 +++
src/tests/cmocka/test_sss_pam_data.c | 171 +++++++++++++++++++++++++++
src/util/sss_pam_data.c | 34 ++++++
src/util/sss_pam_data.h | 17 +++
4 files changed, 240 insertions(+)
create mode 100644 src/tests/cmocka/test_sss_pam_data.c
diff --git a/Makefile.am b/Makefile.am
index 60bec863d..ab8a3fe9d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -298,6 +298,7 @@ if HAVE_CMOCKA
test_sssd_krb5_locator_plugin \
test_confdb \
test_krb5_idp_plugin \
+ test_sss_pam_data \
$(NULL)
@@ -2694,6 +2695,23 @@ if BUILD_PASSKEY
pam_srv_tests_SOURCES += src/responder/pam/pamsrv_passkey.c
endif # BUILD_PASSKEY
+test_sss_pam_data_SOURCES = \
+ src/util/sss_pam_data.c \
+ src/tests/cmocka/test_sss_pam_data.c \
+ $(NULL)
+test_sss_pam_data_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(NULL)
+test_sss_pam_data_LDFLAGS = \
+ $(NULL)
+test_sss_pam_data_LDADD = \
+ $(CMOCKA_LIBS) \
+ $(SSSD_LIBS) \
+ $(SSSD_INTERNAL_LTLIBS) \
+ $(TALLOC_LIBS) \
+ libsss_test_common.la \
+ $(NULL)
+
EXTRA_ssh_srv_tests_DEPENDENCIES = \
$(ldblib_LTLIBRARIES) \
$(NULL)
diff --git a/src/tests/cmocka/test_sss_pam_data.c b/src/tests/cmocka/test_sss_pam_data.c
new file mode 100644
index 000000000..442b37372
--- /dev/null
+++ b/src/tests/cmocka/test_sss_pam_data.c
@@ -0,0 +1,171 @@
+/*
+ SSSD
+
+ Unit test for sss_pam_data
+
+ Authors:
+ Iker Pedrosa <ipedrosa@redhat.com>
+
+ Copyright (C) 2024 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+
+#include "util/sss_pam_data.h"
+
+#define PASSKEY_PIN "1234"
+#define OAUTH2_URI "short.url.com/tmp\0"
+#define OAUTH2_CODE "1234-5678"
+#define OAUTH2_STR OAUTH2_URI OAUTH2_CODE
+#define CCACHE_NAME "KRB5CCNAME=KCM:"
+
+
+/***********************
+ * TEST
+ **********************/
+void test_pam_get_response_data_not_found(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ uint8_t *buf = NULL;
+ int32_t len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->resp_list = NULL;
+ pam_add_response(pd, SSS_PAM_PASSKEY_INFO, 5, discard_const(PASSKEY_PIN));
+
+ ret = pam_get_response_data(test_ctx, pd, SSS_PAM_OAUTH2_INFO, &buf, &len);
+ assert_int_equal(ret, ENOENT);
+
+ talloc_free(test_ctx);
+}
+
+void test_pam_get_response_data_one_element(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ uint8_t *buf = NULL;
+ int32_t len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->resp_list = NULL;
+ pam_add_response(pd, SSS_PAM_PASSKEY_INFO, 5, discard_const(PASSKEY_PIN));
+
+ ret = pam_get_response_data(test_ctx, pd, SSS_PAM_PASSKEY_INFO, &buf, &len);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(len, strlen(PASSKEY_PIN) + 1);
+ assert_string_equal((const char*) buf, PASSKEY_PIN);
+
+ talloc_free(test_ctx);
+}
+
+void test_pam_get_response_data_three_elements(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ uint8_t *buf = NULL;
+ int32_t len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->resp_list = NULL;
+ pam_add_response(pd, SSS_PAM_PASSKEY_INFO, 5, discard_const(PASSKEY_PIN));
+ len = strlen(OAUTH2_URI)+1+strlen(OAUTH2_CODE)+1;
+ pam_add_response(pd, SSS_PAM_OAUTH2_INFO, len, discard_const(OAUTH2_STR));
+ len = strlen(CCACHE_NAME) + 1;
+ pam_add_response(pd, SSS_PAM_ENV_ITEM, len, discard_const(CCACHE_NAME));
+
+ ret = pam_get_response_data(test_ctx, pd, SSS_PAM_ENV_ITEM, &buf, &len);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(len, strlen(CCACHE_NAME) + 1);
+ assert_string_equal((const char*) buf, CCACHE_NAME);
+
+ ret = pam_get_response_data(test_ctx, pd, SSS_PAM_OAUTH2_INFO, &buf, &len);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(len, strlen(OAUTH2_URI)+1+strlen(OAUTH2_CODE)+1);
+ assert_string_equal((const char*) buf, OAUTH2_URI);
+ assert_string_equal((const char*) buf+strlen(OAUTH2_URI)+1, OAUTH2_CODE);
+
+ ret = pam_get_response_data(test_ctx, pd, SSS_PAM_PASSKEY_INFO, &buf, &len);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(len, strlen(PASSKEY_PIN) + 1);
+ assert_string_equal((const char*) buf, PASSKEY_PIN);
+
+ talloc_free(test_ctx);
+}
+
+static void test_parse_supp_valgrind_args(void)
+{
+ /*
+ * The objective of this function is to filter the unit-test functions
+ * that trigger a valgrind memory leak and suppress them to avoid false
+ * positives.
+ */
+ DEBUG_CLI_INIT(debug_level);
+}
+
+int main(int argc, const char *argv[])
+{
+ poptContext pc;
+ int opt;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ POPT_TABLEEND
+ };
+
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_pam_get_response_data_not_found),
+ cmocka_unit_test(test_pam_get_response_data_one_element),
+ cmocka_unit_test(test_pam_get_response_data_three_elements),
+ };
+
+ /* Set debug level to invalid value so we can decide if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ test_parse_supp_valgrind_args();
+
+ /* Even though normally the tests should clean up after themselves
+ * they might not after a failed run. Remove the old DB to be sure */
+ tests_set_cwd();
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/src/util/sss_pam_data.c b/src/util/sss_pam_data.c
index f09b9c5eb..75421d8e0 100644
--- a/src/util/sss_pam_data.c
+++ b/src/util/sss_pam_data.c
@@ -203,3 +203,37 @@ int pam_add_response(struct pam_data *pd, enum response_type type,
return EOK;
}
+
+errno_t
+pam_get_response_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, int32_t type,
+ uint8_t **_buf, int32_t *_len)
+{
+ struct response_data *data = pd->resp_list;
+ struct response_data *match = NULL;
+ uint8_t *buf = NULL;
+ int ret;
+
+ while (data != NULL) {
+ if (data->type == type) match = data;
+
+ data = data->next;
+ }
+
+ if (match != NULL) {
+ buf = talloc_memdup(mem_ctx, match->data, match->len);
+ if (buf == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_buf = buf;
+ *_len = match->len;
+ ret = EOK;
+ goto done;
+ }
+
+ ret = ENOENT;
+
+done:
+ return ret;
+}
diff --git a/src/util/sss_pam_data.h b/src/util/sss_pam_data.h
index e9b90a8a4..a7efba791 100644
--- a/src/util/sss_pam_data.h
+++ b/src/util/sss_pam_data.h
@@ -96,4 +96,21 @@ int pam_add_response(struct pam_data *pd,
enum response_type type,
int len, const uint8_t *data);
+/**
+ * @brief Get the selected response type data from the response_data linked
+ * list
+ *
+ * @param[in] mem_ctx Memory context
+ * @param[in] pd Data structure containing the response_data linked list
+ * @param[in] type Response type
+ * @param[out] _buf Data wrapped inside response_data structure
+ * @param[out] _len Data length
+ *
+ * @return 0 if the data was obtained properly,
+ * error code otherwise.
+ */
+errno_t
+pam_get_response_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, int32_t type,
+ uint8_t **_buf, int32_t *_len);
+
#endif /* _SSS_PAM_DATA_H_ */
--
2.54.0
From ecd8ebbccd44d149010476199bc7904d5b3fa084 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 8 May 2024 10:46:47 +0200
Subject: [PATCH 02/36] sss_client: add EIdP to prompt_config structure
Integration with GDM requests two prompts for EIdP so adding them to
prompt_config structure. In addition, implement all the functions needed
to manipulate the structure for these new prompts. Finally, add
unit-tests for the new functions.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/sss_client/pam_sss_prompt_config.c | 146 +++++++++++++++++++++++++
src/sss_client/sss_cli.h | 5 +
src/tests/cmocka/test_prompt_config.c | 39 ++++++-
3 files changed, 185 insertions(+), 5 deletions(-)
diff --git a/src/sss_client/pam_sss_prompt_config.c b/src/sss_client/pam_sss_prompt_config.c
index f3360544b..891fcd60c 100644
--- a/src/sss_client/pam_sss_prompt_config.c
+++ b/src/sss_client/pam_sss_prompt_config.c
@@ -49,6 +49,11 @@ struct prompt_config_sc_pin {
char *prompt; /* Currently not used */
};
+struct prompt_config_eidp {
+ char *prompt_init;
+ char *prompt_link;
+};
+
struct prompt_config {
enum prompt_config_type type;
union {
@@ -57,6 +62,7 @@ struct prompt_config {
struct prompt_config_2fa_single two_fa_single;
struct prompt_config_passkey passkey;
struct prompt_config_sc_pin sc_pin;
+ struct prompt_config_eidp eidp;
} data;
};
@@ -116,6 +122,22 @@ const char *pc_get_passkey_inter_prompt(struct prompt_config *pc)
return NULL;
}
+const char *pc_get_eidp_init_prompt(struct prompt_config *pc)
+{
+ if (pc != NULL && (pc_get_type(pc) == PC_TYPE_EIDP)) {
+ return pc->data.eidp.prompt_init;
+ }
+ return NULL;
+}
+
+const char *pc_get_eidp_link_prompt(struct prompt_config *pc)
+{
+ if (pc != NULL && (pc_get_type(pc) == PC_TYPE_EIDP)) {
+ return pc->data.eidp.prompt_link;
+ }
+ return NULL;
+}
+
static void pc_free_passkey(struct prompt_config *pc)
{
if (pc != NULL && pc_get_type(pc) == PC_TYPE_PASSKEY) {
@@ -165,6 +187,17 @@ static void pc_free_sc_pin(struct prompt_config *pc)
return;
}
+static void pc_free_eidp(struct prompt_config *pc)
+{
+ if (pc != NULL && pc_get_type(pc) == PC_TYPE_EIDP) {
+ free(pc->data.eidp.prompt_init);
+ pc->data.eidp.prompt_init = NULL;
+ free(pc->data.eidp.prompt_link);
+ pc->data.eidp.prompt_link = NULL;
+ }
+ return;
+}
+
void pc_list_free(struct prompt_config **pc_list)
{
@@ -191,6 +224,9 @@ void pc_list_free(struct prompt_config **pc_list)
case PC_TYPE_PASSKEY:
pc_free_passkey(pc_list[c]);
break;
+ case PC_TYPE_EIDP:
+ pc_free_eidp(pc_list[c]);
+ break;
default:
return;
}
@@ -396,6 +432,53 @@ done:
return ret;
}
+errno_t pc_list_add_eidp(struct prompt_config ***pc_list,
+ const char *prompt_init, const char *prompt_link)
+{
+ struct prompt_config *pc;
+ int ret;
+
+ if (pc_list == NULL) {
+ return EINVAL;
+ }
+
+ pc = calloc(1, sizeof(struct prompt_config));
+ if (pc == NULL) {
+ return ENOMEM;
+ }
+
+ pc->type = PC_TYPE_EIDP;
+
+ pc->data.eidp.prompt_init = strdup(prompt_init != NULL ? prompt_init
+ : "");
+ if (pc->data.eidp.prompt_init == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ pc->data.eidp.prompt_link = strdup(prompt_link != NULL ? prompt_link
+ : "");
+ if (pc->data.eidp.prompt_link == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = pc_list_add_pc(pc_list, pc);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ free(pc->data.eidp.prompt_init);
+ free(pc->data.eidp.prompt_link);
+ free(pc);
+ }
+
+ return ret;
+}
+
errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
uint8_t **data)
{
@@ -435,6 +518,12 @@ errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
break;
case PC_TYPE_SC_PIN:
break;
+ case PC_TYPE_EIDP:
+ l += sizeof(uint32_t);
+ l += strlen(pc_list[c]->data.eidp.prompt_init);
+ l += sizeof(uint32_t);
+ l += strlen(pc_list[c]->data.eidp.prompt_link);
+ break;
default:
return EINVAL;
}
@@ -494,6 +583,18 @@ errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
break;
case PC_TYPE_SC_PIN:
break;
+ case PC_TYPE_EIDP:
+ SAFEALIGN_SET_UINT32(&d[rp],
+ strlen(pc_list[c]->data.eidp.prompt_init),
+ &rp);
+ safealign_memcpy(&d[rp], pc_list[c]->data.eidp.prompt_init,
+ strlen(pc_list[c]->data.eidp.prompt_init), &rp);
+ SAFEALIGN_SET_UINT32(&d[rp],
+ strlen(pc_list[c]->data.eidp.prompt_link),
+ &rp);
+ safealign_memcpy(&d[rp], pc_list[c]->data.eidp.prompt_link,
+ strlen(pc_list[c]->data.eidp.prompt_link), &rp);
+ break;
default:
free(d);
return EINVAL;
@@ -681,6 +782,51 @@ errno_t pc_list_from_response(int size, uint8_t *buf,
break;
case PC_TYPE_SC_PIN:
break;
+ case PC_TYPE_EIDP:
+ if (rp > size - sizeof(uint32_t)) {
+ ret = EINVAL;
+ goto done;
+ }
+ SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+ if (l > size || rp > size - l) {
+ ret = EINVAL;
+ goto done;
+ }
+ str = strndup((char *) buf + rp, l);
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ rp += l;
+
+ if (rp > size - sizeof(uint32_t)) {
+ free(str);
+ ret = EINVAL;
+ goto done;
+ }
+ SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+ if (l > size || rp > size - l) {
+ free(str);
+ ret = EINVAL;
+ goto done;
+ }
+ str2 = strndup((char *) buf + rp, l);
+ if (str2 == NULL) {
+ free(str);
+ ret = ENOMEM;
+ goto done;
+ }
+ rp += l;
+
+ ret = pc_list_add_eidp(&pl, str, str2);
+ free(str);
+ free(str2);
+ if (ret != EOK) {
+ goto done;
+ }
+ break;
default:
ret = EINVAL;
goto done;
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 29b496e1d..792c34cb9 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -664,6 +664,7 @@ enum prompt_config_type {
PC_TYPE_2FA_SINGLE,
PC_TYPE_PASSKEY,
PC_TYPE_SC_PIN,
+ PC_TYPE_EIDP,
PC_TYPE_LAST
};
@@ -676,6 +677,8 @@ const char *pc_get_2fa_2nd_prompt(struct prompt_config *pc);
const char *pc_get_2fa_single_prompt(struct prompt_config *pc);
const char *pc_get_passkey_inter_prompt(struct prompt_config *pc);
const char *pc_get_passkey_touch_prompt(struct prompt_config *pc);
+const char *pc_get_eidp_init_prompt(struct prompt_config *pc);
+const char *pc_get_eidp_link_prompt(struct prompt_config *pc);
errno_t pc_list_add_passkey(struct prompt_config ***pc_list,
const char *inter_prompt,
const char *touch_prompt);
@@ -686,6 +689,8 @@ errno_t pc_list_add_2fa(struct prompt_config ***pc_list,
const char *prompt_1st, const char *prompt_2nd);
errno_t pc_list_add_2fa_single(struct prompt_config ***pc_list,
const char *prompt);
+errno_t pc_list_add_eidp(struct prompt_config ***pc_list,
+ const char *prompt_init, const char *prompt_link);
errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
uint8_t **data);
errno_t pc_list_from_response(int size, uint8_t *buf,
diff --git a/src/tests/cmocka/test_prompt_config.c b/src/tests/cmocka/test_prompt_config.c
index 0b761ae4c..70b27875a 100644
--- a/src/tests/cmocka/test_prompt_config.c
+++ b/src/tests/cmocka/test_prompt_config.c
@@ -100,6 +100,23 @@ void test_pc_list_add_2fa(void **state)
pc_list_free(pc_list);
}
+void test_pc_list_add_eidp(void **state)
+{
+ int ret;
+ struct prompt_config **pc_list = NULL;
+
+ ret = pc_list_add_eidp(&pc_list, "init", "link");
+ assert_int_equal(ret, EOK);
+ assert_non_null(pc_list);
+ assert_non_null(pc_list[0]);
+ assert_int_equal(PC_TYPE_EIDP, pc_get_type(pc_list[0]));
+ assert_string_equal("init", pc_get_eidp_init_prompt(pc_list[0]));
+ assert_string_equal("link", pc_get_eidp_link_prompt(pc_list[0]));
+ assert_null(pc_list[1]);
+
+ pc_list_free(pc_list);
+}
+
void test_pam_get_response_prompt_config(void **state)
{
int ret;
@@ -116,15 +133,18 @@ void test_pam_get_response_prompt_config(void **state)
ret = pc_list_add_2fa_single(&pc_list, "single");
assert_int_equal(ret, EOK);
+ ret = pc_list_add_eidp(&pc_list, "init", "link");
+ assert_int_equal(ret, EOK);
+
ret = pam_get_response_prompt_config(pc_list, &len, &data);
pc_list_free(pc_list);
assert_int_equal(ret, EOK);
- assert_int_equal(len, 57);
+ assert_int_equal(len, 77);
#if __BYTE_ORDER == __LITTLE_ENDIAN
- assert_memory_equal(data, "\3\0\0\0\1\0\0\0\10\0\0\0" "password\2\0\0\0\5\0\0\0" "first\6\0\0\0" "second\3\0\0\0\6\0\0\0" "single", len);
+ assert_memory_equal(data, "\4\0\0\0\1\0\0\0\10\0\0\0" "password\2\0\0\0\5\0\0\0" "first\6\0\0\0" "second\3\0\0\0\6\0\0\0" "single\6\0\0\0\4\0\0\0" "init\4\0\0\0" "link", len);
#else
- assert_memory_equal(data, "\0\0\0\3\0\0\0\1\0\0\0\10" "password\0\0\0\2\0\0\0\5" "first\0\0\0\6" "second\0\0\0\3\0\0\0\6" "single", len);
+ assert_memory_equal(data, "\0\0\0\4\0\0\0\1\0\0\0\10" "password\0\0\0\2\0\0\0\5" "first\0\0\0\6" "second\0\0\0\3\0\0\0\6" "single\0\0\0\6\0\0\0\4" "init\0\0\0\4" "link", len);
#endif
free(data);
@@ -146,10 +166,13 @@ void test_pc_list_from_response(void **state)
ret = pc_list_add_2fa_single(&pc_list, "single");
assert_int_equal(ret, EOK);
+ ret = pc_list_add_eidp(&pc_list, "init", "link");
+ assert_int_equal(ret, EOK);
+
ret = pam_get_response_prompt_config(pc_list, &len, &data);
pc_list_free(pc_list);
assert_int_equal(ret, EOK);
- assert_int_equal(len, 57);
+ assert_int_equal(len, 77);
pc_list = NULL;
@@ -171,7 +194,12 @@ void test_pc_list_from_response(void **state)
assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[2]));
assert_string_equal("single", pc_get_2fa_single_prompt(pc_list[2]));
- assert_null(pc_list[3]);
+ assert_non_null(pc_list[3]);
+ assert_int_equal(PC_TYPE_EIDP, pc_get_type(pc_list[3]));
+ assert_string_equal("init", pc_get_eidp_init_prompt(pc_list[3]));
+ assert_string_equal("link", pc_get_eidp_link_prompt(pc_list[3]));
+
+ assert_null(pc_list[4]);
pc_list_free(pc_list);
}
@@ -190,6 +218,7 @@ int main(int argc, const char *argv[])
cmocka_unit_test(test_pc_list_add_password),
cmocka_unit_test(test_pc_list_add_2fa_single),
cmocka_unit_test(test_pc_list_add_2fa),
+ cmocka_unit_test(test_pc_list_add_eidp),
cmocka_unit_test(test_pam_get_response_prompt_config),
cmocka_unit_test(test_pc_list_from_response),
};
--
2.54.0
From 88c7149497557970e22468ab323a208caa1c1833 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 8 May 2024 10:30:54 +0200
Subject: [PATCH 03/36] Responder: tune prompts in the GUI
Return `prompt_config` structure in `pam_eval_prompting_config` to tune
the prompts from the SSSD config in the GUI.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pam_prompting_config.c | 8 ++++++--
src/responder/pam/pamsrv.h | 6 +++++-
src/responder/pam/pamsrv_cmd.c | 3 ++-
3 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/responder/pam/pam_prompting_config.c b/src/responder/pam/pam_prompting_config.c
index 7d0362fbb..27f794b92 100644
--- a/src/responder/pam/pam_prompting_config.c
+++ b/src/responder/pam/pam_prompting_config.c
@@ -212,7 +212,8 @@ done:
return ret;
}
-errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd)
+errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd,
+ struct prompt_config ***_pc_list)
{
int ret;
struct prompt_config **pc_list = NULL;
@@ -300,10 +301,13 @@ errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd)
}
}
+ *_pc_list = pc_list;
ret = EOK;
done:
free(resp_data);
- pc_list_free(pc_list);
+ if (ret != EOK) {
+ pc_list_free(pc_list);
+ }
return ret;
}
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 0246faa14..a2567b068 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -28,6 +28,9 @@
#include "responder/common/cache_req/cache_req.h"
#include "lib/certmap/sss_certmap.h"
+#define PROMPT_CONFIG_FIRST 1
+#define PROMPT_CONFIG_SECOND 2
+
struct pam_auth_req;
typedef void (pam_dp_callback_t)(struct pam_auth_req *preq);
@@ -174,7 +177,8 @@ errno_t filter_responses(struct pam_ctx *pctx,
errno_t pam_get_auth_types(struct pam_data *pd,
struct pam_resp_auth_type *_auth_types);
-errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd);
+errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd,
+ struct prompt_config ***_pc_list);
enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str);
const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme);
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 62587f053..3bd19f243 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1231,6 +1231,7 @@ void pam_reply(struct pam_auth_req *preq)
int pam_verbosity;
bool local_sc_auth_allow = false;
bool local_passkey_auth_allow = false;
+ struct prompt_config **pc_list = NULL;
#ifdef BUILD_PASSKEY
bool pk_preauth_done = false;
bool pk_kerberos = false;
@@ -1510,7 +1511,7 @@ void pam_reply(struct pam_auth_req *preq)
}
if (pd->cmd == SSS_PAM_PREAUTH) {
- ret = pam_eval_prompting_config(pctx, pd);
+ ret = pam_eval_prompting_config(pctx, pd, &pc_list);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to add prompting information, "
"using defaults.\n");
--
2.54.0
From b3a9cb2ac244efc5a20ad62dbd570bb10667043a Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Fri, 19 Jan 2024 09:27:24 +0100
Subject: [PATCH 04/36] Responder: generate JSON message for GUI
Implement a set of functions to check the available authentication
mechanisms and their associated data, and generate a JSON message with
it. This JSON formatted message will be consumed by apps that provide
GUI login (i.e. GDM). Currently, the implementation only takes into
account password and OAUTH2 mechanisms.
Include unit tests to check the implemented functions.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
Makefile.am | 40 +++
src/responder/pam/pamsrv_json.c | 407 ++++++++++++++++++++++++++++
src/responder/pam/pamsrv_json.h | 112 ++++++++
src/sss_client/sss_cli.h | 5 +
src/tests/cmocka/test_pamsrv_json.c | 288 ++++++++++++++++++++
5 files changed, 852 insertions(+)
create mode 100644 src/responder/pam/pamsrv_json.c
create mode 100644 src/responder/pam/pamsrv_json.h
create mode 100644 src/tests/cmocka/test_pamsrv_json.c
diff --git a/Makefile.am b/Makefile.am
index ab8a3fe9d..290a94890 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -299,6 +299,7 @@ if HAVE_CMOCKA
test_confdb \
test_krb5_idp_plugin \
test_sss_pam_data \
+ test_pamsrv_json \
$(NULL)
@@ -750,6 +751,7 @@ dist_noinst_HEADERS = \
src/responder/common/cache_req/cache_req_private.h \
src/responder/pam/pamsrv.h \
src/responder/pam/pam_helpers.h \
+ src/responder/pam/pamsrv_json.h \
src/responder/pam/pamsrv_passkey.h \
src/responder/nss/nss_private.h \
src/responder/nss/nss_protocol.h \
@@ -2695,6 +2697,44 @@ if BUILD_PASSKEY
pam_srv_tests_SOURCES += src/responder/pam/pamsrv_passkey.c
endif # BUILD_PASSKEY
+test_pamsrv_json_SOURCES = \
+ $(TEST_MOCK_RESP_OBJ) \
+ src/responder/pam/pamsrv_cmd.c \
+ src/responder/pam/pamsrv_json.c \
+ src/responder/pam/pamsrv_p11.c \
+ src/responder/pam/pamsrv_gssapi.c \
+ src/responder/pam/pam_helpers.c \
+ src/responder/pam/pamsrv_dp.c \
+ src/responder/pam/pam_prompting_config.c \
+ src/sss_client/pam_sss_prompt_config.c \
+ src/tests/cmocka/test_pamsrv_json.c \
+ $(NULL)
+if BUILD_PASSKEY
+ test_pamsrv_json_SOURCES += src/responder/pam/pamsrv_passkey.c
+endif # BUILD_PASSKEY
+test_pamsrv_json_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CMOCKA_CFLAGS) \
+ $(NULL)
+test_pamsrv_json_LDFLAGS = \
+ -Wl,-wrap,json_array_append_new \
+ $(NULL)
+test_pamsrv_json_LDADD = \
+ $(LIBADD_DL) \
+ $(CMOCKA_LIBS) \
+ $(PAM_LIBS) \
+ $(SSSD_LIBS) \
+ $(SSSD_INTERNAL_LTLIBS) \
+ $(JANSSON_LIBS) \
+ $(GSSAPI_KRB5_LIBS) \
+ $(TALLOC_LIBS) \
+ libsss_test_common.la \
+ libsss_idmap.la \
+ libsss_certmap.la \
+ libsss_iface.la \
+ libsss_sbus.la \
+ $(NULL)
+
test_sss_pam_data_SOURCES = \
src/util/sss_pam_data.c \
src/tests/cmocka/test_sss_pam_data.c \
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
new file mode 100644
index 000000000..c95247420
--- /dev/null
+++ b/src/responder/pam/pamsrv_json.c
@@ -0,0 +1,407 @@
+/*
+ SSSD
+
+ pamsrv_json authentication selection helper for GDM
+
+ Authors:
+ Iker Pedrosa <ipedrosa@redhat.com>
+
+ Copyright (C) 2024 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "responder/pam/pamsrv.h"
+#include "util/debug.h"
+
+#include "pamsrv_json.h"
+
+
+static errno_t
+obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, char **_uri,
+ char **_code)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ uint8_t *oauth2 = NULL;
+ char *uri = NULL;
+ char *uri_complete = NULL;
+ char *code = NULL;
+ int32_t len;
+ int32_t offset;
+ int32_t str_len;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ ret = pam_get_response_data(tmp_ctx, pd, SSS_PAM_OAUTH2_INFO, &oauth2, &len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get SSS_PAM_OAUTH2_INFO, ret %d.\n",
+ ret);
+ goto done;
+ }
+
+ str_len = strnlen((const char *)oauth2, len);
+ if (str_len >= len) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "uri string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ uri = talloc_strndup(tmp_ctx, (const char *)oauth2, str_len);
+ if (uri == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset = str_len + 1;
+
+ if (offset >= len) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)oauth2 + offset, len - offset);
+ if (str_len >= (len - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "uri_complete string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ uri_complete = talloc_strndup(tmp_ctx, (const char *)oauth2 + offset, str_len);
+ if (uri_complete == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ if (offset >= len) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)oauth2 + offset, len - offset);
+ if (str_len >= (len - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "code string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ code = talloc_strndup(tmp_ctx, (const char *)oauth2 + offset, str_len);
+ if (code == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_uri = talloc_steal(mem_ctx, uri);
+ *_code = talloc_steal(mem_ctx, code);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static errno_t
+obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
+ struct prompt_config **pc_list, const char **_password_prompt,
+ const char **_oauth2_init_prompt, const char **_oauth2_link_prompt)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *password_prompt = NULL;
+ char *oauth2_init_prompt = NULL;
+ char *oauth2_link_prompt = NULL;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ password_prompt = talloc_strdup(tmp_ctx, PASSWORD_PROMPT);
+ if (password_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ oauth2_init_prompt = talloc_strdup(tmp_ctx, OAUTH2_INIT_PROMPT);
+ if (oauth2_init_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ oauth2_link_prompt = talloc_strdup(tmp_ctx, OAUTH2_LINK_PROMPT);
+ if (oauth2_link_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_password_prompt = talloc_steal(mem_ctx, password_prompt);
+ *_oauth2_init_prompt = talloc_steal(mem_ctx, oauth2_init_prompt);
+ *_oauth2_link_prompt = talloc_steal(mem_ctx, oauth2_link_prompt);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+errno_t
+json_format_mechanisms(bool password_auth, const char *password_prompt,
+ bool oauth2_auth, const char *uri, const char *code,
+ const char *oauth2_init_prompt,
+ const char *oauth2_link_prompt,
+ json_t **_list_mech)
+{
+ json_t *root = NULL;
+ json_t *json_pass = NULL;
+ json_t *json_oauth2 = NULL;
+ int ret;
+
+ root = json_object();
+ if (root == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (password_auth) {
+ json_pass = json_pack("{s:s,s:s,s:s}",
+ "name", "Password",
+ "role", "password",
+ "prompt", password_prompt);
+ if (json_pass == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = json_object_set_new(root, "password", json_pass);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_pass);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ if (oauth2_auth) {
+ json_oauth2 = json_pack("{s:s,s:s,s:s,s:s,s:s,s:s,s:i}",
+ "name", "Web Login",
+ "role", "eidp",
+ "init_prompt", oauth2_init_prompt,
+ "link_prompt", oauth2_link_prompt,
+ "uri", uri,
+ "code", code,
+ "timeout", 300);
+ if (json_oauth2 == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = json_object_set_new(root, "eidp", json_oauth2);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_oauth2);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ *_list_mech = root;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ json_decref(root);
+ }
+
+ return ret;
+}
+
+errno_t
+json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority)
+{
+ json_t *root = NULL;
+ json_t *json_priority = NULL;
+ int ret;
+
+ root = json_array();
+ if (root == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (oauth2_auth) {
+ json_priority = json_string("eidp");
+ ret = json_array_append_new(root, json_priority);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_priority);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ if (password_auth) {
+ json_priority = json_string("password");
+ ret = json_array_append_new(root, json_priority);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_priority);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ ret = EOK;
+ *_priority = root;
+
+done:
+ if (ret != EOK) {
+ json_decref(root);
+ }
+
+ return ret;
+}
+
+errno_t
+json_format_auth_selection(TALLOC_CTX *mem_ctx,
+ bool password_auth, const char *password_prompt,
+ bool oauth2_auth, const char *uri, const char *code,
+ const char *oauth2_init_prompt,
+ const char *oauth2_link_prompt,
+ char **_result)
+{
+ json_t *root = NULL;
+ json_t *json_mech = NULL;
+ json_t *json_priority = NULL;
+ char *string = NULL;
+ int ret;
+
+ ret = json_format_mechanisms(password_auth, password_prompt,
+ oauth2_auth, uri, code, oauth2_init_prompt,
+ oauth2_link_prompt, &json_mech);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = json_format_priority(password_auth, oauth2_auth, &json_priority);
+ if (ret != EOK) {
+ json_decref(json_mech);
+ goto done;
+ }
+
+ root = json_pack("{s:{s:o,s:o}}",
+ "auth-selection",
+ "mechanisms", json_mech,
+ "priority", json_priority);
+ if (root == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
+ ret = ENOMEM;
+ json_decref(json_mech);
+ json_decref(json_priority);
+ goto done;
+ }
+
+ string = json_dumps(root, 0);
+ if (string == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_dumps failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_result = talloc_strdup(mem_ctx, string);
+ ret = EOK;
+
+done:
+ free(string);
+ json_decref(root);
+
+ return ret;
+}
+
+errno_t
+generate_json_auth_message(struct confdb_ctx *cdb,
+ struct prompt_config **pc_list,
+ struct pam_data *_pd)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *password_prompt = NULL;
+ const char *oauth2_init_prompt = NULL;
+ const char *oauth2_link_prompt = NULL;
+ char *oauth2_uri = NULL;
+ char *oauth2_code = NULL;
+ char *result = NULL;
+ bool oauth2_auth = true;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ ret = obtain_prompts(cdb, tmp_ctx, pc_list, &password_prompt,
+ &oauth2_init_prompt, &oauth2_link_prompt);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain the prompts.\n");
+ goto done;
+ }
+
+ ret = obtain_oauth2_data(tmp_ctx, _pd, &oauth2_uri, &oauth2_code);
+ if (ret == ENOENT) {
+ oauth2_auth = false;
+ } else if (ret != EOK) {
+ goto done;
+ }
+
+ ret = json_format_auth_selection(tmp_ctx, true, password_prompt,
+ oauth2_auth, oauth2_uri, oauth2_code,
+ oauth2_init_prompt, oauth2_link_prompt,
+ &result);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = pam_add_response(_pd, SSS_PAM_JSON_AUTH_INFO, strlen(result)+1,
+ (const uint8_t *)result);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_FUNC, "Generated JSON message: %s.\n", result);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
new file mode 100644
index 000000000..57fce53e2
--- /dev/null
+++ b/src/responder/pam/pamsrv_json.h
@@ -0,0 +1,112 @@
+/*
+ SSSD
+
+ pamsrv_json authentication selection helper for GDM
+
+ Authors:
+ Iker Pedrosa <ipedrosa@redhat.com>
+
+ Copyright (C) 2024 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __PAMSRV_JSON__H__
+#define __PAMSRV_JSON__H__
+
+#include <jansson.h>
+#include <talloc.h>
+
+#include "util/sss_pam_data.h"
+
+#define PASSWORD_PROMPT "Password"
+#define OAUTH2_INIT_PROMPT "Log In"
+#define OAUTH2_LINK_PROMPT "Log in online with another device"
+
+
+/**
+ * @brief Format authentication mechanisms to JSON
+ *
+ * @param[in] password_auth Whether password authentication is allowed
+ * @param[in] password_prompt Password prompt
+ * @param[in] oath2_auth Whether OAUTH2 authentication is allowed
+ * @param[in] uri OAUTH2 uri
+ * @param[in] code OAUTH2 code
+ * @param[in] oauth2_init_prompt OAUTH2 initial prompt
+ * @param[in] oauth2_link_prompt OAUTH2 link prompt
+ * @param[out] _list_mech authentication mechanisms JSON object
+ *
+ * @return 0 if the authentication mechanisms were formatted properly,
+ * error code otherwise.
+ */
+errno_t
+json_format_mechanisms(bool password_auth, const char *password_prompt,
+ bool oauth2_auth, const char *uri, const char *code,
+ const char *oauth2_init_prompt,
+ const char *oauth2_link_prompt,
+ json_t **_list_mech);
+
+/**
+ * @brief Format priority to JSON
+ *
+ * @param[in] password_auth Whether password authentication is allowed
+ * @param[in] oath2_auth Whether OAUTH2 authentication is allowed
+ * @param[out] _priority priority JSON object
+ *
+ * @return 0 if the priority was formatted properly,
+ * error code otherwise.
+ */
+errno_t
+json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority);
+
+/**
+ * @brief Format data to JSON
+ *
+ * @param[in] mem_ctx Memory context
+ * @param[in] password_auth Whether password authentication is allowed
+ * @param[in] password_prompt Password prompt
+ * @param[in] oath2_auth Whether OAUTH2 authentication is allowed
+ * @param[in] uri OAUTH2 uri
+ * @param[in] code OAUTH2 code
+ * @param[in] oauth2_init_prompt OAUTH2 initial prompt
+ * @param[in] oauth2_link_prompt OAUTH2 link prompt
+ * @param[out] _result JSON message
+ *
+ * @return 0 if the JSON message was formatted properly,
+ * error code otherwise.
+ */
+errno_t
+json_format_auth_selection(TALLOC_CTX *mem_ctx,
+ bool password_auth, const char *password_prompt,
+ bool oath2_auth, const char *uri, const char *code,
+ const char *oauth2_init_prompt,
+ const char *oauth2_link_prompt,
+ char **_result);
+
+/**
+ * @brief Check the internal data and generate the JSON message
+ *
+ * @param[in] cdb The connection object to the confdb
+ * @param[in] pc_list List that contains all authentication mechanisms prompts
+ * @param[out] pd Data structure containing the response_data linked list
+ *
+ * @return 0 if the data was extracted correctly and JSON message was formatted
+ * properly, error code otherwise.
+ */
+errno_t
+generate_json_auth_message(struct confdb_ctx *cdb,
+ struct prompt_config **pc_list,
+ struct pam_data *_pd);
+
+#endif /* __PAMSRV_JSON__H__ */
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 792c34cb9..b99c145c4 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -556,6 +556,11 @@ enum response_type {
* - user verification (string)
* - key (string)
*/
+ SSS_PAM_JSON_AUTH_INFO, /**< A JSON formatted message containing the available
+ * authentication mechanisms and their associated data.
+ * @param
+ * - json_auth_msg
+ */
};
/**
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
new file mode 100644
index 000000000..7f391732d
--- /dev/null
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -0,0 +1,288 @@
+/*
+ SSSD
+
+ Unit test for pamsrv_json
+
+ Authors:
+ Iker Pedrosa <ipedrosa@redhat.com>
+
+ Copyright (C) 2024 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <jansson.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+
+#include "src/responder/pam/pamsrv_json.h"
+
+#define OAUTH2_URI "short.url.com/tmp\0"
+#define OAUTH2_URI_COMP "\0"
+#define OAUTH2_CODE "1234-5678"
+#define OAUTH2_STR OAUTH2_URI OAUTH2_URI_COMP OAUTH2_CODE
+
+#define BASIC_PASSWORD "\"password\": {" \
+ "\"name\": \"Password\", \"role\": \"password\", " \
+ "\"prompt\": \"Password\"}"
+#define BASIC_OAUTH2 "\"eidp\": {" \
+ "\"name\": \"Web Login\", \"role\": \"eidp\", " \
+ "\"init_prompt\": \"" OAUTH2_INIT_PROMPT "\", " \
+ "\"link_prompt\": \"" OAUTH2_LINK_PROMPT "\", " \
+ "\"uri\": \"short.url.com/tmp\", \"code\": \"1234-5678\", " \
+ "\"timeout\": 300}"
+#define MECHANISMS_PASSWORD "{" BASIC_PASSWORD "}"
+#define MECHANISMS_OAUTH2 "{" BASIC_OAUTH2 "}"
+#define PRIORITY_ALL "[\"eidp\", \"password\"]"
+#define AUTH_SELECTION_PASSWORD "{\"auth-selection\": {\"mechanisms\": " \
+ MECHANISMS_PASSWORD ", " \
+ "\"priority\": [\"password\"]}}"
+#define AUTH_SELECTION_OAUTH2 "{\"auth-selection\": {\"mechanisms\": " \
+ MECHANISMS_OAUTH2 ", " \
+ "\"priority\": [\"eidp\"]}}"
+#define AUTH_SELECTION_ALL "{\"auth-selection\": {\"mechanisms\": {" \
+ BASIC_PASSWORD ", " \
+ BASIC_OAUTH2 "}, " \
+ "\"priority\": " PRIORITY_ALL "}}"
+
+
+/***********************
+ * WRAPPERS
+ **********************/
+int __real_json_array_append_new(json_t *array, json_t *value);
+
+int
+__wrap_json_array_append_new(json_t *array, json_t *value)
+{
+ int fail;
+ int ret;
+
+ fail = mock();
+
+ if(fail) {
+ ret = -1;
+ } else {
+ ret = __real_json_array_append_new(array, value);
+ }
+
+ return ret;
+}
+
+/***********************
+ * TEST
+ **********************/
+void test_json_format_mechanisms_password(void **state)
+{
+ json_t *mechs = NULL;
+ char *string;
+ int ret;
+
+ ret = json_format_mechanisms(true, PASSWORD_PROMPT, false, NULL, NULL,
+ NULL, NULL, &mechs);
+ assert_int_equal(ret, EOK);
+
+ string = json_dumps(mechs, 0);
+ assert_string_equal(string, MECHANISMS_PASSWORD);
+ json_decref(mechs);
+ free(string);
+}
+
+void test_json_format_mechanisms_oauth2(void **state)
+{
+ json_t *mechs = NULL;
+ char *string;
+ int ret;
+
+ ret = json_format_mechanisms(false, NULL, true, OAUTH2_URI, OAUTH2_CODE,
+ OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ &mechs);
+ assert_int_equal(ret, EOK);
+
+ string = json_dumps(mechs, 0);
+ assert_string_equal(string, MECHANISMS_OAUTH2);
+ json_decref(mechs);
+ free(string);
+}
+
+void test_json_format_priority_all(void **state)
+{
+ json_t *priority = NULL;
+ char *string;
+ int ret;
+
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_priority(true, true, &priority);
+ assert_int_equal(ret, EOK);
+
+ string = json_dumps(priority, 0);
+ assert_string_equal(string, PRIORITY_ALL);
+ json_decref(priority);
+ free(string);
+}
+
+void test_json_format_auth_selection_password(void **state)
+{
+ TALLOC_CTX *test_ctx;
+ char *json_msg = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
+ false, NULL, NULL, NULL, NULL, &json_msg);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(json_msg, AUTH_SELECTION_PASSWORD);
+}
+
+void test_json_format_auth_selection_oauth2(void **state)
+{
+ TALLOC_CTX *test_ctx;
+ char *json_msg = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_auth_selection(test_ctx, false, NULL,
+ true, OAUTH2_URI, OAUTH2_CODE,
+ OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ &json_msg);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(json_msg, AUTH_SELECTION_OAUTH2);
+}
+
+void test_json_format_auth_selection_all(void **state)
+{
+ TALLOC_CTX *test_ctx;
+ char *json_msg = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
+ true, OAUTH2_URI, OAUTH2_CODE,
+ OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ &json_msg);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(json_msg, AUTH_SELECTION_ALL);
+}
+
+void test_json_format_auth_selection_failure(void **state)
+{
+ TALLOC_CTX *test_ctx;
+ char *json_msg = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+
+ will_return(__wrap_json_array_append_new, true);
+ ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
+ true, OAUTH2_URI, OAUTH2_CODE,
+ OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ &json_msg);
+ assert_int_equal(ret, ENOMEM);
+ assert_null(json_msg);
+}
+
+void test_generate_json_message_integration(void **state)
+{
+ TALLOC_CTX *test_ctx;
+ struct pam_data *pd = NULL;
+ struct prompt_config **pc_list = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+
+ pd->resp_list = talloc(pd, struct response_data);
+ pd->resp_list->type = SSS_PAM_OAUTH2_INFO;
+ pd->resp_list->len = strlen(OAUTH2_URI)+1+strlen(OAUTH2_URI_COMP)+1+strlen(OAUTH2_CODE)+1;
+ pd->resp_list->data = discard_const(OAUTH2_STR);
+ pd->resp_list->next = NULL;
+
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
+ ret = generate_json_auth_message(NULL, pc_list, pd);
+ assert_int_equal(ret, EOK);
+ assert_string_equal((char*) pd->resp_list->data, AUTH_SELECTION_ALL);
+
+ pc_list_free(pc_list);
+ talloc_free(test_ctx);
+}
+
+static void test_parse_supp_valgrind_args(void)
+{
+ /*
+ * The objective of this function is to filter the unit-test functions
+ * that trigger a valgrind memory leak and suppress them to avoid false
+ * positives.
+ */
+ DEBUG_CLI_INIT(debug_level);
+}
+
+int main(int argc, const char *argv[])
+{
+ poptContext pc;
+ int opt;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ POPT_TABLEEND
+ };
+
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_json_format_mechanisms_password),
+ cmocka_unit_test(test_json_format_mechanisms_oauth2),
+ cmocka_unit_test(test_json_format_priority_all),
+ cmocka_unit_test(test_json_format_auth_selection_password),
+ cmocka_unit_test(test_json_format_auth_selection_oauth2),
+ cmocka_unit_test(test_json_format_auth_selection_all),
+ cmocka_unit_test(test_json_format_auth_selection_failure),
+ cmocka_unit_test(test_generate_json_message_integration),
+ };
+
+ /* Set debug level to invalid value so we can decide if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ test_parse_supp_valgrind_args();
+
+ /* Even though normally the tests should clean up after themselves
+ * they might not after a failed run. Remove the old DB to be sure */
+ tests_set_cwd();
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
--
2.54.0
From a05cb30e05fcfc2766b2cb76180956dbaf378139 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 30 Jan 2024 11:21:59 +0100
Subject: [PATCH 05/36] Responder: unpack JSON reply from GUI
Implement a set of functions to unpack the JSON reply from the GUI.
Include unit tests to check the implemented functions.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 170 ++++++++++++++++++++++++++++
src/responder/pam/pamsrv_json.h | 38 +++++++
src/tests/cmocka/test_pamsrv_json.c | 116 +++++++++++++++++++
src/util/sss_pam_data.h | 2 +
4 files changed, 326 insertions(+)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index c95247420..f0ed24f7a 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -405,3 +405,173 @@ done:
return ret;
}
+
+errno_t
+json_unpack_password(json_t *jroot, char **_password)
+{
+ char *password = NULL;
+ int ret = EOK;
+
+ ret = json_unpack(jroot, "{s:s}",
+ "password", &password);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for password failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_password = password;
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+errno_t
+json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg,
+ char **_oauth2_code)
+{
+ json_t *jroot = NULL;
+ json_t *json_mechs = NULL;
+ json_t *json_priority = NULL;
+ json_t *json_mech = NULL;
+ json_t *jobj = NULL;
+ const char *key = NULL;
+ const char *oauth2_code = NULL;
+ json_error_t jret;
+ int ret = EOK;
+
+ jroot = json_loads(json_auth_msg, 0, &jret);
+ if (jroot == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_loads failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = json_unpack(jroot, "{s:{s:o,s:o}}",
+ "auth-selection",
+ "mechanisms", &json_mechs,
+ "priority", &json_priority);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ json_object_foreach(json_mechs, key, json_mech){
+ if (strcmp(key, "eidp") == 0) {
+ json_object_foreach(json_mech, key, jobj){
+ if (strcmp(key, "code") == 0) {
+ oauth2_code = json_string_value(jobj);
+ ret = EOK;
+ goto done;
+ }
+ }
+ }
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "OAUTH2 code not found in JSON message.\n");
+ ret = ENOENT;
+
+done:
+ if (ret == EOK) {
+ *_oauth2_code = talloc_strdup(mem_ctx, oauth2_code);
+ }
+ if (jroot != NULL) {
+ json_decref(jroot);
+ }
+
+ return ret;
+}
+
+errno_t
+json_unpack_auth_reply(struct pam_data *pd)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ json_t *jroot = NULL;
+ json_t *jauth_selection = NULL;
+ json_t *jobj = NULL;
+ json_error_t jret;
+ const char *key = NULL;
+ const char *status = NULL;
+ char *password = NULL;
+ char *oauth2_code = NULL;
+ int ret = EOK;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Received JSON message: %s.\n",
+ pd->json_auth_selected);
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ jroot = json_loads(pd->json_auth_selected, 0, &jret);
+ if (jroot == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = json_unpack(jroot, "{s:o}", "auth-selection", &jauth_selection);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for auth-selection failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ json_object_foreach(jauth_selection, key, jobj){
+ if (strcmp(key, "status") == 0) {
+ status = json_string_value(jobj);
+ if (status == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "NULL status returned.\n");
+ ret = EINVAL;
+ goto done;
+ } else if (strcmp(status, "Ok") != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Incorrect status returned: %s.\n", status);
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ if (strcmp(key, "password") == 0) {
+ ret = json_unpack_password(jobj, &password);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sss_authtok_set_password(pd->authtok, password, strlen(password));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_authtok_set_password failed: %d.\n", ret);
+ }
+ goto done;
+ }
+
+ if (strcmp(key, "eidp") == 0) {
+ ret = json_unpack_oauth2_code(tmp_ctx, pd->json_auth_msg, &oauth2_code);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sss_authtok_set_oauth2(pd->authtok, oauth2_code,
+ strlen(oauth2_code));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_authtok_set_oauth2 failed: %d.\n", ret);
+ }
+ goto done;
+ }
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown authentication mechanism\n");
+ ret = EINVAL;
+
+done:
+ if (jroot != NULL) {
+ json_decref(jroot);
+ }
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 57fce53e2..d861120df 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -109,4 +109,42 @@ generate_json_auth_message(struct confdb_ctx *cdb,
struct prompt_config **pc_list,
struct pam_data *_pd);
+
+/**
+ * @brief Unpack password specific data reply
+ *
+ * @param[in] jroot jansson structure containing the password specific data
+ * @param[out] _password user password
+ *
+ * @return 0 if the reply was unpacked and the result is ok,
+ * error code otherwise.
+ */
+errno_t
+json_unpack_password(json_t *jroot, char **_password);
+
+/**
+ * @brief Unpack OAUTH2 code
+ *
+ * @param[in] mem_ctx Memory context
+ * @param[in] json_auth_msg JSON authentication mechanisms message
+ * @param[out] _oauth2_code OAUTH2 code
+ *
+ * @return 0 if the reply was unpacked and the result is ok,
+ * error code otherwise.
+ */
+errno_t
+json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg,
+ char **_oauth2_code);
+
+/**
+ * @brief Unpack GDM reply and check its value
+ *
+ * @param[in] pd pam_data containing the GDM reply in JSON format
+ *
+ * @return 0 if the reply was unpacked and the result is ok,
+ * error code otherwise.
+ */
+errno_t
+json_unpack_auth_reply(struct pam_data *pd);
+
#endif /* __PAMSRV_JSON__H__ */
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 7f391732d..6d00d0bd0 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -57,6 +57,15 @@
BASIC_OAUTH2 "}, " \
"\"priority\": " PRIORITY_ALL "}}"
+#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}"
+#define AUTH_MECH_REPLY_PASSWORD "{\"auth-selection\": {" \
+ "\"status\": \"Ok\", \"password\": " \
+ PASSWORD_CONTENT "}}"
+#define AUTH_MECH_REPLY_OAUTH2 "{\"auth-selection\": {" \
+ "\"status\": \"Ok\", \"eidp\": {}}}"
+#define AUTH_MECH_ERRONEOUS "{\"auth-selection\": {" \
+ "\"status\": \"Ok\", \"lololo\": {}}}"
+
/***********************
* WRAPPERS
@@ -232,6 +241,108 @@ void test_generate_json_message_integration(void **state)
talloc_free(test_ctx);
}
+void test_json_unpack_password_ok(void **state)
+{
+ json_t *jroot = NULL;
+ char *password = NULL;
+ json_error_t jret;
+ int ret;
+
+ jroot = json_loads(PASSWORD_CONTENT, 0, &jret);
+ assert_non_null(jroot);
+
+ ret = json_unpack_password(jroot, &password);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(password, "ThePassword");
+ json_decref(jroot);
+}
+
+void test_json_unpack_auth_reply_password(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ const char *password = NULL;
+ size_t len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->authtok = sss_authtok_new(pd);
+ assert_non_null(pd->authtok);
+ pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_PASSWORD);
+
+ ret = json_unpack_auth_reply(pd);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(sss_authtok_get_type(pd->authtok), SSS_AUTHTOK_TYPE_PASSWORD);
+ sss_authtok_get_password(pd->authtok, &password, &len);
+ assert_string_equal(password, "ThePassword");
+
+ talloc_free(test_ctx);
+}
+
+void test_json_unpack_auth_reply_oauth2(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ const char *code = NULL;
+ size_t len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->authtok = sss_authtok_new(pd);
+ assert_non_null(pd->authtok);
+ pd->json_auth_msg = discard_const(AUTH_SELECTION_OAUTH2);
+ pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_OAUTH2);
+
+ ret = json_unpack_auth_reply(pd);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(sss_authtok_get_type(pd->authtok), SSS_AUTHTOK_TYPE_OAUTH2);
+ sss_authtok_get_oauth2(pd->authtok, &code, &len);
+ assert_string_equal(code, OAUTH2_CODE);
+
+ talloc_free(test_ctx);
+}
+
+void test_json_unpack_auth_reply_failure(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->json_auth_selected = discard_const(AUTH_MECH_ERRONEOUS);
+
+ ret = json_unpack_auth_reply(pd);
+ assert_int_equal(ret, EINVAL);
+
+ talloc_free(test_ctx);
+}
+
+void test_json_unpack_oauth2_code(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ char *oauth2_code = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+
+ ret = json_unpack_oauth2_code(test_ctx, discard_const(AUTH_SELECTION_ALL),
+ &oauth2_code);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(oauth2_code, OAUTH2_CODE);
+
+ talloc_free(test_ctx);
+}
+
static void test_parse_supp_valgrind_args(void)
{
/*
@@ -261,6 +372,11 @@ int main(int argc, const char *argv[])
cmocka_unit_test(test_json_format_auth_selection_all),
cmocka_unit_test(test_json_format_auth_selection_failure),
cmocka_unit_test(test_generate_json_message_integration),
+ cmocka_unit_test(test_json_unpack_password_ok),
+ cmocka_unit_test(test_json_unpack_auth_reply_password),
+ cmocka_unit_test(test_json_unpack_auth_reply_oauth2),
+ cmocka_unit_test(test_json_unpack_auth_reply_failure),
+ cmocka_unit_test(test_json_unpack_oauth2_code),
};
/* Set debug level to invalid value so we can decide if -d 0 was used. */
diff --git a/src/util/sss_pam_data.h b/src/util/sss_pam_data.h
index a7efba791..441720e97 100644
--- a/src/util/sss_pam_data.h
+++ b/src/util/sss_pam_data.h
@@ -75,6 +75,8 @@ struct pam_data {
key_serial_t key_serial;
#endif
bool passkey_local_done;
+ char *json_auth_msg;
+ char *json_auth_selected;
};
/**
--
2.54.0
From 470a3d1eecc2ac36c67ffba072bc5fffe7222afa Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 6 Mar 2024 12:25:55 +0100
Subject: [PATCH 06/36] Responder: check PAM service file for JSON protocol
Implement a function to check whether the PAM service file in use is
enabled for the JSON procotol. This helps us filter which applications
are compatible with this protocol.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 17 +++++++++++++++
src/responder/pam/pamsrv_json.h | 13 +++++++++++
src/tests/cmocka/test_pamsrv_json.c | 34 +++++++++++++++++++++++++++++
3 files changed, 64 insertions(+)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index f0ed24f7a..e59f218b2 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -575,3 +575,20 @@ done:
return ret;
}
+
+bool is_pam_json_enabled(char **json_services,
+ char *service)
+{
+ if (json_services == NULL) {
+ return false;
+ }
+
+ if (strcmp(json_services[0], "-") == 0) {
+ /* Dash is used to disable the JSON protocol */
+ DEBUG(SSSDBG_TRACE_FUNC, "Dash - was used as a PAM service name. "
+ "JSON protocol is disabled.\n");
+ return false;
+ }
+
+ return string_in_list(service, json_services, true);
+}
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index d861120df..4c8c3aef3 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -147,4 +147,17 @@ json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg,
errno_t
json_unpack_auth_reply(struct pam_data *pd);
+/**
+ * @brief Check whether the PAM service file in use is enabled for the JSON
+ * protocol
+ *
+ * @param[in] json_services Enabled PAM services for JSON protocol
+ * @param[in] service PAM service file in use
+ *
+ * @return true if the JSON protocol is enabled for the PAM service file,
+ * false otherwise.
+ */
+bool is_pam_json_enabled(char **json_services,
+ char *service);
+
#endif /* __PAMSRV_JSON__H__ */
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 6d00d0bd0..3e1b8b0d1 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -343,6 +343,37 @@ void test_json_unpack_oauth2_code(void **state)
talloc_free(test_ctx);
}
+void test_is_pam_json_enabled_service_in_list(void **state)
+{
+ char *json_services[] = {discard_const("sshd"), discard_const("su"),
+ discard_const("gdm-switchable-auth"), NULL};
+ bool result;
+
+ result = is_pam_json_enabled(json_services,
+ discard_const("gdm-switchable-auth"));
+ assert_int_equal(result, true);
+}
+
+void test_is_pam_json_enabled_service_not_in_list(void **state)
+{
+ char *json_services[] = {discard_const("sshd"), discard_const("su"),
+ discard_const("gdm-switchable-auth"), NULL};
+ bool result;
+
+ result = is_pam_json_enabled(json_services,
+ discard_const("sudo"));
+ assert_int_equal(result, false);
+}
+
+void test_is_pam_json_enabled_null_list(void **state)
+{
+ bool result;
+
+ result = is_pam_json_enabled(NULL,
+ discard_const("sudo"));
+ assert_int_equal(result, false);
+}
+
static void test_parse_supp_valgrind_args(void)
{
/*
@@ -377,6 +408,9 @@ int main(int argc, const char *argv[])
cmocka_unit_test(test_json_unpack_auth_reply_oauth2),
cmocka_unit_test(test_json_unpack_auth_reply_failure),
cmocka_unit_test(test_json_unpack_oauth2_code),
+ cmocka_unit_test(test_is_pam_json_enabled_service_in_list),
+ cmocka_unit_test(test_is_pam_json_enabled_service_not_in_list),
+ cmocka_unit_test(test_is_pam_json_enabled_null_list),
};
/* Set debug level to invalid value so we can decide if -d 0 was used. */
--
2.54.0
From 9d7998b874eae4e2f543cd4e2d82ee316d19a57a Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 20 Feb 2024 12:19:24 +0100
Subject: [PATCH 07/36] Responder: new option `pam_json_services`
This new option is used to enable the JSON protocol in the PAM responder
based on the PAM service file in use.
:config: A new option `pam_json_services` is now available to enable
JSON protocol to communicate the available authentication
mechanisms.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/confdb/confdb.h | 1 +
src/config/SSSDConfig/sssdoptions.py | 1 +
src/config/cfg_rules.ini | 1 +
src/config/etc/sssd.api.conf | 1 +
src/man/Makefile.am | 3 +++
src/man/sssd.conf.5.xml | 23 +++++++++++++++++++++++
src/responder/pam/pamsrv.c | 24 ++++++++++++++++++++++++
src/responder/pam/pamsrv.h | 1 +
8 files changed, 55 insertions(+)
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index b21b3e0cd..e7772c190 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -161,6 +161,7 @@
#define CONFDB_PAM_PASSKEY_AUTH "pam_passkey_auth"
#define CONFDB_PAM_PASSKEY_CHILD_TIMEOUT "passkey_child_timeout"
#define CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2 "passkey_debug_libfido2"
+#define CONFDB_PAM_JSON_SERVICES "pam_json_services"
/* SUDO */
#define CONFDB_SUDO_CONF_ENTRY "config/sudo"
diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py
index 3a88c1d2f..cf191dc28 100644
--- a/src/config/SSSDConfig/sssdoptions.py
+++ b/src/config/SSSDConfig/sssdoptions.py
@@ -120,6 +120,7 @@ class SSSDOptions(object):
'pam_passkey_auth': _('Allow passkey device authentication.'),
'passkey_child_timeout': _('How many seconds will pam_sss wait for passkey_child to finish'),
'passkey_debug_libfido2': _('Enable debugging in the libfido2 library'),
+ 'pam_json_services': _('Enable JSON protocol for authentication methods selection.'),
# [sudo]
'sudo_timed': _('Whether to evaluate the time-based attributes in sudo rules'),
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index 92f553bf0..c1167875b 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -148,6 +148,7 @@ option = pam_gssapi_indicators_apply
option = pam_passkey_auth
option = passkey_child_timeout
option = passkey_debug_libfido2
+option = pam_json_services
[rule/allowed_sudo_options]
validator = ini_allowed_options
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index b537d72c0..b8960edba 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -90,6 +90,7 @@ pam_gssapi_indicators_apply = str, None, false
pam_passkey_auth = bool, None, false
passkey_child_timeout = int, None, false
passkey_debug_libfido2 = bool, None, false
+pam_json_services = str, None, false
[sudo]
# sudo service
diff --git a/src/man/Makefile.am b/src/man/Makefile.am
index 475bfbbca..876561289 100644
--- a/src/man/Makefile.am
+++ b/src/man/Makefile.am
@@ -67,6 +67,9 @@ endif
if BUILD_SAMBA
SAMBA_CONDS = ;with_samba
endif
+if HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION
+JSON_PAM_CONDS = ;build_json_pam
+endif
CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SYSTEMD_CONDS)$(KCM_CONDS)$(STAP_CONDS)$(KCM_RENEWAL_CONDS)$(LOCKFREE_CLIENT_CONDS)$(HAVE_INOTIFY_CONDS)$(SUBID_CONDS)$(PASSKEY_CONDS)$(FILES_PROVIDER_CONDS)$(SSSD_NON_ROOT_USER_CONDS)$(LIBNL_CONDS)$(SAMBA_CONDS)
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 6a4fea68e..a4829366a 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -2105,6 +2105,29 @@ pam_gssapi_indicators_apply = SID:S-1-5-12345-23456-34567-4321:pkinit
</para>
</listitem>
</varlistentry>
+ <varlistentry condition="build_json_pam">
+ <term>pam_json_services (string)</term>
+ <listitem>
+ <para>
+ Comma separated list of PAM services which can
+ handle the JSON protocol for selecting
+ authentication mechanisms
+ </para>
+ <para>
+ To disable JSON protocol, set this option
+ to <quote>-</quote> (dash).
+ </para>
+ <para>
+ Example:
+ <programlisting>
+pam_json_services = gdm-switchable-auth
+ </programlisting>
+ </para>
+ <para>
+ Default: - (JSON protocol is disabled)
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect2>
diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c
index 95536fee7..291bcdf23 100644
--- a/src/responder/pam/pamsrv.c
+++ b/src/responder/pam/pamsrv.c
@@ -435,6 +435,30 @@ static int pam_process_init(TALLOC_CTX *mem_ctx,
#endif
}
+ /* Check if JSON authentication selection method is enabled for any PAM
+ * services
+ */
+ ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY,
+ CONFDB_PAM_JSON_SERVICES, "-", &tmpstr);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Failed to determine json services.\n");
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr,
+ CONFDB_PAM_JSON_SERVICES);
+
+ if (tmpstr != NULL) {
+ ret = split_on_separator(pctx, tmpstr, ',', true, true,
+ &pctx->json_services, NULL);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "split_on_separator() failed [%d]: [%s].\n", ret,
+ sss_strerror(ret));
+ goto done;
+ }
+ }
+
/* The responder is initialized. Now tell it to the monitor. */
ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_PAM,
SSS_PAM_SBUS_SERVICE_NAME,
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index a2567b068..627b6c147 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -77,6 +77,7 @@ struct pam_ctx {
bool gssapi_check_upn;
bool passkey_auth;
struct pam_passkey_table_data *pk_table_data;
+ char **json_services;
};
struct pam_auth_req {
--
2.54.0
From a1777b281f96be886c02a1b08948fba350ebaae7 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Mon, 22 Jan 2024 10:30:16 +0100
Subject: [PATCH 08/36] Responder: call JSON message generation
Call JSON message generation function and fill the data structure
containing the response_data linked list.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
Makefile.am | 4 ++++
src/responder/pam/pamsrv_cmd.c | 14 ++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index 290a94890..e052c3079 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1566,6 +1566,7 @@ endif
sssd_pam_SOURCES = \
src/responder/pam/pamsrv.c \
src/responder/pam/pamsrv_cmd.c \
+ src/responder/pam/pamsrv_json.c \
src/responder/pam/pamsrv_p11.c \
src/responder/pam/pamsrv_dp.c \
src/responder/pam/pamsrv_gssapi.c \
@@ -1594,6 +1595,7 @@ sssd_pam_LDADD = \
$(PAM_LIBS) \
$(SYSTEMD_DAEMON_LIBS) \
$(GSSAPI_KRB5_LIBS) \
+ $(JANSSON_LIBS) \
libsss_certmap.la \
$(SSSD_INTERNAL_LTLIBS) \
libsss_iface.la \
@@ -2649,6 +2651,7 @@ pam_srv_tests_SOURCES = \
src/tests/cmocka/common_utils.c \
src/sss_client/pam_message.c \
src/responder/pam/pamsrv_cmd.c \
+ src/responder/pam/pamsrv_json.c \
src/responder/pam/pamsrv_p11.c \
src/responder/pam/pamsrv_gssapi.c \
src/responder/pam/pam_helpers.c \
@@ -2684,6 +2687,7 @@ pam_srv_tests_LDADD = \
$(SSSD_INTERNAL_LTLIBS) \
$(SYSTEMD_DAEMON_LIBS) \
$(GSSAPI_KRB5_LIBS) \
+ $(JANSSON_LIBS) \
libsss_test_common.la \
libsss_idmap.la \
libsss_certmap.la \
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 3bd19f243..0daf4d584 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -37,6 +37,7 @@
#include "responder/common/negcache.h"
#include "providers/data_provider.h"
#include "responder/pam/pamsrv.h"
+#include "responder/pam/pamsrv_json.h"
#include "responder/pam/pamsrv_passkey.h"
#include "responder/pam/pam_helpers.h"
#include "responder/common/cache_req/cache_req.h"
@@ -1535,6 +1536,19 @@ void pam_reply(struct pam_auth_req *preq)
return;
}
#endif /* BUILD_PASSKEY */
+
+#ifdef HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION
+ if (is_pam_json_enabled(pctx->json_services,
+ pd->service)) {
+ ret = generate_json_auth_message(pctx->rctx->cdb, pc_list, pd);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "failed to generate JSON message.\n");
+ goto done;
+ }
+ }
+#endif /* HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION */
+ pc_list_free(pc_list);
}
/*
--
2.54.0
From c85859645da2374789704b8c32474ef73d4c27d4 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Mon, 22 Jan 2024 10:32:48 +0100
Subject: [PATCH 09/36] SSS_CLIENT: forward available auth JSON message
Forward the available authentication mechanisms and their associated
data message to the GUI login using a PAM conversation. Then, obtain the
reply and forward it to the responder, so that it can parse it.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
Signed-off-by: Ray Strode <halfline@redhat.com>
---
src/external/pam.m4 | 7 +++
src/sss_client/pam_message.c | 8 ++++
src/sss_client/pam_message.h | 4 ++
src/sss_client/pam_sss.c | 93 ++++++++++++++++++++++++++++++++++++
src/sss_client/sss_cli.h | 2 +
5 files changed, 114 insertions(+)
diff --git a/src/external/pam.m4 b/src/external/pam.m4
index 0dc7f19d0..844a0a711 100644
--- a/src/external/pam.m4
+++ b/src/external/pam.m4
@@ -39,3 +39,10 @@ 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])])
+
+AS_IF([test x"$found_gdm_pam_extensions" = xyes],
+ [AC_CHECK_HEADER([gdm/gdm-custom-json-pam-extension.h],
+ [AC_DEFINE_UNQUOTED(HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION, 1,
+ [Build with gdm-custom-json-pam-extension support])])])
+AM_CONDITIONAL([HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION],
+ [test x"$found_gdm_pam_extensions" = xyes])
diff --git a/src/sss_client/pam_message.c b/src/sss_client/pam_message.c
index e3a09f501..e98192c11 100644
--- a/src/sss_client/pam_message.c
+++ b/src/sss_client/pam_message.c
@@ -128,6 +128,10 @@ int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer)
len += *pi->requested_domains != '\0' ?
2*sizeof(uint32_t) + pi->requested_domains_size : 0;
len += 3*sizeof(uint32_t); /* flags */
+ len += *pi->json_auth_msg != '\0' ?
+ 2*sizeof(uint32_t) + pi->json_auth_msg_size : 0;
+ len += *pi->json_auth_selected != '\0' ?
+ 2*sizeof(uint32_t) + pi->json_auth_selected_size : 0;
/* optional child_pid */
if(pi->child_pid > 0) {
@@ -178,6 +182,10 @@ int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer)
rp += add_uint32_t_item(SSS_PAM_ITEM_FLAGS, (uint32_t) pi->flags,
&buf[rp]);
+ rp += add_string_item(SSS_PAM_ITEM_JSON_AUTH_INFO, pi->json_auth_msg,
+ pi->json_auth_msg_size, &buf[rp]);
+ rp += add_string_item(SSS_PAM_ITEM_JSON_AUTH_SELECTED, pi->json_auth_selected,
+ pi->json_auth_selected_size, &buf[rp]);
SAFEALIGN_SETMEM_UINT32(buf + rp, SSS_END_OF_PAM_REQUEST, &rp);
diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
index d6fb254f2..c145b8a51 100644
--- a/src/sss_client/pam_message.h
+++ b/src/sss_client/pam_message.h
@@ -66,6 +66,10 @@ struct pam_items {
char *first_factor;
char *passkey_key;
char *passkey_prompt_pin;
+ char *json_auth_msg;
+ size_t json_auth_msg_size;
+ const char *json_auth_selected;
+ size_t json_auth_selected_size;
bool password_prompting;
bool user_name_hint;
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 969ff366d..ca400b03b 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -41,6 +41,10 @@
#include <gdm/gdm-pam-extensions.h>
#endif
+#ifdef HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION
+#include <gdm/gdm-custom-json-pam-extension.h>
+#endif
+
#include "sss_pam_compat.h"
#include "sss_pam_macros.h"
@@ -1350,6 +1354,19 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
break;
}
break;
+ case SSS_PAM_JSON_AUTH_INFO:
+ if (buf[p + (len - 1)] != '\0') {
+ D(("json auth info does not end with \\0."));
+ break;
+ }
+
+ free(pi->json_auth_msg);
+ pi->json_auth_msg = strdup((char *) &buf[p]);
+ if (pi->json_auth_msg == NULL) {
+ D(("strdup failed"));
+ break;
+ }
+ break;
default:
D(("Unknown response type [%d]", type));
}
@@ -1464,6 +1481,10 @@ static int get_pam_items(pam_handle_t *pamh, uint32_t flags,
pi->pc = NULL;
pi->flags = flags;
+ if (pi->json_auth_msg == NULL) pi->json_auth_msg = strdup("");
+ pi->json_auth_msg_size = strlen(pi->json_auth_msg) + 1;
+ if (pi->json_auth_selected == NULL) pi->json_auth_selected = "";
+ pi->json_auth_selected_size = strlen(pi->json_auth_selected) + 1;
return PAM_SUCCESS;
}
@@ -2008,6 +2029,65 @@ done:
return ret;
}
+static int auth_selection_conversation_gdm(pam_handle_t *pamh,
+ struct pam_items *pi)
+{
+#ifdef HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION
+ const struct pam_conv *conv;
+ GdmPamExtensionJSONProtocol *request = NULL;
+ GdmPamExtensionJSONProtocol *response = NULL;
+ struct pam_message prompt_message;
+ const struct pam_message *prompt_messages[1];
+ struct pam_response *reply = NULL;
+ int ret;
+
+ ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv);
+ if (ret != PAM_SUCCESS) {
+ ret = EIO;
+ return ret;
+ }
+
+ request = calloc(1, GDM_PAM_EXTENSION_CUSTOM_JSON_SIZE);
+ if (request == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ GDM_PAM_EXTENSION_CUSTOM_JSON_REQUEST_INIT(request, "auth-mechanisms", 1,
+ pi->json_auth_msg);
+ 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;
+ }
+
+ response = GDM_PAM_EXTENSION_REPLY_TO_CUSTOM_JSON_RESPONSE(reply);
+ if (response->json == NULL) {
+ ret = EIO;
+ goto done;
+ }
+
+ pi->json_auth_msg_size = strlen(pi->json_auth_msg)+1;
+ pi->json_auth_selected = strdup(response->json);
+ pi->json_auth_selected_size = strlen(response->json)+1;
+ ret = EOK;
+
+done:
+ if (request != NULL) {
+ free(request);
+ }
+ free(response);
+
+ return ret;
+#else
+ return ENOTSUP;
+#endif /* HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION */
+}
+
#define SC_PROMPT_FMT "PIN for %s: "
#ifndef discard_const
@@ -3014,6 +3094,19 @@ static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh,
* errors can be ignored here.
*/
}
+
+ if (pi.json_auth_msg != NULL
+ && strcmp(pi.json_auth_msg, "") != 0) {
+ ret = auth_selection_conversation_gdm(pamh, &pi);
+ if (ret == EOK) {
+ break;
+ } else if (ret == ENOTSUP) {
+ D(("gdm-custom-json-pam-extensions not supported."));
+ } else {
+ D(("auth_selection_conversation_gdm failed."));
+ return ret;
+ }
+ }
}
if (flags & PAM_CLI_FLAGS_TRY_CERT_AUTH
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index b99c145c4..0b00c4452 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -415,6 +415,8 @@ enum pam_item_type {
SSS_PAM_ITEM_CHILD_PID,
SSS_PAM_ITEM_REQUESTED_DOMAINS,
SSS_PAM_ITEM_FLAGS,
+ SSS_PAM_ITEM_JSON_AUTH_INFO,
+ SSS_PAM_ITEM_JSON_AUTH_SELECTED,
};
#define PAM_CLI_FLAGS_USE_FIRST_PASS (1 << 0)
--
2.54.0
From dbfb5185be2fb8750a8642bb9ae546ddce95ccd7 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 30 Jan 2024 11:32:25 +0100
Subject: [PATCH 10/36] Responder: parse GUI reply
Parse GUI reply and set the appropriate data in `sss_auth_token`
structure.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_cmd.c | 39 ++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 0daf4d584..6b45c47c7 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -289,6 +289,8 @@ static int pam_parse_in_data_v2(struct pam_data *pd,
uint32_t start;
uint32_t terminator;
char *requested_domains;
+ bool authtok_set = false;
+ bool json_auth_set = false;
if (blen < 4*sizeof(uint32_t)+2) {
DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n");
@@ -366,6 +368,14 @@ static int pam_parse_in_data_v2(struct pam_data *pd,
if (ret != EOK) return ret;
break;
case SSS_PAM_ITEM_AUTHTOK:
+ if (json_auth_set) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failing because SSS_PAM_ITEM_AUTHTOK and " \
+ "SSS_PAM_ITEM_JSON_AUTH_SELECTED are mutually " \
+ "exclusive.\n");
+ return EPERM;
+ }
+ authtok_set = true;
ret = extract_authtok_v2(pd->authtok,
size, body, blen, &c);
if (ret != EOK) return ret;
@@ -380,6 +390,24 @@ static int pam_parse_in_data_v2(struct pam_data *pd,
body, blen, &c);
if (ret != EOK) return ret;
break;
+ case SSS_PAM_ITEM_JSON_AUTH_INFO:
+ ret = extract_string(&pd->json_auth_msg, size, body,
+ blen, &c);
+ if (ret != EOK) return ret;
+ break;
+ case SSS_PAM_ITEM_JSON_AUTH_SELECTED:
+ if (authtok_set) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failing because SSS_PAM_ITEM_AUTHTOK and " \
+ "SSS_PAM_ITEM_JSON_AUTH_SELECTED are mutually " \
+ "exclusive.\n");
+ return EPERM;
+ }
+ json_auth_set = true;
+ ret = extract_string(&pd->json_auth_selected, size, body,
+ blen, &c);
+ if (ret != EOK) return ret;
+ break;
default:
DEBUG(SSSDBG_CRIT_FAILURE,
"Ignoring unknown data type [%d].\n", type);
@@ -1737,6 +1765,17 @@ static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *p
goto done;
}
+#ifdef HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION
+ if (pd->cmd == SSS_PAM_AUTHENTICATE
+ && pd->json_auth_selected != NULL) {
+ ret = json_unpack_auth_reply(pd);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_unpack_auth_reply failed.\n");
+ goto done;
+ }
+ }
+#endif /* HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION */
+
if (pd->logon_name != NULL) {
ret = sss_parse_name_for_domains(pd, cctx->rctx->domains,
cctx->rctx->default_domain,
--
2.54.0
From 970e1df5c8099aeb00f7f2bfa5238024a554d692 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 5 Mar 2024 15:45:40 +0100
Subject: [PATCH 11/36] Test: adapt test_pam_srv to JSON message
Include JSON message where applies.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 6 ++++--
src/tests/cmocka/test_pam_srv.c | 6 ++++++
src/tests/cmocka/test_pamsrv_json.c | 3 ++-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index e59f218b2..98bc3cbaa 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -192,9 +192,10 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
if (password_auth) {
- json_pass = json_pack("{s:s,s:s,s:s}",
+ json_pass = json_pack("{s:s,s:s,s:b,s:s}",
"name", "Password",
"role", "password",
+ "selectable", true,
"prompt", password_prompt);
if (json_pass == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
@@ -212,9 +213,10 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
if (oauth2_auth) {
- json_oauth2 = json_pack("{s:s,s:s,s:s,s:s,s:s,s:s,s:i}",
+ json_oauth2 = json_pack("{s:s,s:s,s:b,s:s,s:s,s:s,s:s,s:i}",
"name", "Web Login",
"role", "eidp",
+ "selectable", true,
"init_prompt", oauth2_init_prompt,
"link_prompt", oauth2_link_prompt,
"uri", uri,
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
index 35ebfbafa..12ae8bea7 100644
--- a/src/tests/cmocka/test_pam_srv.c
+++ b/src/tests/cmocka/test_pam_srv.c
@@ -641,6 +641,8 @@ static void mock_input_pam_passkey(TALLOC_CTX *mem_ctx,
pi.pam_rhost_size = strlen(pi.pam_rhost) + 1;
pi.requested_domains = "";
pi.cli_pid = 12345;
+ pi.json_auth_msg = discard_const("");
+ pi.json_auth_selected = "";
ret = pack_message_v3(&pi, &buf_size, &m_buf);
assert_int_equal(ret, 0);
@@ -736,6 +738,8 @@ static void mock_input_pam_ex(TALLOC_CTX *mem_ctx,
pi.pam_rhost_size = strlen(pi.pam_rhost) + 1;
pi.requested_domains = "";
pi.cli_pid = 12345;
+ pi.json_auth_msg = discard_const("");
+ pi.json_auth_selected = "";
ret = pack_message_v3(&pi, &buf_size, &m_buf);
assert_int_equal(ret, 0);
@@ -817,6 +821,8 @@ static void mock_input_pam_cert(TALLOC_CTX *mem_ctx, const char *name,
pi.pam_rhost_size = strlen(pi.pam_rhost) + 1;
pi.requested_domains = "";
pi.cli_pid = 12345;
+ pi.json_auth_msg = discard_const("");
+ pi.json_auth_selected = "";
ret = pack_message_v3(&pi, &buf_size, &m_buf);
free(pi.pam_authtok);
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 3e1b8b0d1..7322c533d 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -36,9 +36,10 @@
#define BASIC_PASSWORD "\"password\": {" \
"\"name\": \"Password\", \"role\": \"password\", " \
- "\"prompt\": \"Password\"}"
+ "\"selectable\": true, \"prompt\": \"Password\"}"
#define BASIC_OAUTH2 "\"eidp\": {" \
"\"name\": \"Web Login\", \"role\": \"eidp\", " \
+ "\"selectable\": true, " \
"\"init_prompt\": \"" OAUTH2_INIT_PROMPT "\", " \
"\"link_prompt\": \"" OAUTH2_LINK_PROMPT "\", " \
"\"uri\": \"short.url.com/tmp\", \"code\": \"1234-5678\", " \
--
2.54.0
From 6540db69463791cdc54ed5e89ec98701c51f25ae Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Thu, 13 Jun 2024 14:18:46 +0200
Subject: [PATCH 12/36] Responder: check return value for json_string()
It returns NULL on error, but this wasn't checked.
Fixes: ceeffa9e1 ("Responder: generate JSON message for GUI")
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index 98bc3cbaa..898cd4cf5 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -264,6 +264,11 @@ json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority)
if (oauth2_auth) {
json_priority = json_string("eidp");
+ if (json_priority == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
ret = json_array_append_new(root, json_priority);
if (ret == -1) {
DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
@@ -275,6 +280,11 @@ json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority)
if (password_auth) {
json_priority = json_string("password");
+ if (json_priority == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
ret = json_array_append_new(root, json_priority);
if (ret == -1) {
DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
--
2.54.0
From b1307326efaa950de011c7fd48775245a2264647 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Mon, 22 Sep 2025 10:19:51 +0200
Subject: [PATCH 13/36] Responder: update JSON message format
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 18 ++++++++----------
src/tests/cmocka/test_pamsrv_json.c | 19 +++++++++----------
2 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index 898cd4cf5..a434e660a 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -192,10 +192,9 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
if (password_auth) {
- json_pass = json_pack("{s:s,s:s,s:b,s:s}",
+ json_pass = json_pack("{s:s,s:s,s:s}",
"name", "Password",
"role", "password",
- "selectable", true,
"prompt", password_prompt);
if (json_pass == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
@@ -213,12 +212,11 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
if (oauth2_auth) {
- json_oauth2 = json_pack("{s:s,s:s,s:b,s:s,s:s,s:s,s:s,s:i}",
+ json_oauth2 = json_pack("{s:s,s:s,s:s,s:s,s:s,s:s,s:i}",
"name", "Web Login",
"role", "eidp",
- "selectable", true,
- "init_prompt", oauth2_init_prompt,
- "link_prompt", oauth2_link_prompt,
+ "initPrompt", oauth2_init_prompt,
+ "linkPrompt", oauth2_link_prompt,
"uri", uri,
"code", code,
"timeout", 300);
@@ -333,7 +331,7 @@ json_format_auth_selection(TALLOC_CTX *mem_ctx,
}
root = json_pack("{s:{s:o,s:o}}",
- "auth-selection",
+ "authSelection",
"mechanisms", json_mech,
"priority", json_priority);
if (root == NULL) {
@@ -461,7 +459,7 @@ json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg,
}
ret = json_unpack(jroot, "{s:{s:o,s:o}}",
- "auth-selection",
+ "authSelection",
"mechanisms", &json_mechs,
"priority", &json_priority);
if (ret != 0) {
@@ -524,9 +522,9 @@ json_unpack_auth_reply(struct pam_data *pd)
goto done;
}
- ret = json_unpack(jroot, "{s:o}", "auth-selection", &jauth_selection);
+ ret = json_unpack(jroot, "{s:o}", "authSelection", &jauth_selection);
if (ret != 0) {
- DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for auth-selection failed.\n");
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for authSelection failed.\n");
ret = EINVAL;
goto done;
}
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 7322c533d..fcc7928f6 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -36,35 +36,34 @@
#define BASIC_PASSWORD "\"password\": {" \
"\"name\": \"Password\", \"role\": \"password\", " \
- "\"selectable\": true, \"prompt\": \"Password\"}"
+ "\"prompt\": \"Password\"}"
#define BASIC_OAUTH2 "\"eidp\": {" \
"\"name\": \"Web Login\", \"role\": \"eidp\", " \
- "\"selectable\": true, " \
- "\"init_prompt\": \"" OAUTH2_INIT_PROMPT "\", " \
- "\"link_prompt\": \"" OAUTH2_LINK_PROMPT "\", " \
+ "\"initPrompt\": \"" OAUTH2_INIT_PROMPT "\", " \
+ "\"linkPrompt\": \"" OAUTH2_LINK_PROMPT "\", " \
"\"uri\": \"short.url.com/tmp\", \"code\": \"1234-5678\", " \
"\"timeout\": 300}"
#define MECHANISMS_PASSWORD "{" BASIC_PASSWORD "}"
#define MECHANISMS_OAUTH2 "{" BASIC_OAUTH2 "}"
#define PRIORITY_ALL "[\"eidp\", \"password\"]"
-#define AUTH_SELECTION_PASSWORD "{\"auth-selection\": {\"mechanisms\": " \
+#define AUTH_SELECTION_PASSWORD "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_PASSWORD ", " \
"\"priority\": [\"password\"]}}"
-#define AUTH_SELECTION_OAUTH2 "{\"auth-selection\": {\"mechanisms\": " \
+#define AUTH_SELECTION_OAUTH2 "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_OAUTH2 ", " \
"\"priority\": [\"eidp\"]}}"
-#define AUTH_SELECTION_ALL "{\"auth-selection\": {\"mechanisms\": {" \
+#define AUTH_SELECTION_ALL "{\"authSelection\": {\"mechanisms\": {" \
BASIC_PASSWORD ", " \
BASIC_OAUTH2 "}, " \
"\"priority\": " PRIORITY_ALL "}}"
#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}"
-#define AUTH_MECH_REPLY_PASSWORD "{\"auth-selection\": {" \
+#define AUTH_MECH_REPLY_PASSWORD "{\"authSelection\": {" \
"\"status\": \"Ok\", \"password\": " \
PASSWORD_CONTENT "}}"
-#define AUTH_MECH_REPLY_OAUTH2 "{\"auth-selection\": {" \
+#define AUTH_MECH_REPLY_OAUTH2 "{\"authSelection\": {" \
"\"status\": \"Ok\", \"eidp\": {}}}"
-#define AUTH_MECH_ERRONEOUS "{\"auth-selection\": {" \
+#define AUTH_MECH_ERRONEOUS "{\"authSelection\": {" \
"\"status\": \"Ok\", \"lololo\": {}}}"
--
2.54.0
From c1d9b9d10e266e4046c6cfc828a446bcd2f7c3b4 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 4 Jun 2024 10:51:36 +0200
Subject: [PATCH 14/36] sss_client: modify smartcard in prompt_config structure
Integration with GDM requests two prompts for smartcard so modifying the
prompt_config structure. In addition, implement all the functions needed
to manipulate the structure for these new prompts. Finally, add
unit-tests for the new functions.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/sss_client/pam_sss.c | 2 +-
src/sss_client/pam_sss_prompt_config.c | 147 +++++++++++++++++++++++--
src/sss_client/sss_cli.h | 6 +-
src/tests/cmocka/test_prompt_config.c | 45 +++++++-
4 files changed, 181 insertions(+), 19 deletions(-)
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index ca400b03b..ba25959d5 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -2604,7 +2604,7 @@ static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi)
pc_get_passkey_inter_prompt(pi->pc[c]),
pc_get_passkey_touch_prompt(pi->pc[c]));
break;
- case PC_TYPE_SC_PIN:
+ case PC_TYPE_SMARTCARD:
ret = prompt_sc_pin(pamh, pi);
/* Todo: add extra string option */
break;
diff --git a/src/sss_client/pam_sss_prompt_config.c b/src/sss_client/pam_sss_prompt_config.c
index 891fcd60c..caf308c82 100644
--- a/src/sss_client/pam_sss_prompt_config.c
+++ b/src/sss_client/pam_sss_prompt_config.c
@@ -45,8 +45,9 @@ struct prompt_config_passkey {
char *prompt_touch;
};
-struct prompt_config_sc_pin {
- char *prompt; /* Currently not used */
+struct prompt_config_smartcard {
+ char *prompt_init;
+ char *prompt_pin;
};
struct prompt_config_eidp {
@@ -61,7 +62,7 @@ struct prompt_config {
struct prompt_config_2fa two_fa;
struct prompt_config_2fa_single two_fa_single;
struct prompt_config_passkey passkey;
- struct prompt_config_sc_pin sc_pin;
+ struct prompt_config_smartcard smartcard;
struct prompt_config_eidp eidp;
} data;
};
@@ -138,6 +139,22 @@ const char *pc_get_eidp_link_prompt(struct prompt_config *pc)
return NULL;
}
+const char *pc_get_smartcard_init_prompt(struct prompt_config *pc)
+{
+ if (pc != NULL && (pc_get_type(pc) == PC_TYPE_SMARTCARD)) {
+ return pc->data.smartcard.prompt_init;
+ }
+ return NULL;
+}
+
+const char *pc_get_smartcard_pin_prompt(struct prompt_config *pc)
+{
+ if (pc != NULL && (pc_get_type(pc) == PC_TYPE_SMARTCARD)) {
+ return pc->data.smartcard.prompt_pin;
+ }
+ return NULL;
+}
+
static void pc_free_passkey(struct prompt_config *pc)
{
if (pc != NULL && pc_get_type(pc) == PC_TYPE_PASSKEY) {
@@ -178,11 +195,13 @@ static void pc_free_2fa_single(struct prompt_config *pc)
return;
}
-static void pc_free_sc_pin(struct prompt_config *pc)
+static void pc_free_smartcard(struct prompt_config *pc)
{
- if (pc != NULL && pc_get_type(pc) == PC_TYPE_SC_PIN) {
- free(pc->data.sc_pin.prompt);
- pc->data.sc_pin.prompt = NULL;
+ if (pc != NULL && pc_get_type(pc) == PC_TYPE_SMARTCARD) {
+ free(pc->data.smartcard.prompt_init);
+ pc->data.smartcard.prompt_init = NULL;
+ free(pc->data.smartcard.prompt_pin);
+ pc->data.smartcard.prompt_pin = NULL;
}
return;
}
@@ -218,8 +237,8 @@ void pc_list_free(struct prompt_config **pc_list)
case PC_TYPE_2FA_SINGLE:
pc_free_2fa_single(pc_list[c]);
break;
- case PC_TYPE_SC_PIN:
- pc_free_sc_pin(pc_list[c]);
+ case PC_TYPE_SMARTCARD:
+ pc_free_smartcard(pc_list[c]);
break;
case PC_TYPE_PASSKEY:
pc_free_passkey(pc_list[c]);
@@ -479,6 +498,53 @@ done:
return ret;
}
+errno_t pc_list_add_smartcard(struct prompt_config ***pc_list,
+ const char *prompt_init, const char *prompt_pin)
+{
+ struct prompt_config *pc;
+ int ret;
+
+ if (pc_list == NULL) {
+ return EINVAL;
+ }
+
+ pc = calloc(1, sizeof(struct prompt_config));
+ if (pc == NULL) {
+ return ENOMEM;
+ }
+
+ pc->type = PC_TYPE_SMARTCARD;
+
+ pc->data.smartcard.prompt_init = strdup(prompt_init != NULL ? prompt_init
+ : "");
+ if (pc->data.smartcard.prompt_init == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ pc->data.smartcard.prompt_pin = strdup(prompt_pin != NULL ? prompt_pin
+ : "");
+ if (pc->data.smartcard.prompt_pin == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = pc_list_add_pc(pc_list, pc);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ free(pc->data.smartcard.prompt_init);
+ free(pc->data.smartcard.prompt_pin);
+ free(pc);
+ }
+
+ return ret;
+}
+
errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
uint8_t **data)
{
@@ -516,7 +582,11 @@ errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
l += sizeof(uint32_t);
l += strlen(pc_list[c]->data.passkey.prompt_touch);
break;
- case PC_TYPE_SC_PIN:
+ case PC_TYPE_SMARTCARD:
+ l += sizeof(uint32_t);
+ l += strlen(pc_list[c]->data.smartcard.prompt_init);
+ l += sizeof(uint32_t);
+ l += strlen(pc_list[c]->data.smartcard.prompt_pin);
break;
case PC_TYPE_EIDP:
l += sizeof(uint32_t);
@@ -581,7 +651,17 @@ errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
safealign_memcpy(&d[rp], pc_list[c]->data.passkey.prompt_touch,
strlen(pc_list[c]->data.passkey.prompt_touch), &rp);
break;
- case PC_TYPE_SC_PIN:
+ case PC_TYPE_SMARTCARD:
+ SAFEALIGN_SET_UINT32(&d[rp],
+ strlen(pc_list[c]->data.smartcard.prompt_init),
+ &rp);
+ safealign_memcpy(&d[rp], pc_list[c]->data.smartcard.prompt_init,
+ strlen(pc_list[c]->data.smartcard.prompt_init), &rp);
+ SAFEALIGN_SET_UINT32(&d[rp],
+ strlen(pc_list[c]->data.smartcard.prompt_pin),
+ &rp);
+ safealign_memcpy(&d[rp], pc_list[c]->data.smartcard.prompt_pin,
+ strlen(pc_list[c]->data.smartcard.prompt_pin), &rp);
break;
case PC_TYPE_EIDP:
SAFEALIGN_SET_UINT32(&d[rp],
@@ -780,7 +860,50 @@ errno_t pc_list_from_response(int size, uint8_t *buf,
goto done;
}
break;
- case PC_TYPE_SC_PIN:
+ case PC_TYPE_SMARTCARD:
+ if (rp > size - sizeof(uint32_t)) {
+ ret = EINVAL;
+ goto done;
+ }
+ SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+ if (l > size || rp > size - l) {
+ ret = EINVAL;
+ goto done;
+ }
+ str = strndup((char *) buf + rp, l);
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ rp += l;
+
+ if (rp > size - sizeof(uint32_t)) {
+ free(str);
+ ret = EINVAL;
+ goto done;
+ }
+ SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+ if (l > size || rp > size - l) {
+ free(str);
+ ret = EINVAL;
+ goto done;
+ }
+ str2 = strndup((char *) buf + rp, l);
+ if (str2 == NULL) {
+ free(str);
+ ret = ENOMEM;
+ goto done;
+ }
+ rp += l;
+
+ ret = pc_list_add_smartcard(&pl, str, str2);
+ free(str);
+ free(str2);
+ if (ret != EOK) {
+ goto done;
+ }
break;
case PC_TYPE_EIDP:
if (rp > size - sizeof(uint32_t)) {
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 0b00c4452..4e3c58c31 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -670,7 +670,7 @@ enum prompt_config_type {
PC_TYPE_2FA,
PC_TYPE_2FA_SINGLE,
PC_TYPE_PASSKEY,
- PC_TYPE_SC_PIN,
+ PC_TYPE_SMARTCARD,
PC_TYPE_EIDP,
PC_TYPE_LAST
};
@@ -686,6 +686,8 @@ const char *pc_get_passkey_inter_prompt(struct prompt_config *pc);
const char *pc_get_passkey_touch_prompt(struct prompt_config *pc);
const char *pc_get_eidp_init_prompt(struct prompt_config *pc);
const char *pc_get_eidp_link_prompt(struct prompt_config *pc);
+const char *pc_get_smartcard_init_prompt(struct prompt_config *pc);
+const char *pc_get_smartcard_pin_prompt(struct prompt_config *pc);
errno_t pc_list_add_passkey(struct prompt_config ***pc_list,
const char *inter_prompt,
const char *touch_prompt);
@@ -698,6 +700,8 @@ errno_t pc_list_add_2fa_single(struct prompt_config ***pc_list,
const char *prompt);
errno_t pc_list_add_eidp(struct prompt_config ***pc_list,
const char *prompt_init, const char *prompt_link);
+errno_t pc_list_add_smartcard(struct prompt_config ***pc_list,
+ const char *prompt_init, const char *prompt_pin);
errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
uint8_t **data);
errno_t pc_list_from_response(int size, uint8_t *buf,
diff --git a/src/tests/cmocka/test_prompt_config.c b/src/tests/cmocka/test_prompt_config.c
index 70b27875a..99f2ebc4b 100644
--- a/src/tests/cmocka/test_prompt_config.c
+++ b/src/tests/cmocka/test_prompt_config.c
@@ -117,6 +117,23 @@ void test_pc_list_add_eidp(void **state)
pc_list_free(pc_list);
}
+void test_pc_list_add_smartcard(void **state)
+{
+ int ret;
+ struct prompt_config **pc_list = NULL;
+
+ ret = pc_list_add_smartcard(&pc_list, "init", "PIN");
+ assert_int_equal(ret, EOK);
+ assert_non_null(pc_list);
+ assert_non_null(pc_list[0]);
+ assert_int_equal(PC_TYPE_SMARTCARD, pc_get_type(pc_list[0]));
+ assert_string_equal("init", pc_get_smartcard_init_prompt(pc_list[0]));
+ assert_string_equal("PIN", pc_get_smartcard_pin_prompt(pc_list[0]));
+ assert_null(pc_list[1]);
+
+ pc_list_free(pc_list);
+}
+
void test_pam_get_response_prompt_config(void **state)
{
int ret;
@@ -136,15 +153,24 @@ void test_pam_get_response_prompt_config(void **state)
ret = pc_list_add_eidp(&pc_list, "init", "link");
assert_int_equal(ret, EOK);
+ ret = pc_list_add_smartcard(&pc_list, "init", "PIN");
+ assert_int_equal(ret, EOK);
+
ret = pam_get_response_prompt_config(pc_list, &len, &data);
pc_list_free(pc_list);
assert_int_equal(ret, EOK);
- assert_int_equal(len, 77);
+ assert_int_equal(len, 96);
#if __BYTE_ORDER == __LITTLE_ENDIAN
- assert_memory_equal(data, "\4\0\0\0\1\0\0\0\10\0\0\0" "password\2\0\0\0\5\0\0\0" "first\6\0\0\0" "second\3\0\0\0\6\0\0\0" "single\6\0\0\0\4\0\0\0" "init\4\0\0\0" "link", len);
+ assert_memory_equal(data, "\5\0\0\0\1\0\0\0\10\0\0\0" "password\2\0\0\0\5\0\0\0"
+ "first\6\0\0\0" "second\3\0\0\0\6\0\0\0" "single\6\0\0\0\4\0\0\0"
+ "init\4\0\0\0" "link\5\0\0\0\4\0\0\0"
+ "init\3\0\0\0" "PIN", len);
#else
- assert_memory_equal(data, "\0\0\0\4\0\0\0\1\0\0\0\10" "password\0\0\0\2\0\0\0\5" "first\0\0\0\6" "second\0\0\0\3\0\0\0\6" "single\0\0\0\6\0\0\0\4" "init\0\0\0\4" "link", len);
+ assert_memory_equal(data, "\0\0\0\5\0\0\0\1\0\0\0\10" "password\0\0\0\2\0\0\0\5"
+ "first\0\0\0\6" "second\0\0\0\3\0\0\0\6" "single\0\0\0\6\0\0\0\4"
+ "init\0\0\0\4" "link\0\0\0\5\0\0\0\4"
+ "init\0\0\0\3" "PIN", len);
#endif
free(data);
@@ -169,10 +195,13 @@ void test_pc_list_from_response(void **state)
ret = pc_list_add_eidp(&pc_list, "init", "link");
assert_int_equal(ret, EOK);
+ ret = pc_list_add_smartcard(&pc_list, "init", "PIN");
+ assert_int_equal(ret, EOK);
+
ret = pam_get_response_prompt_config(pc_list, &len, &data);
pc_list_free(pc_list);
assert_int_equal(ret, EOK);
- assert_int_equal(len, 77);
+ assert_int_equal(len, 96);
pc_list = NULL;
@@ -199,7 +228,12 @@ void test_pc_list_from_response(void **state)
assert_string_equal("init", pc_get_eidp_init_prompt(pc_list[3]));
assert_string_equal("link", pc_get_eidp_link_prompt(pc_list[3]));
- assert_null(pc_list[4]);
+ assert_non_null(pc_list[4]);
+ assert_int_equal(PC_TYPE_SMARTCARD, pc_get_type(pc_list[4]));
+ assert_string_equal("init", pc_get_smartcard_init_prompt(pc_list[4]));
+ assert_string_equal("PIN", pc_get_smartcard_pin_prompt(pc_list[4]));
+
+ assert_null(pc_list[5]);
pc_list_free(pc_list);
}
@@ -219,6 +253,7 @@ int main(int argc, const char *argv[])
cmocka_unit_test(test_pc_list_add_2fa_single),
cmocka_unit_test(test_pc_list_add_2fa),
cmocka_unit_test(test_pc_list_add_eidp),
+ cmocka_unit_test(test_pc_list_add_smartcard),
cmocka_unit_test(test_pam_get_response_prompt_config),
cmocka_unit_test(test_pc_list_from_response),
};
--
2.54.0
From 7fd02b5ae107f102f05dc2125d3ddd31cd48275b Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 12 Jun 2024 15:37:31 +0200
Subject: [PATCH 15/36] util: implement pam_get_response_data_all_same_type()
This API gets all the elements with the selected response type data from
the response_data linked list. Includes unit tests.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/tests/cmocka/test_sss_pam_data.c | 73 ++++++++++++++++++++++++++++
src/util/sss_pam_data.c | 66 +++++++++++++++++++++++++
src/util/sss_pam_data.h | 19 ++++++++
3 files changed, 158 insertions(+)
diff --git a/src/tests/cmocka/test_sss_pam_data.c b/src/tests/cmocka/test_sss_pam_data.c
index 442b37372..ce4d328c7 100644
--- a/src/tests/cmocka/test_sss_pam_data.c
+++ b/src/tests/cmocka/test_sss_pam_data.c
@@ -34,6 +34,34 @@
#define OAUTH2_STR OAUTH2_URI OAUTH2_CODE
#define CCACHE_NAME "KRB5CCNAME=KCM:"
+#define SC1_CERT_USER "cert_user1\0"
+#define SC1_TOKEN_NAME "token_name1\0"
+#define SC1_MODULE_NAME "module_name1\0"
+#define SC1_KEY_ID "key_id1\0"
+#define SC1_LABEL "label1\0"
+#define SC1_PROMPT_STR "prompt1\0"
+#define SC1_PAM_CERT_USER "pam_cert_user1"
+#define SC1_STR SC1_CERT_USER SC1_TOKEN_NAME SC1_MODULE_NAME SC1_KEY_ID \
+ SC1_LABEL SC1_PROMPT_STR SC1_PAM_CERT_USER
+#define SC2_CERT_USER "cert_user2\0"
+#define SC2_TOKEN_NAME "token_name2\0"
+#define SC2_MODULE_NAME "module_name2\0"
+#define SC2_KEY_ID "key_id2\0"
+#define SC2_LABEL "label2\0"
+#define SC2_PROMPT_STR "prompt2\0"
+#define SC2_PAM_CERT_USER "pam_cert_user2"
+#define SC2_STR SC2_CERT_USER SC2_TOKEN_NAME SC2_MODULE_NAME SC2_KEY_ID \
+ SC2_LABEL SC2_PROMPT_STR SC2_PAM_CERT_USER
+#define SC3_CERT_USER "cert_user3\0"
+#define SC3_TOKEN_NAME "token_name3\0"
+#define SC3_MODULE_NAME "module_name3\0"
+#define SC3_KEY_ID "key_id3\0"
+#define SC3_LABEL "label3\0"
+#define SC3_PROMPT_STR "prompt3\0"
+#define SC3_PAM_CERT_USER "pam_cert_user3"
+#define SC3_STR SC3_CERT_USER SC3_TOKEN_NAME SC3_MODULE_NAME SC3_KEY_ID \
+ SC3_LABEL SC3_PROMPT_STR SC3_PAM_CERT_USER
+
/***********************
* TEST
@@ -120,6 +148,50 @@ void test_pam_get_response_data_three_elements(void **state)
talloc_free(test_ctx);
}
+void test_pam_get_response_data_three_same_elements(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ uint8_t **buf = NULL;
+ int32_t *expected_len = NULL;
+ int32_t *result_len = NULL;
+ int num;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ expected_len = talloc_array(test_ctx, int32_t, 3);
+ assert_non_null(expected_len);
+ pd->resp_list = NULL;
+ expected_len[0] = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
+ strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
+ strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
+ pam_add_response(pd, SSS_PAM_CERT_INFO, expected_len[0], discard_const(SC1_STR));
+ expected_len[1] = strlen(SC2_CERT_USER)+1+strlen(SC2_TOKEN_NAME)+1+
+ strlen(SC2_MODULE_NAME)+1+strlen(SC2_KEY_ID)+1+strlen(SC2_LABEL)+1+
+ strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
+ pam_add_response(pd, SSS_PAM_CERT_INFO, expected_len[1], discard_const(SC2_STR));
+ expected_len[2] = strlen(SC3_CERT_USER)+1+strlen(SC3_TOKEN_NAME)+1+
+ strlen(SC3_MODULE_NAME)+1+strlen(SC3_KEY_ID)+1+strlen(SC3_LABEL)+1+
+ strlen(SC3_PROMPT_STR)+1+strlen(SC3_PAM_CERT_USER)+1;
+ pam_add_response(pd, SSS_PAM_CERT_INFO, expected_len[2], discard_const(SC3_STR));
+
+ ret = pam_get_response_data_all_same_type(test_ctx, pd, SSS_PAM_CERT_INFO,
+ &buf, &result_len, &num);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(num, 3);
+ assert_int_equal(result_len[0], expected_len[0]);
+ assert_string_equal((const char*) buf[0], SC3_STR);
+ assert_int_equal(result_len[1], expected_len[1]);
+ assert_string_equal((const char*) buf[1], SC2_STR);
+ assert_int_equal(result_len[2], expected_len[2]);
+ assert_string_equal((const char*) buf[2], SC1_STR);
+
+ talloc_free(test_ctx);
+}
+
static void test_parse_supp_valgrind_args(void)
{
/*
@@ -144,6 +216,7 @@ int main(int argc, const char *argv[])
cmocka_unit_test(test_pam_get_response_data_not_found),
cmocka_unit_test(test_pam_get_response_data_one_element),
cmocka_unit_test(test_pam_get_response_data_three_elements),
+ cmocka_unit_test(test_pam_get_response_data_three_same_elements),
};
/* Set debug level to invalid value so we can decide if -d 0 was used. */
diff --git a/src/util/sss_pam_data.c b/src/util/sss_pam_data.c
index 75421d8e0..da7a9f19f 100644
--- a/src/util/sss_pam_data.c
+++ b/src/util/sss_pam_data.c
@@ -237,3 +237,69 @@ pam_get_response_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, int32_t type,
done:
return ret;
}
+
+errno_t
+pam_get_response_data_all_same_type(TALLOC_CTX *mem_ctx, struct pam_data *pd,
+ int32_t type, uint8_t ***_buf,
+ int32_t **_len, int *_num)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct response_data *rdata = pd->resp_list;
+ uint8_t **buf = NULL;
+ int32_t *len = NULL;
+ int count = 0;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ while (rdata != NULL) {
+ if (rdata->type == type) {
+ count++;
+ }
+ rdata = rdata->next;
+ }
+
+ if (count == 0) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ buf = talloc_array(tmp_ctx, uint8_t*, count);
+ len = talloc_array(tmp_ctx, int32_t, count);
+ if (buf == NULL || len == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ count = 0;
+ rdata = pd->resp_list;
+ while (rdata != NULL) {
+ if (rdata->type == type) {
+ buf[count] = talloc_memdup(buf, rdata->data, rdata->len);
+ if (buf[count] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ len[count] = rdata->len;
+ count++;
+ }
+
+ rdata = rdata->next;
+ }
+
+ *_buf = talloc_steal(mem_ctx, buf);
+ *_len = talloc_steal(mem_ctx, len);
+ *_num = count;
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
diff --git a/src/util/sss_pam_data.h b/src/util/sss_pam_data.h
index 441720e97..fa83c4a1c 100644
--- a/src/util/sss_pam_data.h
+++ b/src/util/sss_pam_data.h
@@ -115,4 +115,23 @@ errno_t
pam_get_response_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, int32_t type,
uint8_t **_buf, int32_t *_len);
+/**
+ * @brief Get the all the elements with the selected response type data from
+ * the response_data linked list
+ *
+ * @param[in] mem_ctx Memory context
+ * @param[in] pd Data structure containing the response_data linked list
+ * @param[in] type Response type
+ * @param[out] _buf Data wrapped inside response_data structure
+ * @param[out] _len Data length
+ * @param[out] _num Number of elements with the selected type
+ *
+ * @return 0 if the data was obtained properly,
+ * error code otherwise.
+ */
+errno_t
+pam_get_response_data_all_same_type(TALLOC_CTX *mem_ctx, struct pam_data *pd,
+ int32_t type, uint8_t ***_buf,
+ int32_t **_len, int *_num);
+
#endif /* _SSS_PAM_DATA_H_ */
--
2.54.0
From faeec86c1c9e7c005ff57763c931e4c1323a1492 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 9 Apr 2024 12:05:56 +0200
Subject: [PATCH 16/36] Responder: generate JSON message for smartcard
Implement a set of functions to retrieve the smartcard data and generate
the JSON message with it.
Implement new unit test and adapt the existing ones to take into account
the new data.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 387 +++++++++++++++++++++++++++-
src/responder/pam/pamsrv_json.h | 49 +++-
src/tests/cmocka/test_pamsrv_json.c | 307 +++++++++++++++++++++-
3 files changed, 725 insertions(+), 18 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index a434e660a..7c778c42a 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -22,8 +22,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
#include <errno.h>
#include <stdbool.h>
+#include <stdio.h>
#include <string.h>
#include "responder/pam/pamsrv.h"
@@ -31,6 +36,20 @@
#include "pamsrv_json.h"
+struct cert_auth_info {
+ char *cert_user;
+ char *cert;
+ char *token_name;
+ char *module_name;
+ char *key_id;
+ char *label;
+ char *prompt_str;
+ char *pam_cert_user;
+ char *choice_list_id;
+ struct cert_auth_info *prev;
+ struct cert_auth_info *next;
+};
+
static errno_t
obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, char **_uri,
@@ -130,12 +149,15 @@ done:
static errno_t
obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
struct prompt_config **pc_list, const char **_password_prompt,
- const char **_oauth2_init_prompt, const char **_oauth2_link_prompt)
+ const char **_oauth2_init_prompt, const char **_oauth2_link_prompt,
+ const char **_sc_init_prompt, const char **_sc_pin_prompt)
{
TALLOC_CTX *tmp_ctx = NULL;
char *password_prompt = NULL;
char *oauth2_init_prompt = NULL;
char *oauth2_link_prompt = NULL;
+ char *sc_init_prompt = NULL;
+ char *sc_pin_prompt = NULL;
errno_t ret;
tmp_ctx = talloc_new(NULL);
@@ -161,9 +183,271 @@ obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
goto done;
}
+ sc_init_prompt = talloc_strdup(tmp_ctx, SC_INIT_PROMPT);
+ if (sc_init_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ sc_pin_prompt = talloc_strdup(tmp_ctx, SC_PIN_PROMPT);
+ if (sc_pin_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
*_password_prompt = talloc_steal(mem_ctx, password_prompt);
*_oauth2_init_prompt = talloc_steal(mem_ctx, oauth2_init_prompt);
*_oauth2_link_prompt = talloc_steal(mem_ctx, oauth2_link_prompt);
+ *_sc_init_prompt = talloc_steal(mem_ctx, sc_init_prompt);
+ *_sc_pin_prompt = talloc_steal(mem_ctx, sc_pin_prompt);
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+errno_t
+get_cert_list(TALLOC_CTX *mem_ctx, struct pam_data *pd,
+ struct cert_auth_info **_cert_list)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct cert_auth_info *cert_list = NULL;
+ struct cert_auth_info *cai = NULL;
+ uint8_t **sc = NULL;
+ int32_t *len = NULL;
+ int32_t offset;
+ int32_t str_len;
+ int num;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = pam_get_response_data_all_same_type(tmp_ctx, pd, SSS_PAM_CERT_INFO,
+ &sc, &len, &num);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get SSS_PAM_CERT_INFO, ret %d.\n",
+ ret);
+ goto done;
+ }
+
+ for (int i = 0; i < num; i++) {
+ cai = talloc_zero(tmp_ctx, struct cert_auth_info);
+ if (cai == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i], len[i]);
+ if (str_len >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "cert_user string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->cert_user = talloc_strndup(cai, (const char *)sc[i], str_len);
+ if (cai->cert_user == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset = str_len + 1;
+
+ if (offset >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i] + offset, len[i] - offset);
+ if (str_len >= (len[i] - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "token_name string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->token_name = talloc_strndup(cai, (const char *)sc[i] + offset, str_len);
+ if (cai->token_name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ if (offset >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i] + offset, len[i] - offset);
+ if (str_len >= (len[i] - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "module_name string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->module_name = talloc_strndup(cai, (const char *)sc[i] + offset, str_len);
+ if (cai->module_name == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ if (offset >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i] + offset, len[i] - offset);
+ if (str_len >= (len[i] - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "key_id string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->key_id = talloc_strndup(cai, (const char *)sc[i] + offset, str_len);
+ if (cai->key_id == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ if (offset >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i] + offset, len[i] - offset);
+ if (str_len >= (len[i] - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "label string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->label = talloc_strndup(cai, (const char *)sc[i] + offset, str_len);
+ if (cai->label == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ if (offset >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i] + offset, len[i] - offset);
+ if (str_len >= (len[i] - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "prompt_str string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->prompt_str = talloc_strndup(cai, (const char *)sc[i] + offset, str_len);
+ if (cai->prompt_str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ if (offset >= len[i]) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Trying to access data outside of the boundaries.\n");
+ ret = EPERM;
+ goto done;
+ }
+
+ str_len = strnlen((const char *)sc[i] + offset, len[i] - offset);
+ if (str_len >= (len[i] - offset)) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "pam_cert_user string is not null-terminated within buffer bounds.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ cai->pam_cert_user = talloc_strndup(cai, (const char *)sc[i] + offset, str_len);
+ if (cai->pam_cert_user == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ offset += str_len + 1;
+
+ DEBUG(SSSDBG_FUNC_DATA,
+ "cert_user %s, token_name %s, module_name %s, key_id %s,"
+ "label %s, prompt_str %s, pam_cert_user %s.\n",
+ cai->cert_user, cai->token_name, cai->module_name, cai->key_id,
+ cai->label, cai->prompt_str, cai->pam_cert_user);
+
+ DLIST_ADD(cert_list, cai);
+ }
+
+ DLIST_FOR_EACH(cai, cert_list) {
+ talloc_steal(mem_ctx, cai);
+ }
+ *_cert_list = cert_list;
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+errno_t
+get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
+ char ***_names)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct cert_auth_info *item = NULL;
+ char **names = NULL;
+ int i = 0;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DLIST_FOR_EACH(item, cert_list) {
+ i++;
+ }
+
+ names = talloc_array(tmp_ctx, char *, i+1);
+ if (names == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ DLIST_FOR_EACH(item, cert_list) {
+ names[i] = talloc_strdup(names, item->prompt_str);
+ if (names[i] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ i++;
+ }
+ names[i] = NULL;
+
+ *_names = talloc_steal(mem_ctx, names);
ret = EOK;
done:
@@ -177,11 +461,16 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
bool oauth2_auth, const char *uri, const char *code,
const char *oauth2_init_prompt,
const char *oauth2_link_prompt,
+ bool sc_auth, char **sc_names,
+ const char *sc_init_prompt,
+ const char *sc_pin_prompt,
json_t **_list_mech)
{
json_t *root = NULL;
json_t *json_pass = NULL;
json_t *json_oauth2 = NULL;
+ json_t *json_sc = NULL;
+ char *key = NULL;
int ret;
root = json_object();
@@ -235,6 +524,38 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
}
+ if (sc_auth) {
+ for (int i = 0; sc_names[i] != NULL; i++) {
+ json_sc = json_pack("{s:s,s:s,s:b,s:s,s:s}",
+ "name", sc_names[i],
+ "role", "smartcard",
+ "selectable", true,
+ "init_instruction", sc_init_prompt,
+ "pin_prompt", sc_pin_prompt);
+ if (json_sc == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = asprintf(&key, "smartcard:%d", i+1);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = json_object_set_new(root, key, json_sc);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_pass);
+ ret = ENOMEM;
+ goto done;
+ }
+ free(key);
+ }
+ }
+
*_list_mech = root;
ret = EOK;
@@ -247,10 +568,12 @@ done:
}
errno_t
-json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority)
+json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
+ char **sc_names, json_t **_priority)
{
json_t *root = NULL;
json_t *json_priority = NULL;
+ char *key = NULL;
int ret;
root = json_array();
@@ -276,6 +599,31 @@ json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority)
}
}
+ if (sc_auth) {
+ for (int i = 0; sc_names[i] != NULL; i++) {
+ ret = asprintf(&key, "smartcard:%d", i+1);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "asprintf failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ json_priority = json_string(key);
+ free(key);
+ if (json_priority == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = json_array_append_new(root, json_priority);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_priority);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ }
+
if (password_auth) {
json_priority = json_string("password");
if (json_priority == NULL) {
@@ -309,6 +657,9 @@ json_format_auth_selection(TALLOC_CTX *mem_ctx,
bool oauth2_auth, const char *uri, const char *code,
const char *oauth2_init_prompt,
const char *oauth2_link_prompt,
+ bool sc_auth, char **sc_names,
+ const char *sc_init_prompt,
+ const char *sc_pin_prompt,
char **_result)
{
json_t *root = NULL;
@@ -318,13 +669,17 @@ json_format_auth_selection(TALLOC_CTX *mem_ctx,
int ret;
ret = json_format_mechanisms(password_auth, password_prompt,
- oauth2_auth, uri, code, oauth2_init_prompt,
- oauth2_link_prompt, &json_mech);
+ oauth2_auth, uri, code,
+ oauth2_init_prompt, oauth2_link_prompt,
+ sc_auth, sc_names,
+ sc_init_prompt, sc_pin_prompt,
+ &json_mech);
if (ret != EOK) {
goto done;
}
- ret = json_format_priority(password_auth, oauth2_auth, &json_priority);
+ ret = json_format_priority(password_auth, oauth2_auth, sc_auth, sc_names,
+ &json_priority);
if (ret != EOK) {
json_decref(json_mech);
goto done;
@@ -365,13 +720,18 @@ generate_json_auth_message(struct confdb_ctx *cdb,
struct pam_data *_pd)
{
TALLOC_CTX *tmp_ctx = NULL;
+ struct cert_auth_info *cert_list = NULL;
const char *password_prompt = NULL;
const char *oauth2_init_prompt = NULL;
const char *oauth2_link_prompt = NULL;
+ const char *sc_init_prompt = NULL;
+ const char *sc_pin_prompt = NULL;
char *oauth2_uri = NULL;
char *oauth2_code = NULL;
+ char **sc_names = NULL;
char *result = NULL;
bool oauth2_auth = true;
+ bool sc_auth = true;
int ret;
tmp_ctx = talloc_new(NULL);
@@ -380,7 +740,8 @@ generate_json_auth_message(struct confdb_ctx *cdb,
}
ret = obtain_prompts(cdb, tmp_ctx, pc_list, &password_prompt,
- &oauth2_init_prompt, &oauth2_link_prompt);
+ &oauth2_init_prompt, &oauth2_link_prompt,
+ &sc_init_prompt, &sc_pin_prompt);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain the prompts.\n");
goto done;
@@ -393,9 +754,23 @@ generate_json_auth_message(struct confdb_ctx *cdb,
goto done;
}
+ ret = get_cert_list(tmp_ctx, _pd, &cert_list);
+ if (ret == ENOENT) {
+ sc_auth = false;
+ } else if (ret != EOK) {
+ goto done;
+ }
+
+ ret = get_cert_names(tmp_ctx, cert_list, &sc_names);
+ if (ret != EOK) {
+ goto done;
+ }
+
ret = json_format_auth_selection(tmp_ctx, true, password_prompt,
oauth2_auth, oauth2_uri, oauth2_code,
oauth2_init_prompt, oauth2_link_prompt,
+ sc_auth, sc_names,
+ sc_init_prompt, sc_pin_prompt,
&result);
if (ret != EOK) {
goto done;
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 4c8c3aef3..380bd65f7 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -33,8 +33,38 @@
#define PASSWORD_PROMPT "Password"
#define OAUTH2_INIT_PROMPT "Log In"
#define OAUTH2_LINK_PROMPT "Log in online with another device"
+#define SC_INIT_PROMPT "Insert smartcard"
+#define SC_PIN_PROMPT "PIN"
+/**
+ * @brief Extract smartcard certificate list from pam_data structure
+ *
+ * @param[in] mem_ctx Memory context
+ * @param[in] pd pam_data containing the certificates
+ * @param[out] _cert_list Certificate list
+ *
+ * @return 0 if the data was extracted successfully,
+ * error code otherwise.
+ */
+errno_t
+get_cert_list(TALLOC_CTX *mem_ctx, struct pam_data *pd,
+ struct cert_auth_info **_cert_list);
+
+/**
+ * @brief Extract smartcard certificate name list from the certificate list
+ *
+ * @param[in] mem_ctx Memory context
+ * @param[in] cert_list Certificate list
+ * @param[out] _names Certificate names list
+ *
+ * @return 0 if the data was extracted successfully,
+ * error code otherwise.
+ */
+errno_t
+get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
+ char ***_names);
+
/**
* @brief Format authentication mechanisms to JSON
*
@@ -45,6 +75,10 @@
* @param[in] code OAUTH2 code
* @param[in] oauth2_init_prompt OAUTH2 initial prompt
* @param[in] oauth2_link_prompt OAUTH2 link prompt
+ * @param[in] sc_auth Whether smartcard authentication is allowed
+ * @param[in] sc_names smartcard names
+ * @param[in] sc_init_prompt smartcard initial prompt
+ * @param[in] sc_pin_prompt smartcard PIN prompt
* @param[out] _list_mech authentication mechanisms JSON object
*
* @return 0 if the authentication mechanisms were formatted properly,
@@ -55,6 +89,9 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
bool oauth2_auth, const char *uri, const char *code,
const char *oauth2_init_prompt,
const char *oauth2_link_prompt,
+ bool sc_auth, char **sc_names,
+ const char *sc_init_prompt,
+ const char *sc_pin_prompt,
json_t **_list_mech);
/**
@@ -62,13 +99,16 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
*
* @param[in] password_auth Whether password authentication is allowed
* @param[in] oath2_auth Whether OAUTH2 authentication is allowed
+ * @param[in] sc_auth Whether smartcard authentication is allowed
+ * @param[in] sc_names Smartcard certificate names
* @param[out] _priority priority JSON object
*
* @return 0 if the priority was formatted properly,
* error code otherwise.
*/
errno_t
-json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority);
+json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
+ char **sc_names, json_t **_priority);
/**
* @brief Format data to JSON
@@ -81,6 +121,10 @@ json_format_priority(bool password_auth, bool oauth2_auth, json_t **_priority);
* @param[in] code OAUTH2 code
* @param[in] oauth2_init_prompt OAUTH2 initial prompt
* @param[in] oauth2_link_prompt OAUTH2 link prompt
+ * @param[in] sc_auth Whether smartcard authentication is allowed
+ * @param[in] sc_names smartcard names
+ * @param[in] sc_init_prompt smartcard initial prompt
+ * @param[in] sc_pin_prompt smartcard PIN prompt
* @param[out] _result JSON message
*
* @return 0 if the JSON message was formatted properly,
@@ -92,6 +136,9 @@ json_format_auth_selection(TALLOC_CTX *mem_ctx,
bool oath2_auth, const char *uri, const char *code,
const char *oauth2_init_prompt,
const char *oauth2_link_prompt,
+ bool sc_auth, char **sc_names,
+ const char *sc_init_prompt,
+ const char *sc_pin_prompt,
char **_result);
/**
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index fcc7928f6..0abefa324 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -27,6 +27,7 @@
#include "tests/cmocka/common_mock.h"
+#include "src/responder/pam/pamsrv.h"
#include "src/responder/pam/pamsrv_json.h"
#define OAUTH2_URI "short.url.com/tmp\0"
@@ -34,6 +35,25 @@
#define OAUTH2_CODE "1234-5678"
#define OAUTH2_STR OAUTH2_URI OAUTH2_URI_COMP OAUTH2_CODE
+#define SC1_CERT_USER "cert_user1\0"
+#define SC1_TOKEN_NAME "token_name1\0"
+#define SC1_MODULE_NAME "module_name1\0"
+#define SC1_KEY_ID "key_id1\0"
+#define SC1_LABEL "label1\0"
+#define SC1_PROMPT_STR "prompt1\0"
+#define SC1_PAM_CERT_USER "pam_cert_user1"
+#define SC1_STR SC1_CERT_USER SC1_TOKEN_NAME SC1_MODULE_NAME SC1_KEY_ID \
+ SC1_LABEL SC1_PROMPT_STR SC1_PAM_CERT_USER
+#define SC2_CERT_USER "cert_user2\0"
+#define SC2_TOKEN_NAME "token_name2\0"
+#define SC2_MODULE_NAME "module_name2\0"
+#define SC2_KEY_ID "key_id2\0"
+#define SC2_LABEL "label2\0"
+#define SC2_PROMPT_STR "prompt2\0"
+#define SC2_PAM_CERT_USER "pam_cert_user2"
+#define SC2_STR SC2_CERT_USER SC2_TOKEN_NAME SC2_MODULE_NAME SC2_KEY_ID \
+ SC2_LABEL SC2_PROMPT_STR SC2_PAM_CERT_USER
+
#define BASIC_PASSWORD "\"password\": {" \
"\"name\": \"Password\", \"role\": \"password\", " \
"\"prompt\": \"Password\"}"
@@ -43,18 +63,39 @@
"\"linkPrompt\": \"" OAUTH2_LINK_PROMPT "\", " \
"\"uri\": \"short.url.com/tmp\", \"code\": \"1234-5678\", " \
"\"timeout\": 300}"
+#define BASIC_SC "\"smartcard:1\": {" \
+ "\"name\": \"prompt1\", \"role\": \"smartcard\", " \
+ "\"selectable\": true, " \
+ "\"init_instruction\": \"Insert smartcard\", " \
+ "\"pin_prompt\": \"PIN\"}"
+#define MULTIPLE_SC "\"smartcard:1\": {" \
+ "\"name\": \"prompt1\", \"role\": \"smartcard\", " \
+ "\"selectable\": true, " \
+ "\"init_instruction\": \"Insert smartcard\", " \
+ "\"pin_prompt\": \"PIN\"}, " \
+ "\"smartcard:2\": {" \
+ "\"name\": \"prompt2\", \"role\": \"smartcard\", " \
+ "\"selectable\": true, " \
+ "\"init_instruction\": \"Insert smartcard\", " \
+ "\"pin_prompt\": \"PIN\"}"
#define MECHANISMS_PASSWORD "{" BASIC_PASSWORD "}"
#define MECHANISMS_OAUTH2 "{" BASIC_OAUTH2 "}"
-#define PRIORITY_ALL "[\"eidp\", \"password\"]"
+#define MECHANISMS_SC1 "{" BASIC_SC "}"
+#define MECHANISMS_SC2 "{" MULTIPLE_SC "}"
+#define PRIORITY_ALL "[\"eidp\", \"smartcard:1\", \"smartcard:2\", \"password\"]"
#define AUTH_SELECTION_PASSWORD "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_PASSWORD ", " \
"\"priority\": [\"password\"]}}"
#define AUTH_SELECTION_OAUTH2 "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_OAUTH2 ", " \
"\"priority\": [\"eidp\"]}}"
+#define AUTH_SELECTION_SC "{\"authSelection\": {\"mechanisms\": " \
+ MECHANISMS_SC2 ", " \
+ "\"priority\": [\"smartcard:1\", \"smartcard:2\"]}}"
#define AUTH_SELECTION_ALL "{\"authSelection\": {\"mechanisms\": {" \
BASIC_PASSWORD ", " \
- BASIC_OAUTH2 "}, " \
+ BASIC_OAUTH2 ", " \
+ MULTIPLE_SC "}, " \
"\"priority\": " PRIORITY_ALL "}}"
#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}"
@@ -66,6 +107,19 @@
#define AUTH_MECH_ERRONEOUS "{\"authSelection\": {" \
"\"status\": \"Ok\", \"lololo\": {}}}"
+struct cert_auth_info {
+ char *cert_user;
+ char *cert;
+ char *token_name;
+ char *module_name;
+ char *key_id;
+ char *label;
+ char *prompt_str;
+ char *pam_cert_user;
+ char *choice_list_id;
+ struct cert_auth_info *prev;
+ struct cert_auth_info *next;
+};
/***********************
* WRAPPERS
@@ -92,14 +146,86 @@ __wrap_json_array_append_new(json_t *array, json_t *value)
/***********************
* TEST
**********************/
+void test_get_cert_list(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct cert_auth_info *cert_list = NULL;
+ struct cert_auth_info *item = NULL;
+ struct pam_data *pd = NULL;
+ const char *expected_token_name[] = {SC1_TOKEN_NAME, SC2_TOKEN_NAME};
+ const char *expected_module_name[] = {SC1_MODULE_NAME, SC2_MODULE_NAME};
+ const char *expected_key_id[] = {SC1_KEY_ID, SC2_KEY_ID};
+ const char *expected_label[] = {SC1_LABEL, SC2_LABEL};
+ const char *expected_prompt_str[] = {SC1_PROMPT_STR, SC2_PROMPT_STR};
+ int i = 0;
+ int len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+
+ len = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
+ strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
+ strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC1_STR));
+ assert_int_equal(ret, EOK);
+ len = strlen(SC2_CERT_USER)+1+strlen(SC2_TOKEN_NAME)+1+
+ strlen(SC2_MODULE_NAME)+1+strlen(SC2_KEY_ID)+1+strlen(SC2_LABEL)+1+
+ strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC2_STR));
+ assert_int_equal(ret, EOK);
+
+ ret = get_cert_list(test_ctx, pd, &cert_list);
+ assert_int_equal(ret, EOK);
+ DLIST_FOR_EACH(item, cert_list) {
+ assert_string_equal(expected_token_name[i], item->token_name);
+ assert_string_equal(expected_module_name[i], item->module_name);
+ assert_string_equal(expected_key_id[i], item->key_id);
+ assert_string_equal(expected_label[i], item->label);
+ assert_string_equal(expected_prompt_str[i], item->prompt_str);
+ i++;
+ }
+
+ talloc_free(test_ctx);
+}
+
+void test_get_cert_names(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct cert_auth_info *cert_list = NULL;
+ struct cert_auth_info *cai = NULL;
+ char **names = NULL;
+ int ret;
+
+ cai = talloc_zero(test_ctx, struct cert_auth_info);
+ assert_non_null(cai);
+ cai->prompt_str = discard_const(SC1_PROMPT_STR);
+ DLIST_ADD(cert_list, cai);
+ cai = talloc_zero(test_ctx, struct cert_auth_info);
+ assert_non_null(cai);
+ cai->prompt_str = discard_const(SC2_PROMPT_STR);
+ DLIST_ADD(cert_list, cai);
+
+ ret = get_cert_names(test_ctx, cert_list, &names);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(names[0], SC2_PROMPT_STR);
+ assert_string_equal(names[1], SC1_PROMPT_STR);
+
+ talloc_free(test_ctx);
+}
+
void test_json_format_mechanisms_password(void **state)
{
json_t *mechs = NULL;
char *string;
int ret;
- ret = json_format_mechanisms(true, PASSWORD_PROMPT, false, NULL, NULL,
- NULL, NULL, &mechs);
+ ret = json_format_mechanisms(true, PASSWORD_PROMPT,
+ false, NULL, NULL, NULL, NULL,
+ false, NULL, NULL, NULL,
+ &mechs);
assert_int_equal(ret, EOK);
string = json_dumps(mechs, 0);
@@ -116,6 +242,7 @@ void test_json_format_mechanisms_oauth2(void **state)
ret = json_format_mechanisms(false, NULL, true, OAUTH2_URI, OAUTH2_CODE,
OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ false, NULL, NULL, NULL,
&mechs);
assert_int_equal(ret, EOK);
@@ -125,21 +252,99 @@ void test_json_format_mechanisms_oauth2(void **state)
free(string);
}
+void test_json_format_mechanisms_sc1(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ json_t *mechs = NULL;
+ char **sc_names = NULL;
+ char *string;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ sc_names = talloc_array(test_ctx, char *, 2);
+ assert_non_null(sc_names);
+ sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
+ assert_non_null(sc_names[0]);
+ sc_names[1] = NULL;
+
+ ret = json_format_mechanisms(false, NULL,
+ false, NULL, NULL, NULL, NULL,
+ true, sc_names, SC_INIT_PROMPT, SC_PIN_PROMPT,
+ &mechs);
+ assert_int_equal(ret, EOK);
+
+ string = json_dumps(mechs, 0);
+ assert_string_equal(string, MECHANISMS_SC1);
+
+ json_decref(mechs);
+ free(string);
+ talloc_free(test_ctx);
+}
+
+void test_json_format_mechanisms_sc2(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ json_t *mechs = NULL;
+ char **sc_names = NULL;
+ char *string;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ sc_names = talloc_array(test_ctx, char *, 3);
+ assert_non_null(sc_names);
+ sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
+ assert_non_null(sc_names[0]);
+ sc_names[1] = talloc_strdup(sc_names, SC2_PROMPT_STR);
+ assert_non_null(sc_names[1]);
+ sc_names[2] = NULL;
+
+ ret = json_format_mechanisms(false, NULL,
+ false, NULL, NULL, NULL, NULL,
+ true, sc_names, SC_INIT_PROMPT, SC_PIN_PROMPT,
+ &mechs);
+ assert_int_equal(ret, EOK);
+
+ string = json_dumps(mechs, 0);
+ assert_string_equal(string, MECHANISMS_SC2);
+
+ json_decref(mechs);
+ free(string);
+ talloc_free(test_ctx);
+}
+
void test_json_format_priority_all(void **state)
{
+ TALLOC_CTX *test_ctx = NULL;
json_t *priority = NULL;
+ char **sc_names = NULL;
char *string;
int ret;
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ sc_names = talloc_array(test_ctx, char *, 3);
+ assert_non_null(sc_names);
+ sc_names[0] = talloc_strdup(sc_names, SC1_LABEL);
+ assert_non_null(sc_names[0]);
+ sc_names[1] = talloc_strdup(sc_names, SC2_LABEL);
+ assert_non_null(sc_names[1]);
+ sc_names[2] = NULL;
+
+ will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
- ret = json_format_priority(true, true, &priority);
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_priority(true, true, true, sc_names, &priority);
assert_int_equal(ret, EOK);
string = json_dumps(priority, 0);
assert_string_equal(string, PRIORITY_ALL);
+
json_decref(priority);
free(string);
+ talloc_free(test_ctx);
}
void test_json_format_auth_selection_password(void **state)
@@ -153,9 +358,13 @@ void test_json_format_auth_selection_password(void **state)
will_return(__wrap_json_array_append_new, false);
ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
- false, NULL, NULL, NULL, NULL, &json_msg);
+ false, NULL, NULL, NULL, NULL,
+ false, NULL, NULL, NULL,
+ &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_PASSWORD);
+
+ talloc_free(test_ctx);
}
void test_json_format_auth_selection_oauth2(void **state)
@@ -171,46 +380,105 @@ void test_json_format_auth_selection_oauth2(void **state)
ret = json_format_auth_selection(test_ctx, false, NULL,
true, OAUTH2_URI, OAUTH2_CODE,
OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ false, NULL, NULL, NULL,
&json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_OAUTH2);
+
+ talloc_free(test_ctx);
+}
+
+void test_json_format_auth_selection_sc(void **state)
+{
+ TALLOC_CTX *test_ctx;
+ char **sc_names = NULL;
+ char *json_msg = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ sc_names = talloc_array(test_ctx, char *, 3);
+ assert_non_null(sc_names);
+ sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
+ assert_non_null(sc_names[0]);
+ sc_names[1] = talloc_strdup(sc_names, SC2_PROMPT_STR);
+ assert_non_null(sc_names[1]);
+ sc_names[2] = NULL;
+
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_auth_selection(test_ctx, false, NULL,
+ false, NULL, NULL, NULL, NULL,
+ true, sc_names,
+ SC_INIT_PROMPT, SC_PIN_PROMPT,
+ &json_msg);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(json_msg, AUTH_SELECTION_SC);
+
+ talloc_free(test_ctx);
}
void test_json_format_auth_selection_all(void **state)
{
TALLOC_CTX *test_ctx;
+ char **sc_names = NULL;
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
+ sc_names = talloc_array(test_ctx, char *, 3);
+ assert_non_null(sc_names);
+ sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
+ assert_non_null(sc_names[0]);
+ sc_names[1] = talloc_strdup(sc_names, SC2_PROMPT_STR);
+ assert_non_null(sc_names[1]);
+ sc_names[2] = NULL;
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
true, OAUTH2_URI, OAUTH2_CODE,
OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ true, sc_names,
+ SC_INIT_PROMPT, SC_PIN_PROMPT,
&json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_ALL);
+
+ talloc_free(test_ctx);
}
void test_json_format_auth_selection_failure(void **state)
{
TALLOC_CTX *test_ctx;
+ char **sc_names = NULL;
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
+ sc_names = talloc_array(test_ctx, char *, 3);
+ assert_non_null(sc_names);
+ sc_names[0] = talloc_strdup(sc_names, SC1_LABEL);
+ assert_non_null(sc_names[0]);
+ sc_names[1] = talloc_strdup(sc_names, SC2_LABEL);
+ assert_non_null(sc_names[1]);
+ sc_names[2] = NULL;
will_return(__wrap_json_array_append_new, true);
ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
true, OAUTH2_URI, OAUTH2_CODE,
OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
+ true, sc_names,
+ SC_INIT_PROMPT, SC_PIN_PROMPT,
&json_msg);
assert_int_equal(ret, ENOMEM);
assert_null(json_msg);
+
+ talloc_free(test_ctx);
}
void test_generate_json_message_integration(void **state)
@@ -218,6 +486,7 @@ void test_generate_json_message_integration(void **state)
TALLOC_CTX *test_ctx;
struct pam_data *pd = NULL;
struct prompt_config **pc_list = NULL;
+ int len;
int ret;
test_ctx = talloc_new(NULL);
@@ -225,12 +494,23 @@ void test_generate_json_message_integration(void **state)
pd = talloc_zero(test_ctx, struct pam_data);
assert_non_null(pd);
- pd->resp_list = talloc(pd, struct response_data);
- pd->resp_list->type = SSS_PAM_OAUTH2_INFO;
- pd->resp_list->len = strlen(OAUTH2_URI)+1+strlen(OAUTH2_URI_COMP)+1+strlen(OAUTH2_CODE)+1;
- pd->resp_list->data = discard_const(OAUTH2_STR);
- pd->resp_list->next = NULL;
+ len = strlen(OAUTH2_URI)+1+strlen(OAUTH2_URI_COMP)+1+strlen(OAUTH2_CODE)+1;
+ ret = pam_add_response(pd, SSS_PAM_OAUTH2_INFO, len,
+ discard_const(OAUTH2_STR));
+ assert_int_equal(ret, EOK);
+ len = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
+ strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
+ strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC1_STR));
+ assert_int_equal(ret, EOK);
+ len = strlen(SC2_CERT_USER)+1+strlen(SC2_TOKEN_NAME)+1+
+ strlen(SC2_MODULE_NAME)+1+strlen(SC2_KEY_ID)+1+strlen(SC2_LABEL)+1+
+ strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC2_STR));
+ assert_int_equal(ret, EOK);
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
ret = generate_json_auth_message(NULL, pc_list, pd);
@@ -395,11 +675,16 @@ int main(int argc, const char *argv[])
};
const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_get_cert_list),
+ cmocka_unit_test(test_get_cert_names),
cmocka_unit_test(test_json_format_mechanisms_password),
cmocka_unit_test(test_json_format_mechanisms_oauth2),
+ cmocka_unit_test(test_json_format_mechanisms_sc1),
+ cmocka_unit_test(test_json_format_mechanisms_sc2),
cmocka_unit_test(test_json_format_priority_all),
cmocka_unit_test(test_json_format_auth_selection_password),
cmocka_unit_test(test_json_format_auth_selection_oauth2),
+ cmocka_unit_test(test_json_format_auth_selection_sc),
cmocka_unit_test(test_json_format_auth_selection_all),
cmocka_unit_test(test_json_format_auth_selection_failure),
cmocka_unit_test(test_generate_json_message_integration),
--
2.54.0
From fc69ec8928a44d6dbb04e376d31109608dc0f843 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 10 Apr 2024 17:35:22 +0200
Subject: [PATCH 17/36] Responder: parse reply for smartcard
Parse GUI reply for smartcard and set the appropriate data in
`sss_auth_token` structure.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 86 +++++++++++++++++++++
src/responder/pam/pamsrv_json.h | 9 +++
src/tests/cmocka/test_pamsrv_json.c | 114 ++++++++++++++++++++++++++++
3 files changed, 209 insertions(+)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index 7c778c42a..eba7707e5 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -146,6 +146,27 @@ done:
return ret;
}
+static errno_t
+find_certificate_in_list(struct cert_auth_info *cert_list, int cert_num,
+ struct cert_auth_info **_cai)
+{
+ struct cert_auth_info *cai = NULL;
+ struct cert_auth_info *cai_next = NULL;
+ int i = 0;
+
+ DLIST_FOR_EACH_SAFE(cai, cai_next, cert_list) {
+ if (i == cert_num) {
+ goto done;
+ }
+ i++;
+ }
+
+done:
+ *_cai = cai;
+
+ return EOK;
+}
+
static errno_t
obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
struct prompt_config **pc_list, const char **_password_prompt,
@@ -869,10 +890,33 @@ done:
return ret;
}
+errno_t
+json_unpack_smartcard(json_t *jroot, const char **_pin)
+{
+ char *pin = NULL;
+ int ret = EOK;
+
+ ret = json_unpack(jroot, "{s:s}",
+ "pin", &pin);
+ if (ret != 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for pin failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_pin = pin;
+ ret = EOK;
+
+done:
+ return ret;
+}
+
errno_t
json_unpack_auth_reply(struct pam_data *pd)
{
TALLOC_CTX *tmp_ctx = NULL;
+ struct cert_auth_info *cert_list = NULL;
+ struct cert_auth_info *cai = NULL;
json_t *jroot = NULL;
json_t *jauth_selection = NULL;
json_t *jobj = NULL;
@@ -881,6 +925,9 @@ json_unpack_auth_reply(struct pam_data *pd)
const char *status = NULL;
char *password = NULL;
char *oauth2_code = NULL;
+ const char *pin = NULL;
+ char *index = NULL;
+ int cert_num;
int ret = EOK;
DEBUG(SSSDBG_TRACE_FUNC, "Received JSON message: %s.\n",
@@ -947,6 +994,45 @@ json_unpack_auth_reply(struct pam_data *pd)
}
goto done;
}
+
+ if (strncmp(key, "smartcard", strlen("smartcard")) == 0) {
+ ret = json_unpack_smartcard(jobj, &pin);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = get_cert_list(tmp_ctx, pd, &cert_list);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ index = talloc_strdup(tmp_ctx, key + 10);
+ if (index == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "talloc_strdup failed: %d.\n", ret);
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cert_num = atoi(index);
+ cert_num--;
+ ret = find_certificate_in_list(cert_list, cert_num, &cai);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sss_authtok_set_sc(pd->authtok, SSS_AUTHTOK_TYPE_SC_PIN,
+ pin, strlen(pin),
+ cai->token_name, strlen(cai->token_name),
+ cai->module_name, strlen(cai->module_name),
+ cai->key_id, strlen(cai->key_id),
+ cai->label, strlen(cai->label));
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_authtok_set_sc failed: %d.\n", ret);
+ }
+ goto done;
+ }
}
DEBUG(SSSDBG_CRIT_FAILURE, "Unknown authentication mechanism\n");
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 380bd65f7..2441510d0 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -183,6 +183,15 @@ errno_t
json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg,
char **_oauth2_code);
+/**
+ * @brief Unpack smartcard specific data reply
+ *
+ * @param[in] jroot jansson structure containing the smartcard specific data
+ * @param[out] _pin user PIN
+ */
+errno_t
+json_unpack_smartcard(json_t *jroot, const char **_pin);
+
/**
* @brief Unpack GDM reply and check its value
*
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 0abefa324..2d4d6e4e1 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -99,11 +99,15 @@
"\"priority\": " PRIORITY_ALL "}}"
#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}"
+#define SMARTCARD_CONTENT "{\"pin\": \"ThePIN\"}"
#define AUTH_MECH_REPLY_PASSWORD "{\"authSelection\": {" \
"\"status\": \"Ok\", \"password\": " \
PASSWORD_CONTENT "}}"
#define AUTH_MECH_REPLY_OAUTH2 "{\"authSelection\": {" \
"\"status\": \"Ok\", \"eidp\": {}}}"
+#define AUTH_MECH_REPLY_SMARTCARD "{\"auth-selection\": {" \
+ "\"status\": \"Ok\", \"smartcard:1\": " \
+ SMARTCARD_CONTENT "}}"
#define AUTH_MECH_ERRONEOUS "{\"authSelection\": {" \
"\"status\": \"Ok\", \"lololo\": {}}}"
@@ -537,6 +541,22 @@ void test_json_unpack_password_ok(void **state)
json_decref(jroot);
}
+void test_json_unpack_sc_ok(void **state)
+{
+ json_t *jroot = NULL;
+ const char *pin = NULL;
+ json_error_t jret;
+ int ret;
+
+ jroot = json_loads(SMARTCARD_CONTENT, 0, &jret);
+ assert_non_null(jroot);
+
+ ret = json_unpack_smartcard(jroot, &pin);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(pin, "ThePIN");
+ json_decref(jroot);
+}
+
void test_json_unpack_auth_reply_password(void **state)
{
TALLOC_CTX *test_ctx = NULL;
@@ -588,6 +608,97 @@ void test_json_unpack_auth_reply_oauth2(void **state)
talloc_free(test_ctx);
}
+void test_json_unpack_auth_reply_sc1(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ const char *pin = NULL;
+ const char *token_name = NULL;
+ const char *module_name = NULL;
+ const char *key_id = NULL;
+ const char *label = NULL;
+ size_t pin_len, token_name_len, module_name_len, key_id_len, label_len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->authtok = sss_authtok_new(pd);
+ assert_non_null(pd->authtok);
+ pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_SMARTCARD);
+ len = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
+ strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
+ strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC1_STR));
+ assert_int_equal(ret, EOK);
+
+ ret = json_unpack_auth_reply(pd);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(sss_authtok_get_type(pd->authtok), SSS_AUTHTOK_TYPE_SC_PIN);
+ ret = sss_authtok_get_sc(pd->authtok, &pin, &pin_len,
+ &token_name, &token_name_len,
+ &module_name, &module_name_len,
+ &key_id, &key_id_len,
+ &label, &label_len);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(pin, "ThePIN");
+ assert_string_equal(token_name, SC1_TOKEN_NAME);
+ assert_string_equal(module_name, SC1_MODULE_NAME);
+ assert_string_equal(key_id, SC1_KEY_ID);
+ assert_string_equal(label, SC1_LABEL);
+
+ talloc_free(test_ctx);
+}
+
+void test_json_unpack_auth_reply_sc2(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ const char *pin = NULL;
+ const char *token_name = NULL;
+ const char *module_name = NULL;
+ const char *key_id = NULL;
+ const char *label = NULL;
+ size_t pin_len, token_name_len, module_name_len, key_id_len, label_len;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->authtok = sss_authtok_new(pd);
+ assert_non_null(pd->authtok);
+ pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_SMARTCARD);
+ len = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
+ strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
+ strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC1_STR));
+ assert_int_equal(ret, EOK);
+ len = strlen(SC2_CERT_USER)+1+strlen(SC2_TOKEN_NAME)+1+
+ strlen(SC2_MODULE_NAME)+1+strlen(SC2_KEY_ID)+1+strlen(SC2_LABEL)+1+
+ strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
+ ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC2_STR));
+ assert_int_equal(ret, EOK);
+
+ ret = json_unpack_auth_reply(pd);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(sss_authtok_get_type(pd->authtok), SSS_AUTHTOK_TYPE_SC_PIN);
+ ret = sss_authtok_get_sc(pd->authtok, &pin, &pin_len,
+ &token_name, &token_name_len,
+ &module_name, &module_name_len,
+ &key_id, &key_id_len,
+ &label, &label_len);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(pin, "ThePIN");
+ assert_string_equal(token_name, SC1_TOKEN_NAME);
+ assert_string_equal(module_name, SC1_MODULE_NAME);
+ assert_string_equal(key_id, SC1_KEY_ID);
+ assert_string_equal(label, SC1_LABEL);
+
+ talloc_free(test_ctx);
+}
+
void test_json_unpack_auth_reply_failure(void **state)
{
TALLOC_CTX *test_ctx = NULL;
@@ -689,8 +800,11 @@ int main(int argc, const char *argv[])
cmocka_unit_test(test_json_format_auth_selection_failure),
cmocka_unit_test(test_generate_json_message_integration),
cmocka_unit_test(test_json_unpack_password_ok),
+ cmocka_unit_test(test_json_unpack_sc_ok),
cmocka_unit_test(test_json_unpack_auth_reply_password),
cmocka_unit_test(test_json_unpack_auth_reply_oauth2),
+ cmocka_unit_test(test_json_unpack_auth_reply_sc1),
+ cmocka_unit_test(test_json_unpack_auth_reply_sc2),
cmocka_unit_test(test_json_unpack_auth_reply_failure),
cmocka_unit_test(test_json_unpack_oauth2_code),
cmocka_unit_test(test_is_pam_json_enabled_service_in_list),
--
2.54.0
From a93959dfd0ee637cf7f4ad0a5879a58bea00bb6b Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Fri, 6 Sep 2024 11:23:14 +0200
Subject: [PATCH 18/36] Responder: refactor JSON functions to reduce args
Several of the functions in `pamsrv_json` had lots of arguments and I'm
about to add more for the passkey authentication mechanism. Reduce these
arguments by creating a structure that will contain all these data.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 208 ++++++++++---------
src/responder/pam/pamsrv_json.h | 85 ++++----
src/tests/cmocka/test_pamsrv_json.c | 301 ++++++++++++++++------------
3 files changed, 328 insertions(+), 266 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index eba7707e5..286a3fd50 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -52,8 +52,8 @@ struct cert_auth_info {
static errno_t
-obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, char **_uri,
- char **_code)
+obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd,
+ struct auth_data *_auth_data)
{
TALLOC_CTX *tmp_ctx = NULL;
uint8_t *oauth2 = NULL;
@@ -136,8 +136,8 @@ obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, char **_uri,
goto done;
}
- *_uri = talloc_steal(mem_ctx, uri);
- *_code = talloc_steal(mem_ctx, code);
+ _auth_data->oauth2->uri = talloc_steal(mem_ctx, uri);
+ _auth_data->oauth2->code = talloc_steal(mem_ctx, code);
ret = EOK;
done:
@@ -169,9 +169,7 @@ done:
static errno_t
obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
- struct prompt_config **pc_list, const char **_password_prompt,
- const char **_oauth2_init_prompt, const char **_oauth2_link_prompt,
- const char **_sc_init_prompt, const char **_sc_pin_prompt)
+ struct prompt_config **pc_list, struct auth_data *_auth_data)
{
TALLOC_CTX *tmp_ctx = NULL;
char *password_prompt = NULL;
@@ -216,11 +214,11 @@ obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
goto done;
}
- *_password_prompt = talloc_steal(mem_ctx, password_prompt);
- *_oauth2_init_prompt = talloc_steal(mem_ctx, oauth2_init_prompt);
- *_oauth2_link_prompt = talloc_steal(mem_ctx, oauth2_link_prompt);
- *_sc_init_prompt = talloc_steal(mem_ctx, sc_init_prompt);
- *_sc_pin_prompt = talloc_steal(mem_ctx, sc_pin_prompt);
+ _auth_data->pswd->prompt = talloc_steal(mem_ctx, password_prompt);
+ _auth_data->oauth2->init_prompt = talloc_steal(mem_ctx, oauth2_init_prompt);
+ _auth_data->oauth2->link_prompt = talloc_steal(mem_ctx, oauth2_link_prompt);
+ _auth_data->sc->init_prompt = talloc_steal(mem_ctx, sc_init_prompt);
+ _auth_data->sc->pin_prompt = talloc_steal(mem_ctx, sc_pin_prompt);
ret = EOK;
done:
@@ -428,9 +426,81 @@ done:
return ret;
}
+static errno_t
+init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
+ struct prompt_config **pc_list, struct pam_data *pd,
+ struct auth_data **_auth_data)
+{
+ struct cert_auth_info *cert_list = NULL;
+ errno_t ret = EOK;
+
+ *_auth_data = talloc_zero(mem_ctx, struct auth_data);
+ if (*_auth_data == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ (*_auth_data)->pswd = talloc_zero(mem_ctx, struct password_data);
+ if ((*_auth_data)->pswd == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ (*_auth_data)->pswd->enabled = true;
+
+ (*_auth_data)->oauth2 = talloc_zero(mem_ctx, struct oauth2_data);
+ if ((*_auth_data)->oauth2 == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ (*_auth_data)->oauth2->enabled = true;
+
+ (*_auth_data)->sc = talloc_zero(mem_ctx, struct sc_data);
+ if ((*_auth_data)->sc == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ (*_auth_data)->sc->enabled = true;
+
+ ret = obtain_prompts(cdb, mem_ctx, pc_list, *_auth_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain the prompts.\n");
+ goto done;
+ }
+
+ ret = obtain_oauth2_data(mem_ctx, pd, *_auth_data);
+ if (ret == ENOENT) {
+ (*_auth_data)->oauth2->enabled = false;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain OAUTH2 data.\n");
+ goto done;
+ }
+
+ ret = get_cert_list(mem_ctx, pd, &cert_list);
+ if (ret == ENOENT) {
+ (*_auth_data)->sc->enabled = false;
+ } else if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failure to obtain smartcard certificate list.\n");
+ goto done;
+ }
+
+ ret = get_cert_names(mem_ctx, cert_list, *_auth_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain smartcard labels.\n");
+ goto done;
+ }
+
+done:
+ return ret;
+}
+
errno_t
get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
- char ***_names)
+ struct auth_data *_auth_data)
{
TALLOC_CTX *tmp_ctx = NULL;
struct cert_auth_info *item = NULL;
@@ -468,7 +538,7 @@ get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
}
names[i] = NULL;
- *_names = talloc_steal(mem_ctx, names);
+ _auth_data->sc->names = talloc_steal(mem_ctx, names);
ret = EOK;
done:
@@ -478,14 +548,7 @@ done:
}
errno_t
-json_format_mechanisms(bool password_auth, const char *password_prompt,
- bool oauth2_auth, const char *uri, const char *code,
- const char *oauth2_init_prompt,
- const char *oauth2_link_prompt,
- bool sc_auth, char **sc_names,
- const char *sc_init_prompt,
- const char *sc_pin_prompt,
- json_t **_list_mech)
+json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech)
{
json_t *root = NULL;
json_t *json_pass = NULL;
@@ -501,11 +564,11 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
goto done;
}
- if (password_auth) {
+ if (auth_data->pswd->enabled) {
json_pass = json_pack("{s:s,s:s,s:s}",
"name", "Password",
"role", "password",
- "prompt", password_prompt);
+ "prompt", auth_data->pswd->prompt);
if (json_pass == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
ret = ENOMEM;
@@ -521,14 +584,14 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
}
- if (oauth2_auth) {
+ if (auth_data->oauth2->enabled) {
json_oauth2 = json_pack("{s:s,s:s,s:s,s:s,s:s,s:s,s:i}",
"name", "Web Login",
"role", "eidp",
- "initPrompt", oauth2_init_prompt,
- "linkPrompt", oauth2_link_prompt,
- "uri", uri,
- "code", code,
+ "initPrompt", auth_data->oauth2->init_prompt,
+ "linkPrompt", auth_data->oauth2->link_prompt,
+ "uri", auth_data->oauth2->uri,
+ "code", auth_data->oauth2->code,
"timeout", 300);
if (json_oauth2 == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
@@ -545,14 +608,14 @@ json_format_mechanisms(bool password_auth, const char *password_prompt,
}
}
- if (sc_auth) {
- for (int i = 0; sc_names[i] != NULL; i++) {
+ if (auth_data->sc->enabled) {
+ for (int i = 0; auth_data->sc->names[i] != NULL; i++) {
json_sc = json_pack("{s:s,s:s,s:b,s:s,s:s}",
- "name", sc_names[i],
+ "name", auth_data->sc->names[i],
"role", "smartcard",
"selectable", true,
- "init_instruction", sc_init_prompt,
- "pin_prompt", sc_pin_prompt);
+ "init_instruction", auth_data->sc->init_prompt,
+ "pin_prompt", auth_data->sc->pin_prompt);
if (json_sc == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
ret = ENOMEM;
@@ -589,8 +652,7 @@ done:
}
errno_t
-json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
- char **sc_names, json_t **_priority)
+json_format_priority(struct auth_data *auth_data, json_t **_priority)
{
json_t *root = NULL;
json_t *json_priority = NULL;
@@ -604,7 +666,7 @@ json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
goto done;
}
- if (oauth2_auth) {
+ if (auth_data->oauth2->enabled) {
json_priority = json_string("eidp");
if (json_priority == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
@@ -620,8 +682,8 @@ json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
}
}
- if (sc_auth) {
- for (int i = 0; sc_names[i] != NULL; i++) {
+ if (auth_data->sc->enabled) {
+ for (int i = 0; auth_data->sc->names[i] != NULL; i++) {
ret = asprintf(&key, "smartcard:%d", i+1);
if (ret == -1) {
DEBUG(SSSDBG_OP_FAILURE, "asprintf failed.\n");
@@ -645,7 +707,7 @@ json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
}
}
- if (password_auth) {
+ if (auth_data->pswd->enabled) {
json_priority = json_string("password");
if (json_priority == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
@@ -673,14 +735,7 @@ done:
}
errno_t
-json_format_auth_selection(TALLOC_CTX *mem_ctx,
- bool password_auth, const char *password_prompt,
- bool oauth2_auth, const char *uri, const char *code,
- const char *oauth2_init_prompt,
- const char *oauth2_link_prompt,
- bool sc_auth, char **sc_names,
- const char *sc_init_prompt,
- const char *sc_pin_prompt,
+json_format_auth_selection(TALLOC_CTX *mem_ctx, struct auth_data *auth_data,
char **_result)
{
json_t *root = NULL;
@@ -689,18 +744,12 @@ json_format_auth_selection(TALLOC_CTX *mem_ctx,
char *string = NULL;
int ret;
- ret = json_format_mechanisms(password_auth, password_prompt,
- oauth2_auth, uri, code,
- oauth2_init_prompt, oauth2_link_prompt,
- sc_auth, sc_names,
- sc_init_prompt, sc_pin_prompt,
- &json_mech);
+ ret = json_format_mechanisms(auth_data, &json_mech);
if (ret != EOK) {
goto done;
}
- ret = json_format_priority(password_auth, oauth2_auth, sc_auth, sc_names,
- &json_priority);
+ ret = json_format_priority(auth_data, &json_priority);
if (ret != EOK) {
json_decref(json_mech);
goto done;
@@ -741,18 +790,8 @@ generate_json_auth_message(struct confdb_ctx *cdb,
struct pam_data *_pd)
{
TALLOC_CTX *tmp_ctx = NULL;
- struct cert_auth_info *cert_list = NULL;
- const char *password_prompt = NULL;
- const char *oauth2_init_prompt = NULL;
- const char *oauth2_link_prompt = NULL;
- const char *sc_init_prompt = NULL;
- const char *sc_pin_prompt = NULL;
- char *oauth2_uri = NULL;
- char *oauth2_code = NULL;
- char **sc_names = NULL;
+ struct auth_data *auth_data = NULL;
char *result = NULL;
- bool oauth2_auth = true;
- bool sc_auth = true;
int ret;
tmp_ctx = talloc_new(NULL);
@@ -760,40 +799,17 @@ generate_json_auth_message(struct confdb_ctx *cdb,
return ENOMEM;
}
- ret = obtain_prompts(cdb, tmp_ctx, pc_list, &password_prompt,
- &oauth2_init_prompt, &oauth2_link_prompt,
- &sc_init_prompt, &sc_pin_prompt);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain the prompts.\n");
- goto done;
- }
-
- ret = obtain_oauth2_data(tmp_ctx, _pd, &oauth2_uri, &oauth2_code);
- if (ret == ENOENT) {
- oauth2_auth = false;
- } else if (ret != EOK) {
- goto done;
- }
-
- ret = get_cert_list(tmp_ctx, _pd, &cert_list);
- if (ret == ENOENT) {
- sc_auth = false;
- } else if (ret != EOK) {
- goto done;
- }
-
- ret = get_cert_names(tmp_ctx, cert_list, &sc_names);
+ ret = init_auth_data(tmp_ctx, cdb, pc_list, _pd, &auth_data);
if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to initialize authentication data.\n");
goto done;
}
- ret = json_format_auth_selection(tmp_ctx, true, password_prompt,
- oauth2_auth, oauth2_uri, oauth2_code,
- oauth2_init_prompt, oauth2_link_prompt,
- sc_auth, sc_names,
- sc_init_prompt, sc_pin_prompt,
- &result);
+ ret = json_format_auth_selection(tmp_ctx, auth_data, &result);
if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to format JSON message.\n");
goto done;
}
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 2441510d0..2c8949261 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -37,6 +37,33 @@
#define SC_PIN_PROMPT "PIN"
+struct auth_data {
+ struct password_data *pswd;
+ struct oauth2_data *oauth2;
+ struct sc_data *sc;
+};
+
+struct password_data {
+ bool enabled;
+ char *prompt;
+};
+
+struct oauth2_data {
+ bool enabled;
+ char *uri;
+ char *code;
+ char *init_prompt;
+ char *link_prompt;
+};
+
+struct sc_data {
+ bool enabled;
+ char **names;
+ char *init_prompt;
+ char *pin_prompt;
+};
+
+
/**
* @brief Extract smartcard certificate list from pam_data structure
*
@@ -56,89 +83,55 @@ get_cert_list(TALLOC_CTX *mem_ctx, struct pam_data *pd,
*
* @param[in] mem_ctx Memory context
* @param[in] cert_list Certificate list
- * @param[out] _names Certificate names list
+ * @param[out] _auth_data Structure containing the data from all available
+ * authentication mechanisms
*
* @return 0 if the data was extracted successfully,
* error code otherwise.
*/
errno_t
get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
- char ***_names);
+ struct auth_data *_auth_data);
/**
* @brief Format authentication mechanisms to JSON
*
- * @param[in] password_auth Whether password authentication is allowed
- * @param[in] password_prompt Password prompt
- * @param[in] oath2_auth Whether OAUTH2 authentication is allowed
- * @param[in] uri OAUTH2 uri
- * @param[in] code OAUTH2 code
- * @param[in] oauth2_init_prompt OAUTH2 initial prompt
- * @param[in] oauth2_link_prompt OAUTH2 link prompt
- * @param[in] sc_auth Whether smartcard authentication is allowed
- * @param[in] sc_names smartcard names
- * @param[in] sc_init_prompt smartcard initial prompt
- * @param[in] sc_pin_prompt smartcard PIN prompt
+ * @param[in] auth_data Structure containing the data from all available
+ * authentication mechanisms
* @param[out] _list_mech authentication mechanisms JSON object
*
* @return 0 if the authentication mechanisms were formatted properly,
* error code otherwise.
*/
errno_t
-json_format_mechanisms(bool password_auth, const char *password_prompt,
- bool oauth2_auth, const char *uri, const char *code,
- const char *oauth2_init_prompt,
- const char *oauth2_link_prompt,
- bool sc_auth, char **sc_names,
- const char *sc_init_prompt,
- const char *sc_pin_prompt,
- json_t **_list_mech);
+json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech);
/**
* @brief Format priority to JSON
*
- * @param[in] password_auth Whether password authentication is allowed
- * @param[in] oath2_auth Whether OAUTH2 authentication is allowed
- * @param[in] sc_auth Whether smartcard authentication is allowed
- * @param[in] sc_names Smartcard certificate names
+ * @param[in] auth_data Structure containing the data from all available
+ * authentication mechanisms
* @param[out] _priority priority JSON object
*
* @return 0 if the priority was formatted properly,
* error code otherwise.
*/
errno_t
-json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth,
- char **sc_names, json_t **_priority);
+json_format_priority(struct auth_data *auth_data, json_t **_priority);
/**
* @brief Format data to JSON
*
* @param[in] mem_ctx Memory context
- * @param[in] password_auth Whether password authentication is allowed
- * @param[in] password_prompt Password prompt
- * @param[in] oath2_auth Whether OAUTH2 authentication is allowed
- * @param[in] uri OAUTH2 uri
- * @param[in] code OAUTH2 code
- * @param[in] oauth2_init_prompt OAUTH2 initial prompt
- * @param[in] oauth2_link_prompt OAUTH2 link prompt
- * @param[in] sc_auth Whether smartcard authentication is allowed
- * @param[in] sc_names smartcard names
- * @param[in] sc_init_prompt smartcard initial prompt
- * @param[in] sc_pin_prompt smartcard PIN prompt
+ * @param[in] auth_data Structure containing the data from all available
+ * authentication mechanisms
* @param[out] _result JSON message
*
* @return 0 if the JSON message was formatted properly,
* error code otherwise.
*/
errno_t
-json_format_auth_selection(TALLOC_CTX *mem_ctx,
- bool password_auth, const char *password_prompt,
- bool oath2_auth, const char *uri, const char *code,
- const char *oauth2_init_prompt,
- const char *oauth2_link_prompt,
- bool sc_auth, char **sc_names,
- const char *sc_init_prompt,
- const char *sc_pin_prompt,
+json_format_auth_selection(TALLOC_CTX *mem_ctx, struct auth_data *auth_data,
char **_result);
/**
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 2d4d6e4e1..6dc85e384 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -125,6 +125,47 @@ struct cert_auth_info {
struct cert_auth_info *next;
};
+/***********************
+ * SETUP AND TEARDOWN
+ **********************/
+static int setup(void **state)
+{
+ struct auth_data *auth_data = NULL;
+
+ assert_true(leak_check_setup());
+
+ auth_data = talloc_zero(global_talloc_context, struct auth_data);
+ assert_non_null(auth_data);
+ auth_data->pswd = talloc_zero(auth_data, struct password_data);
+ assert_non_null(auth_data->pswd);
+ auth_data->oauth2 = talloc_zero(auth_data, struct oauth2_data);
+ assert_non_null(auth_data->oauth2);
+ auth_data->sc = talloc_zero(auth_data, struct sc_data);
+ assert_non_null(auth_data->sc);
+ auth_data->sc->names = talloc_array(auth_data->sc, char *, 3);
+ assert_non_null(auth_data->sc->names);
+
+ auth_data->pswd->enabled = false;
+ auth_data->oauth2->enabled = false;
+ auth_data->sc->enabled = false;
+
+ check_leaks_push(auth_data);
+ *state = (void *)auth_data;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
+
+ assert_non_null(auth_data);
+ assert_true(check_leaks_pop(auth_data));
+ talloc_free(auth_data);
+ assert_true(leak_check_teardown());
+
+ return 0;
+}
+
/***********************
* WRAPPERS
**********************/
@@ -198,11 +239,13 @@ void test_get_cert_list(void **state)
void test_get_cert_names(void **state)
{
TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
struct cert_auth_info *cert_list = NULL;
struct cert_auth_info *cai = NULL;
- char **names = NULL;
int ret;
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
cai = talloc_zero(test_ctx, struct cert_auth_info);
assert_non_null(cai);
cai->prompt_str = discard_const(SC1_PROMPT_STR);
@@ -212,70 +255,72 @@ void test_get_cert_names(void **state)
cai->prompt_str = discard_const(SC2_PROMPT_STR);
DLIST_ADD(cert_list, cai);
- ret = get_cert_names(test_ctx, cert_list, &names);
+ ret = get_cert_names(test_ctx, cert_list, auth_data);
assert_int_equal(ret, EOK);
- assert_string_equal(names[0], SC2_PROMPT_STR);
- assert_string_equal(names[1], SC1_PROMPT_STR);
+ assert_string_equal(auth_data->sc->names[0], SC2_PROMPT_STR);
+ assert_string_equal(auth_data->sc->names[1], SC1_PROMPT_STR);
talloc_free(test_ctx);
}
void test_json_format_mechanisms_password(void **state)
{
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
json_t *mechs = NULL;
char *string;
int ret;
- ret = json_format_mechanisms(true, PASSWORD_PROMPT,
- false, NULL, NULL, NULL, NULL,
- false, NULL, NULL, NULL,
- &mechs);
+ auth_data->pswd->enabled = true;
+ auth_data->pswd->prompt = discard_const(PASSWORD_PROMPT);
+
+ ret = json_format_mechanisms(auth_data, &mechs);
assert_int_equal(ret, EOK);
string = json_dumps(mechs, 0);
assert_string_equal(string, MECHANISMS_PASSWORD);
+
json_decref(mechs);
free(string);
}
void test_json_format_mechanisms_oauth2(void **state)
{
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
json_t *mechs = NULL;
char *string;
int ret;
- ret = json_format_mechanisms(false, NULL, true, OAUTH2_URI, OAUTH2_CODE,
- OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
- false, NULL, NULL, NULL,
- &mechs);
+ auth_data->oauth2->enabled = true;
+ auth_data->oauth2->uri = discard_const(OAUTH2_URI);
+ auth_data->oauth2->code = discard_const(OAUTH2_CODE);
+ auth_data->oauth2->init_prompt = discard_const(OAUTH2_INIT_PROMPT);
+ auth_data->oauth2->link_prompt = discard_const(OAUTH2_LINK_PROMPT);
+
+ ret = json_format_mechanisms(auth_data, &mechs);
assert_int_equal(ret, EOK);
string = json_dumps(mechs, 0);
assert_string_equal(string, MECHANISMS_OAUTH2);
+
json_decref(mechs);
free(string);
}
void test_json_format_mechanisms_sc1(void **state)
{
- TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
json_t *mechs = NULL;
- char **sc_names = NULL;
char *string;
int ret;
- test_ctx = talloc_new(NULL);
- assert_non_null(test_ctx);
- sc_names = talloc_array(test_ctx, char *, 2);
- assert_non_null(sc_names);
- sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
- assert_non_null(sc_names[0]);
- sc_names[1] = NULL;
-
- ret = json_format_mechanisms(false, NULL,
- false, NULL, NULL, NULL, NULL,
- true, sc_names, SC_INIT_PROMPT, SC_PIN_PROMPT,
- &mechs);
+ auth_data->sc->enabled = true;
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->names[1] = NULL;
+ auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
+ auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
+
+ ret = json_format_mechanisms(auth_data, &mechs);
assert_int_equal(ret, EOK);
string = json_dumps(mechs, 0);
@@ -283,31 +328,26 @@ void test_json_format_mechanisms_sc1(void **state)
json_decref(mechs);
free(string);
- talloc_free(test_ctx);
+ talloc_free(auth_data->sc->names[0]);
}
void test_json_format_mechanisms_sc2(void **state)
{
- TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
json_t *mechs = NULL;
- char **sc_names = NULL;
char *string;
int ret;
- test_ctx = talloc_new(NULL);
- assert_non_null(test_ctx);
- sc_names = talloc_array(test_ctx, char *, 3);
- assert_non_null(sc_names);
- sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
- assert_non_null(sc_names[0]);
- sc_names[1] = talloc_strdup(sc_names, SC2_PROMPT_STR);
- assert_non_null(sc_names[1]);
- sc_names[2] = NULL;
-
- ret = json_format_mechanisms(false, NULL,
- false, NULL, NULL, NULL, NULL,
- true, sc_names, SC_INIT_PROMPT, SC_PIN_PROMPT,
- &mechs);
+ auth_data->sc->enabled = true;
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->names[2] = NULL;
+ auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
+ auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
+
+ ret = json_format_mechanisms(auth_data, &mechs);
assert_int_equal(ret, EOK);
string = json_dumps(mechs, 0);
@@ -315,32 +355,31 @@ void test_json_format_mechanisms_sc2(void **state)
json_decref(mechs);
free(string);
- talloc_free(test_ctx);
+ talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->names[1]);
}
void test_json_format_priority_all(void **state)
{
- TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
json_t *priority = NULL;
- char **sc_names = NULL;
char *string;
int ret;
- test_ctx = talloc_new(NULL);
- assert_non_null(test_ctx);
- sc_names = talloc_array(test_ctx, char *, 3);
- assert_non_null(sc_names);
- sc_names[0] = talloc_strdup(sc_names, SC1_LABEL);
- assert_non_null(sc_names[0]);
- sc_names[1] = talloc_strdup(sc_names, SC2_LABEL);
- assert_non_null(sc_names[1]);
- sc_names[2] = NULL;
+ auth_data->pswd->enabled = true;
+ auth_data->oauth2->enabled = true;
+ auth_data->sc->enabled = true;
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_LABEL);
+ assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_LABEL);
+ assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->names[2] = NULL;
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
- ret = json_format_priority(true, true, true, sc_names, &priority);
+ ret = json_format_priority(auth_data, &priority);
assert_int_equal(ret, EOK);
string = json_dumps(priority, 0);
@@ -348,23 +387,24 @@ void test_json_format_priority_all(void **state)
json_decref(priority);
free(string);
- talloc_free(test_ctx);
+ talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->names[1]);
}
void test_json_format_auth_selection_password(void **state)
{
- TALLOC_CTX *test_ctx;
+ TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
+ auth_data->pswd->enabled = true;
+ auth_data->pswd->prompt = discard_const(PASSWORD_PROMPT);
will_return(__wrap_json_array_append_new, false);
- ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
- false, NULL, NULL, NULL, NULL,
- false, NULL, NULL, NULL,
- &json_msg);
+ ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_PASSWORD);
@@ -373,19 +413,21 @@ void test_json_format_auth_selection_password(void **state)
void test_json_format_auth_selection_oauth2(void **state)
{
- TALLOC_CTX *test_ctx;
+ TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
+ auth_data->oauth2->enabled = true;
+ auth_data->oauth2->uri = discard_const(OAUTH2_URI);
+ auth_data->oauth2->code = discard_const(OAUTH2_CODE);
+ auth_data->oauth2->init_prompt = discard_const(OAUTH2_INIT_PROMPT);
+ auth_data->oauth2->link_prompt = discard_const(OAUTH2_LINK_PROMPT);
will_return(__wrap_json_array_append_new, false);
- ret = json_format_auth_selection(test_ctx, false, NULL,
- true, OAUTH2_URI, OAUTH2_CODE,
- OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
- false, NULL, NULL, NULL,
- &json_msg);
+ ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_OAUTH2);
@@ -394,100 +436,111 @@ void test_json_format_auth_selection_oauth2(void **state)
void test_json_format_auth_selection_sc(void **state)
{
- TALLOC_CTX *test_ctx;
- char **sc_names = NULL;
+ TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
- sc_names = talloc_array(test_ctx, char *, 3);
- assert_non_null(sc_names);
- sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
- assert_non_null(sc_names[0]);
- sc_names[1] = talloc_strdup(sc_names, SC2_PROMPT_STR);
- assert_non_null(sc_names[1]);
- sc_names[2] = NULL;
+ auth_data->sc->enabled = true;
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->names[2] = NULL;
+ auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
+ auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
- ret = json_format_auth_selection(test_ctx, false, NULL,
- false, NULL, NULL, NULL, NULL,
- true, sc_names,
- SC_INIT_PROMPT, SC_PIN_PROMPT,
- &json_msg);
+ ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_SC);
+ talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->names[1]);
talloc_free(test_ctx);
}
void test_json_format_auth_selection_all(void **state)
{
- TALLOC_CTX *test_ctx;
- char **sc_names = NULL;
+ TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
- sc_names = talloc_array(test_ctx, char *, 3);
- assert_non_null(sc_names);
- sc_names[0] = talloc_strdup(sc_names, SC1_PROMPT_STR);
- assert_non_null(sc_names[0]);
- sc_names[1] = talloc_strdup(sc_names, SC2_PROMPT_STR);
- assert_non_null(sc_names[1]);
- sc_names[2] = NULL;
+ auth_data->pswd->enabled = true;
+ auth_data->pswd->prompt = discard_const(PASSWORD_PROMPT);
+ auth_data->oauth2->enabled = true;
+ auth_data->oauth2->uri = discard_const(OAUTH2_URI);
+ auth_data->oauth2->code = discard_const(OAUTH2_CODE);
+ auth_data->oauth2->init_prompt = discard_const(OAUTH2_INIT_PROMPT);
+ auth_data->oauth2->link_prompt = discard_const(OAUTH2_LINK_PROMPT);
+ auth_data->sc->enabled = true;
+ auth_data->sc->names = talloc_array(test_ctx, char *, 3);
+ assert_non_null(auth_data->sc->names);
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->names[2] = NULL;
+ auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
+ auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
- ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
- true, OAUTH2_URI, OAUTH2_CODE,
- OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
- true, sc_names,
- SC_INIT_PROMPT, SC_PIN_PROMPT,
- &json_msg);
+ ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_ALL);
+ talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->names[1]);
talloc_free(test_ctx);
}
void test_json_format_auth_selection_failure(void **state)
{
- TALLOC_CTX *test_ctx;
- char **sc_names = NULL;
+ TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
char *json_msg = NULL;
int ret;
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
- sc_names = talloc_array(test_ctx, char *, 3);
- assert_non_null(sc_names);
- sc_names[0] = talloc_strdup(sc_names, SC1_LABEL);
- assert_non_null(sc_names[0]);
- sc_names[1] = talloc_strdup(sc_names, SC2_LABEL);
- assert_non_null(sc_names[1]);
- sc_names[2] = NULL;
+ auth_data->pswd->enabled = true;
+ auth_data->pswd->prompt = discard_const(PASSWORD_PROMPT);
+ auth_data->oauth2->enabled = true;
+ auth_data->oauth2->uri = discard_const(OAUTH2_URI);
+ auth_data->oauth2->code = discard_const(OAUTH2_CODE);
+ auth_data->oauth2->init_prompt = discard_const(OAUTH2_INIT_PROMPT);
+ auth_data->oauth2->link_prompt = discard_const(OAUTH2_LINK_PROMPT);
+ auth_data->sc->enabled = true;
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_LABEL);
+ assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_LABEL);
+ assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->names[2] = NULL;
+ auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
+ auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
will_return(__wrap_json_array_append_new, true);
- ret = json_format_auth_selection(test_ctx, true, PASSWORD_PROMPT,
- true, OAUTH2_URI, OAUTH2_CODE,
- OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT,
- true, sc_names,
- SC_INIT_PROMPT, SC_PIN_PROMPT,
- &json_msg);
+ ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, ENOMEM);
assert_null(json_msg);
+ talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->names[1]);
talloc_free(test_ctx);
}
void test_generate_json_message_integration(void **state)
{
- TALLOC_CTX *test_ctx;
+ TALLOC_CTX *test_ctx = NULL;
struct pam_data *pd = NULL;
struct prompt_config **pc_list = NULL;
int len;
@@ -787,17 +840,17 @@ int main(int argc, const char *argv[])
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_get_cert_list),
- cmocka_unit_test(test_get_cert_names),
- cmocka_unit_test(test_json_format_mechanisms_password),
- cmocka_unit_test(test_json_format_mechanisms_oauth2),
- cmocka_unit_test(test_json_format_mechanisms_sc1),
- cmocka_unit_test(test_json_format_mechanisms_sc2),
- cmocka_unit_test(test_json_format_priority_all),
- cmocka_unit_test(test_json_format_auth_selection_password),
- cmocka_unit_test(test_json_format_auth_selection_oauth2),
- cmocka_unit_test(test_json_format_auth_selection_sc),
- cmocka_unit_test(test_json_format_auth_selection_all),
- cmocka_unit_test(test_json_format_auth_selection_failure),
+ cmocka_unit_test_setup_teardown(test_get_cert_names, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_mechanisms_password, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_mechanisms_oauth2, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_mechanisms_sc1, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_mechanisms_sc2, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_priority_all, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_auth_selection_password, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_auth_selection_oauth2, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_auth_selection_sc, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_auth_selection_all, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_auth_selection_failure, setup, teardown),
cmocka_unit_test(test_generate_json_message_integration),
cmocka_unit_test(test_json_unpack_password_ok),
cmocka_unit_test(test_json_unpack_sc_ok),
--
2.54.0
From 3b56743dab76b7afa6a6e0e997aac402b688afac Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 29 Jan 2025 10:42:40 +0100
Subject: [PATCH 19/36] Responder: extend smartcard JSON request message
Include the certificate data in the JSON messages to set it in the
authtok structure more easily.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 172 ++++++++++++++++------
src/responder/pam/pamsrv_json.h | 8 +-
src/tests/cmocka/test_pamsrv_json.c | 219 +++++++++++++++++++++++-----
3 files changed, 312 insertions(+), 87 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index 286a3fd50..e28051060 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -488,7 +488,7 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
goto done;
}
- ret = get_cert_names(mem_ctx, cert_list, *_auth_data);
+ ret = get_cert_data(mem_ctx, cert_list, *_auth_data);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain smartcard labels.\n");
goto done;
@@ -499,12 +499,16 @@ done:
}
errno_t
-get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
+get_cert_data(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
struct auth_data *_auth_data)
{
TALLOC_CTX *tmp_ctx = NULL;
struct cert_auth_info *item = NULL;
char **names = NULL;
+ char **cert_instructions = NULL;
+ char **module_names = NULL;
+ char **key_ids = NULL;
+ char **labels = NULL;
int i = 0;
int ret;
@@ -526,19 +530,83 @@ get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
goto done;
}
+ cert_instructions = talloc_array(tmp_ctx, char *, i+1);
+ if (cert_instructions == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ module_names = talloc_array(tmp_ctx, char *, i+1);
+ if (module_names == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ key_ids = talloc_array(tmp_ctx, char *, i+1);
+ if (key_ids == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ labels = talloc_array(tmp_ctx, char *, i+1);
+ if (labels == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
i = 0;
DLIST_FOR_EACH(item, cert_list) {
- names[i] = talloc_strdup(names, item->prompt_str);
+ names[i] = talloc_strdup(names, item->token_name);
if (names[i] == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
ret = ENOMEM;
goto done;
}
+
+ cert_instructions[i] = talloc_strdup(names, item->prompt_str);
+ if (cert_instructions[i] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ module_names[i] = talloc_strdup(names, item->module_name);
+ if (module_names[i] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ key_ids[i] = talloc_strdup(names, item->key_id);
+ if (key_ids[i] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ labels[i] = talloc_strdup(names, item->label);
+ if (labels[i] == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
i++;
}
names[i] = NULL;
+ cert_instructions[i] = NULL;
+ module_names[i] = NULL;
+ key_ids[i] = NULL;
+ labels[i] = NULL;
_auth_data->sc->names = talloc_steal(mem_ctx, names);
+ _auth_data->sc->cert_instructions = talloc_steal(mem_ctx, cert_instructions);
+ _auth_data->sc->module_names = talloc_steal(mem_ctx, module_names);
+ _auth_data->sc->key_ids = talloc_steal(mem_ctx, key_ids);
+ _auth_data->sc->labels = talloc_steal(mem_ctx, labels);
ret = EOK;
done:
@@ -553,8 +621,9 @@ json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech)
json_t *root = NULL;
json_t *json_pass = NULL;
json_t *json_oauth2 = NULL;
+ json_t *json_cert = NULL;
+ json_t *json_cert_array = NULL;
json_t *json_sc = NULL;
- char *key = NULL;
int ret;
root = json_object();
@@ -609,34 +678,52 @@ json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech)
}
if (auth_data->sc->enabled) {
+ json_cert_array = json_array();
+ if (json_cert_array == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
for (int i = 0; auth_data->sc->names[i] != NULL; i++) {
- json_sc = json_pack("{s:s,s:s,s:b,s:s,s:s}",
- "name", auth_data->sc->names[i],
- "role", "smartcard",
- "selectable", true,
- "init_instruction", auth_data->sc->init_prompt,
- "pin_prompt", auth_data->sc->pin_prompt);
- if (json_sc == NULL) {
+ json_cert = json_pack("{s:s,s:s,s:s,s:s,s:s,s:s}",
+ "tokenName", auth_data->sc->names[i],
+ "certInstruction", auth_data->sc->cert_instructions[i],
+ "pinPrompt", auth_data->sc->pin_prompt,
+ "moduleName", auth_data->sc->module_names[i],
+ "keyId", auth_data->sc->key_ids[i],
+ "label", auth_data->sc->labels[i]);
+ if (json_cert == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
ret = ENOMEM;
goto done;
}
- ret = asprintf(&key, "smartcard:%d", i+1);
+ ret = json_array_append_new(json_cert_array, json_cert);
if (ret == -1) {
- DEBUG(SSSDBG_OP_FAILURE, "asprintf failed.\n");
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append_new failed.\n");
+ json_decref(json_cert);
ret = ENOMEM;
goto done;
}
+ }
- ret = json_object_set_new(root, key, json_sc);
- if (ret == -1) {
- DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
- json_decref(json_pass);
- ret = ENOMEM;
- goto done;
- }
- free(key);
+ json_sc = json_pack("{s:s,s:s,s:o}",
+ "name", "Smartcard",
+ "role", "smartcard",
+ "certificates", json_cert_array);
+ if (json_sc == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = json_object_set_new(root, "smartcard", json_sc);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_object_set_new failed.\n");
+ json_decref(json_sc);
+ ret = ENOMEM;
+ goto done;
}
}
@@ -646,6 +733,9 @@ json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech)
done:
if (ret != EOK) {
json_decref(root);
+ if (json_cert_array != NULL) {
+ json_decref(json_cert_array);
+ }
}
return ret;
@@ -656,7 +746,6 @@ json_format_priority(struct auth_data *auth_data, json_t **_priority)
{
json_t *root = NULL;
json_t *json_priority = NULL;
- char *key = NULL;
int ret;
root = json_array();
@@ -666,8 +755,8 @@ json_format_priority(struct auth_data *auth_data, json_t **_priority)
goto done;
}
- if (auth_data->oauth2->enabled) {
- json_priority = json_string("eidp");
+ if (auth_data->sc->enabled) {
+ json_priority = json_string("smartcard");
if (json_priority == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
ret = ENOMEM;
@@ -682,28 +771,19 @@ json_format_priority(struct auth_data *auth_data, json_t **_priority)
}
}
- if (auth_data->sc->enabled) {
- for (int i = 0; auth_data->sc->names[i] != NULL; i++) {
- ret = asprintf(&key, "smartcard:%d", i+1);
- if (ret == -1) {
- DEBUG(SSSDBG_OP_FAILURE, "asprintf failed.\n");
- ret = ENOMEM;
- goto done;
- }
- json_priority = json_string(key);
- free(key);
- if (json_priority == NULL) {
- DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
- ret = ENOMEM;
- goto done;
- }
- ret = json_array_append_new(root, json_priority);
- if (ret == -1) {
- DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
- json_decref(json_priority);
- ret = ENOMEM;
- goto done;
- }
+ if (auth_data->oauth2->enabled) {
+ json_priority = json_string("eidp");
+ if (json_priority == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = json_array_append_new(root, json_priority);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_priority);
+ ret = ENOMEM;
+ goto done;
}
}
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 2c8949261..ddc1c442a 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -60,7 +60,11 @@ struct sc_data {
bool enabled;
char **names;
char *init_prompt;
+ char **cert_instructions;
char *pin_prompt;
+ char **module_names;
+ char **key_ids;
+ char **labels;
};
@@ -79,7 +83,7 @@ get_cert_list(TALLOC_CTX *mem_ctx, struct pam_data *pd,
struct cert_auth_info **_cert_list);
/**
- * @brief Extract smartcard certificate name list from the certificate list
+ * @brief Extract smartcard certificate data from the certificate list
*
* @param[in] mem_ctx Memory context
* @param[in] cert_list Certificate list
@@ -90,7 +94,7 @@ get_cert_list(TALLOC_CTX *mem_ctx, struct pam_data *pd,
* error code otherwise.
*/
errno_t
-get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
+get_cert_data(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list,
struct auth_data *_auth_data);
/**
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 6dc85e384..bb4d9c071 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -63,26 +63,35 @@
"\"linkPrompt\": \"" OAUTH2_LINK_PROMPT "\", " \
"\"uri\": \"short.url.com/tmp\", \"code\": \"1234-5678\", " \
"\"timeout\": 300}"
-#define BASIC_SC "\"smartcard:1\": {" \
- "\"name\": \"prompt1\", \"role\": \"smartcard\", " \
- "\"selectable\": true, " \
- "\"init_instruction\": \"Insert smartcard\", " \
- "\"pin_prompt\": \"PIN\"}"
-#define MULTIPLE_SC "\"smartcard:1\": {" \
- "\"name\": \"prompt1\", \"role\": \"smartcard\", " \
- "\"selectable\": true, " \
- "\"init_instruction\": \"Insert smartcard\", " \
- "\"pin_prompt\": \"PIN\"}, " \
- "\"smartcard:2\": {" \
- "\"name\": \"prompt2\", \"role\": \"smartcard\", " \
- "\"selectable\": true, " \
- "\"init_instruction\": \"Insert smartcard\", " \
- "\"pin_prompt\": \"PIN\"}"
+#define BASIC_SC "\"smartcard\": {" \
+ "\"name\": \"Smartcard\", \"role\": \"smartcard\", " \
+ "\"certificates\": [{" \
+ "\"tokenName\": \"token_name1\", " \
+ "\"certInstruction\": \"prompt1\", " \
+ "\"pinPrompt\": \"" SC_PIN_PROMPT "\", " \
+ "\"moduleName\": \"module_name1\", " \
+ "\"keyId\": \"key_id1\", " \
+ "\"label\": \"label1\"}]}"
+#define MULTIPLE_SC "\"smartcard\": {" \
+ "\"name\": \"Smartcard\", \"role\": \"smartcard\", " \
+ "\"certificates\": [{" \
+ "\"tokenName\": \"token_name1\", " \
+ "\"certInstruction\": \"prompt1\", " \
+ "\"pinPrompt\": \"" SC_PIN_PROMPT "\", " \
+ "\"moduleName\": \"module_name1\", " \
+ "\"keyId\": \"key_id1\", " \
+ "\"label\": \"label1\"}, {" \
+ "\"tokenName\": \"token_name2\", " \
+ "\"certInstruction\": \"prompt2\", " \
+ "\"pinPrompt\": \"" SC_PIN_PROMPT "\", " \
+ "\"moduleName\": \"module_name2\", " \
+ "\"keyId\": \"key_id2\", " \
+ "\"label\": \"label2\"}]}"
#define MECHANISMS_PASSWORD "{" BASIC_PASSWORD "}"
#define MECHANISMS_OAUTH2 "{" BASIC_OAUTH2 "}"
#define MECHANISMS_SC1 "{" BASIC_SC "}"
#define MECHANISMS_SC2 "{" MULTIPLE_SC "}"
-#define PRIORITY_ALL "[\"eidp\", \"smartcard:1\", \"smartcard:2\", \"password\"]"
+#define PRIORITY_ALL "[\"smartcard\", \"eidp\", \"password\"]"
#define AUTH_SELECTION_PASSWORD "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_PASSWORD ", " \
"\"priority\": [\"password\"]}}"
@@ -91,7 +100,7 @@
"\"priority\": [\"eidp\"]}}"
#define AUTH_SELECTION_SC "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_SC2 ", " \
- "\"priority\": [\"smartcard:1\", \"smartcard:2\"]}}"
+ "\"priority\": [\"smartcard\"]}}"
#define AUTH_SELECTION_ALL "{\"authSelection\": {\"mechanisms\": {" \
BASIC_PASSWORD ", " \
BASIC_OAUTH2 ", " \
@@ -144,6 +153,14 @@ static int setup(void **state)
assert_non_null(auth_data->sc);
auth_data->sc->names = talloc_array(auth_data->sc, char *, 3);
assert_non_null(auth_data->sc->names);
+ auth_data->sc->cert_instructions = talloc_array(auth_data->sc, char *, 3);
+ assert_non_null(auth_data->sc->cert_instructions);
+ auth_data->sc->module_names = talloc_array(auth_data->sc, char *, 3);
+ assert_non_null(auth_data->sc->module_names);
+ auth_data->sc->key_ids = talloc_array(auth_data->sc, char *, 3);
+ assert_non_null(auth_data->sc->key_ids);
+ auth_data->sc->labels = talloc_array(auth_data->sc, char *, 3);
+ assert_non_null(auth_data->sc->labels);
auth_data->pswd->enabled = false;
auth_data->oauth2->enabled = false;
@@ -236,7 +253,7 @@ void test_get_cert_list(void **state)
talloc_free(test_ctx);
}
-void test_get_cert_names(void **state)
+void test_get_cert_data(void **state)
{
TALLOC_CTX *test_ctx = NULL;
struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
@@ -248,17 +265,33 @@ void test_get_cert_names(void **state)
assert_non_null(test_ctx);
cai = talloc_zero(test_ctx, struct cert_auth_info);
assert_non_null(cai);
+ cai->token_name = discard_const(SC1_TOKEN_NAME);
+ cai->module_name = discard_const(SC1_MODULE_NAME);
+ cai->key_id = discard_const(SC1_KEY_ID);
+ cai->label = discard_const(SC1_LABEL);
cai->prompt_str = discard_const(SC1_PROMPT_STR);
DLIST_ADD(cert_list, cai);
cai = talloc_zero(test_ctx, struct cert_auth_info);
assert_non_null(cai);
+ cai->token_name = discard_const(SC2_TOKEN_NAME);
+ cai->module_name = discard_const(SC2_MODULE_NAME);
+ cai->key_id = discard_const(SC2_KEY_ID);
+ cai->label = discard_const(SC2_LABEL);
cai->prompt_str = discard_const(SC2_PROMPT_STR);
DLIST_ADD(cert_list, cai);
- ret = get_cert_names(test_ctx, cert_list, auth_data);
+ ret = get_cert_data(test_ctx, cert_list, auth_data);
assert_int_equal(ret, EOK);
- assert_string_equal(auth_data->sc->names[0], SC2_PROMPT_STR);
- assert_string_equal(auth_data->sc->names[1], SC1_PROMPT_STR);
+ assert_string_equal(auth_data->sc->names[0], SC2_TOKEN_NAME);
+ assert_string_equal(auth_data->sc->module_names[0], SC2_MODULE_NAME);
+ assert_string_equal(auth_data->sc->key_ids[0], SC2_KEY_ID);
+ assert_string_equal(auth_data->sc->labels[0], SC2_LABEL);
+ assert_string_equal(auth_data->sc->cert_instructions[0], SC2_PROMPT_STR);
+ assert_string_equal(auth_data->sc->names[1], SC1_TOKEN_NAME);
+ assert_string_equal(auth_data->sc->module_names[1], SC1_MODULE_NAME);
+ assert_string_equal(auth_data->sc->key_ids[1], SC1_KEY_ID);
+ assert_string_equal(auth_data->sc->labels[1], SC1_LABEL);
+ assert_string_equal(auth_data->sc->cert_instructions[1], SC1_PROMPT_STR);
talloc_free(test_ctx);
}
@@ -314,12 +347,21 @@ void test_json_format_mechanisms_sc1(void **state)
int ret;
auth_data->sc->enabled = true;
- auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_TOKEN_NAME);
assert_non_null(auth_data->sc->names[0]);
+ auth_data->sc->cert_instructions[0] = talloc_strdup(auth_data->sc->cert_instructions, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[0]);
+ auth_data->sc->module_names[0] = talloc_strdup(auth_data->sc->module_names, SC1_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[0]);
+ auth_data->sc->key_ids[0] = talloc_strdup(auth_data->sc->key_ids, SC1_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[0]);
+ auth_data->sc->labels[0] = talloc_strdup(auth_data->sc->labels, SC1_LABEL);
+ assert_non_null(auth_data->sc->labels[0]);
auth_data->sc->names[1] = NULL;
- auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
+ will_return(__wrap_json_array_append_new, false);
+
ret = json_format_mechanisms(auth_data, &mechs);
assert_int_equal(ret, EOK);
@@ -329,6 +371,10 @@ void test_json_format_mechanisms_sc1(void **state)
json_decref(mechs);
free(string);
talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->cert_instructions[0]);
+ talloc_free(auth_data->sc->module_names[0]);
+ talloc_free(auth_data->sc->key_ids[0]);
+ talloc_free(auth_data->sc->labels[0]);
}
void test_json_format_mechanisms_sc2(void **state)
@@ -339,14 +385,32 @@ void test_json_format_mechanisms_sc2(void **state)
int ret;
auth_data->sc->enabled = true;
- auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_TOKEN_NAME);
assert_non_null(auth_data->sc->names[0]);
- auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_PROMPT_STR);
+ auth_data->sc->cert_instructions[0] = talloc_strdup(auth_data->sc->cert_instructions, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[0]);
+ auth_data->sc->module_names[0] = talloc_strdup(auth_data->sc->module_names, SC1_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[0]);
+ auth_data->sc->key_ids[0] = talloc_strdup(auth_data->sc->key_ids, SC1_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[0]);
+ auth_data->sc->labels[0] = talloc_strdup(auth_data->sc->labels, SC1_LABEL);
+ assert_non_null(auth_data->sc->labels[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_TOKEN_NAME);
assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->cert_instructions[1] = talloc_strdup(auth_data->sc->cert_instructions, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[1]);
+ auth_data->sc->module_names[1] = talloc_strdup(auth_data->sc->module_names, SC2_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[1]);
+ auth_data->sc->key_ids[1] = talloc_strdup(auth_data->sc->key_ids, SC2_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[1]);
+ auth_data->sc->labels[1] = talloc_strdup(auth_data->sc->labels, SC2_LABEL);
+ assert_non_null(auth_data->sc->labels[1]);
auth_data->sc->names[2] = NULL;
- auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
+ will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
+
ret = json_format_mechanisms(auth_data, &mechs);
assert_int_equal(ret, EOK);
@@ -356,7 +420,15 @@ void test_json_format_mechanisms_sc2(void **state)
json_decref(mechs);
free(string);
talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->cert_instructions[0]);
+ talloc_free(auth_data->sc->module_names[0]);
+ talloc_free(auth_data->sc->key_ids[0]);
+ talloc_free(auth_data->sc->labels[0]);
talloc_free(auth_data->sc->names[1]);
+ talloc_free(auth_data->sc->cert_instructions[1]);
+ talloc_free(auth_data->sc->module_names[1]);
+ talloc_free(auth_data->sc->key_ids[1]);
+ talloc_free(auth_data->sc->labels[1]);
}
void test_json_format_priority_all(void **state)
@@ -378,7 +450,6 @@ void test_json_format_priority_all(void **state)
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
ret = json_format_priority(auth_data, &priority);
assert_int_equal(ret, EOK);
@@ -444,14 +515,30 @@ void test_json_format_auth_selection_sc(void **state)
test_ctx = talloc_new(NULL);
assert_non_null(test_ctx);
auth_data->sc->enabled = true;
- auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_TOKEN_NAME);
assert_non_null(auth_data->sc->names[0]);
- auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_PROMPT_STR);
+ auth_data->sc->cert_instructions[0] = talloc_strdup(auth_data->sc->cert_instructions, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[0]);
+ auth_data->sc->module_names[0] = talloc_strdup(auth_data->sc->module_names, SC1_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[0]);
+ auth_data->sc->key_ids[0] = talloc_strdup(auth_data->sc->key_ids, SC1_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[0]);
+ auth_data->sc->labels[0] = talloc_strdup(auth_data->sc->labels, SC1_LABEL);
+ assert_non_null(auth_data->sc->labels[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_TOKEN_NAME);
assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->cert_instructions[1] = talloc_strdup(auth_data->sc->cert_instructions, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[1]);
+ auth_data->sc->module_names[1] = talloc_strdup(auth_data->sc->module_names, SC2_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[1]);
+ auth_data->sc->key_ids[1] = talloc_strdup(auth_data->sc->key_ids, SC2_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[1]);
+ auth_data->sc->labels[1] = talloc_strdup(auth_data->sc->labels, SC2_LABEL);
+ assert_non_null(auth_data->sc->labels[1]);
auth_data->sc->names[2] = NULL;
- auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
+ will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
@@ -459,7 +546,15 @@ void test_json_format_auth_selection_sc(void **state)
assert_string_equal(json_msg, AUTH_SELECTION_SC);
talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->cert_instructions[0]);
+ talloc_free(auth_data->sc->module_names[0]);
+ talloc_free(auth_data->sc->key_ids[0]);
+ talloc_free(auth_data->sc->labels[0]);
talloc_free(auth_data->sc->names[1]);
+ talloc_free(auth_data->sc->cert_instructions[1]);
+ talloc_free(auth_data->sc->module_names[1]);
+ talloc_free(auth_data->sc->key_ids[1]);
+ talloc_free(auth_data->sc->labels[1]);
talloc_free(test_ctx);
}
@@ -480,26 +575,48 @@ void test_json_format_auth_selection_all(void **state)
auth_data->oauth2->init_prompt = discard_const(OAUTH2_INIT_PROMPT);
auth_data->oauth2->link_prompt = discard_const(OAUTH2_LINK_PROMPT);
auth_data->sc->enabled = true;
- auth_data->sc->names = talloc_array(test_ctx, char *, 3);
- assert_non_null(auth_data->sc->names);
- auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_PROMPT_STR);
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_TOKEN_NAME);
assert_non_null(auth_data->sc->names[0]);
- auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_PROMPT_STR);
+ auth_data->sc->cert_instructions[0] = talloc_strdup(auth_data->sc->cert_instructions, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[0]);
+ auth_data->sc->module_names[0] = talloc_strdup(auth_data->sc->module_names, SC1_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[0]);
+ auth_data->sc->key_ids[0] = talloc_strdup(auth_data->sc->key_ids, SC1_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[0]);
+ auth_data->sc->labels[0] = talloc_strdup(auth_data->sc->labels, SC1_LABEL);
+ assert_non_null(auth_data->sc->labels[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_TOKEN_NAME);
assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->cert_instructions[1] = talloc_strdup(auth_data->sc->cert_instructions, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[1]);
+ auth_data->sc->module_names[1] = talloc_strdup(auth_data->sc->module_names, SC2_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[1]);
+ auth_data->sc->key_ids[1] = talloc_strdup(auth_data->sc->key_ids, SC2_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[1]);
+ auth_data->sc->labels[1] = talloc_strdup(auth_data->sc->labels, SC2_LABEL);
+ assert_non_null(auth_data->sc->labels[1]);
auth_data->sc->names[2] = NULL;
- auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_ALL);
talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->cert_instructions[0]);
+ talloc_free(auth_data->sc->module_names[0]);
+ talloc_free(auth_data->sc->key_ids[0]);
+ talloc_free(auth_data->sc->labels[0]);
talloc_free(auth_data->sc->names[1]);
+ talloc_free(auth_data->sc->cert_instructions[1]);
+ talloc_free(auth_data->sc->module_names[1]);
+ talloc_free(auth_data->sc->key_ids[1]);
+ talloc_free(auth_data->sc->labels[1]);
talloc_free(test_ctx);
}
@@ -520,12 +637,27 @@ void test_json_format_auth_selection_failure(void **state)
auth_data->oauth2->init_prompt = discard_const(OAUTH2_INIT_PROMPT);
auth_data->oauth2->link_prompt = discard_const(OAUTH2_LINK_PROMPT);
auth_data->sc->enabled = true;
- auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_LABEL);
+ auth_data->sc->names[0] = talloc_strdup(auth_data->sc->names, SC1_TOKEN_NAME);
assert_non_null(auth_data->sc->names[0]);
- auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_LABEL);
+ auth_data->sc->cert_instructions[0] = talloc_strdup(auth_data->sc->cert_instructions, SC1_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[0]);
+ auth_data->sc->module_names[0] = talloc_strdup(auth_data->sc->module_names, SC1_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[0]);
+ auth_data->sc->key_ids[0] = talloc_strdup(auth_data->sc->key_ids, SC1_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[0]);
+ auth_data->sc->labels[0] = talloc_strdup(auth_data->sc->labels, SC1_LABEL);
+ assert_non_null(auth_data->sc->labels[0]);
+ auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_TOKEN_NAME);
assert_non_null(auth_data->sc->names[1]);
+ auth_data->sc->cert_instructions[1] = talloc_strdup(auth_data->sc->cert_instructions, SC2_PROMPT_STR);
+ assert_non_null(auth_data->sc->cert_instructions[1]);
+ auth_data->sc->module_names[1] = talloc_strdup(auth_data->sc->module_names, SC2_MODULE_NAME);
+ assert_non_null(auth_data->sc->module_names[1]);
+ auth_data->sc->key_ids[1] = talloc_strdup(auth_data->sc->key_ids, SC2_KEY_ID);
+ assert_non_null(auth_data->sc->key_ids[1]);
+ auth_data->sc->labels[1] = talloc_strdup(auth_data->sc->labels, SC2_LABEL);
+ assert_non_null(auth_data->sc->labels[1]);
auth_data->sc->names[2] = NULL;
- auth_data->sc->init_prompt = discard_const(SC_INIT_PROMPT);
auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
will_return(__wrap_json_array_append_new, true);
@@ -534,7 +666,15 @@ void test_json_format_auth_selection_failure(void **state)
assert_null(json_msg);
talloc_free(auth_data->sc->names[0]);
+ talloc_free(auth_data->sc->cert_instructions[0]);
+ talloc_free(auth_data->sc->module_names[0]);
+ talloc_free(auth_data->sc->key_ids[0]);
+ talloc_free(auth_data->sc->labels[0]);
talloc_free(auth_data->sc->names[1]);
+ talloc_free(auth_data->sc->cert_instructions[1]);
+ talloc_free(auth_data->sc->module_names[1]);
+ talloc_free(auth_data->sc->key_ids[1]);
+ talloc_free(auth_data->sc->labels[1]);
talloc_free(test_ctx);
}
@@ -570,6 +710,7 @@ void test_generate_json_message_integration(void **state)
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
will_return(__wrap_json_array_append_new, false);
+ will_return(__wrap_json_array_append_new, false);
ret = generate_json_auth_message(NULL, pc_list, pd);
assert_int_equal(ret, EOK);
assert_string_equal((char*) pd->resp_list->data, AUTH_SELECTION_ALL);
@@ -840,7 +981,7 @@ int main(int argc, const char *argv[])
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_get_cert_list),
- cmocka_unit_test_setup_teardown(test_get_cert_names, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_get_cert_data, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_mechanisms_password, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_mechanisms_oauth2, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_mechanisms_sc1, setup, teardown),
--
2.54.0
From bb37515adfac3bc9e3c1a95e96363d14f0fb7f86 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 29 Jan 2025 17:27:06 +0100
Subject: [PATCH 20/36] Responder: extend smartcard JSON reply message
Include the certificate data in the JSON message to set it in the
authtok structure more easily.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 77 +++++++++++------------------
src/responder/pam/pamsrv_json.h | 4 +-
src/tests/cmocka/test_pamsrv_json.c | 37 +++++++-------
3 files changed, 48 insertions(+), 70 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index e28051060..09b6c35d1 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -146,27 +146,6 @@ done:
return ret;
}
-static errno_t
-find_certificate_in_list(struct cert_auth_info *cert_list, int cert_num,
- struct cert_auth_info **_cai)
-{
- struct cert_auth_info *cai = NULL;
- struct cert_auth_info *cai_next = NULL;
- int i = 0;
-
- DLIST_FOR_EACH_SAFE(cai, cai_next, cert_list) {
- if (i == cert_num) {
- goto done;
- }
- i++;
- }
-
-done:
- *_cai = cai;
-
- return EOK;
-}
-
static errno_t
obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
struct prompt_config **pc_list, struct auth_data *_auth_data)
@@ -987,23 +966,46 @@ done:
}
errno_t
-json_unpack_smartcard(json_t *jroot, const char **_pin)
+json_unpack_smartcard(TALLOC_CTX *mem_ctx, json_t *jroot,
+ const char **_pin, struct cert_auth_info **_cai)
{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct cert_auth_info *cai = NULL;
char *pin = NULL;
int ret = EOK;
- ret = json_unpack(jroot, "{s:s}",
- "pin", &pin);
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ cai = talloc_zero(tmp_ctx, struct cert_auth_info);
+ if (cai == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = json_unpack(jroot, "{s:s,s:s,s:s,s:s,s:s}",
+ "pin", &pin,
+ "tokenName", &cai->token_name,
+ "moduleName", &cai->module_name,
+ "keyId", &cai->key_id,
+ "label", &cai->label);
if (ret != 0) {
- DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for pin failed.\n");
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for smartcard failed.\n");
ret = EINVAL;
goto done;
}
*_pin = pin;
+ *_cai = talloc_steal(mem_ctx, cai);
+
ret = EOK;
done:
+ talloc_free(tmp_ctx);
+
return ret;
}
@@ -1011,7 +1013,6 @@ errno_t
json_unpack_auth_reply(struct pam_data *pd)
{
TALLOC_CTX *tmp_ctx = NULL;
- struct cert_auth_info *cert_list = NULL;
struct cert_auth_info *cai = NULL;
json_t *jroot = NULL;
json_t *jauth_selection = NULL;
@@ -1022,8 +1023,6 @@ json_unpack_auth_reply(struct pam_data *pd)
char *password = NULL;
char *oauth2_code = NULL;
const char *pin = NULL;
- char *index = NULL;
- int cert_num;
int ret = EOK;
DEBUG(SSSDBG_TRACE_FUNC, "Received JSON message: %s.\n",
@@ -1092,27 +1091,7 @@ json_unpack_auth_reply(struct pam_data *pd)
}
if (strncmp(key, "smartcard", strlen("smartcard")) == 0) {
- ret = json_unpack_smartcard(jobj, &pin);
- if (ret != EOK) {
- goto done;
- }
-
- ret = get_cert_list(tmp_ctx, pd, &cert_list);
- if (ret != EOK) {
- goto done;
- }
-
- index = talloc_strdup(tmp_ctx, key + 10);
- if (index == NULL) {
- DEBUG(SSSDBG_CRIT_FAILURE,
- "talloc_strdup failed: %d.\n", ret);
- ret = ENOMEM;
- goto done;
- }
-
- cert_num = atoi(index);
- cert_num--;
- ret = find_certificate_in_list(cert_list, cert_num, &cai);
+ ret = json_unpack_smartcard(tmp_ctx, jobj, &pin, &cai);
if (ret != EOK) {
goto done;
}
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index ddc1c442a..6e2c90d70 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -185,9 +185,11 @@ json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg,
*
* @param[in] jroot jansson structure containing the smartcard specific data
* @param[out] _pin user PIN
+ * @param[out] _cai certificate data
*/
errno_t
-json_unpack_smartcard(json_t *jroot, const char **_pin);
+json_unpack_smartcard(TALLOC_CTX *mem_ctx, json_t *jroot,
+ const char **_pin, struct cert_auth_info **_cai);
/**
* @brief Unpack GDM reply and check its value
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index bb4d9c071..8e58222ed 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -108,13 +108,15 @@
"\"priority\": " PRIORITY_ALL "}}"
#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}"
-#define SMARTCARD_CONTENT "{\"pin\": \"ThePIN\"}"
+#define SMARTCARD_CONTENT "{\"pin\": \"ThePIN\", \"tokenName\": \"token_name1\", " \
+ "\"moduleName\": \"module_name1\", \"keyId\": \"key_id1\", " \
+ "\"label\": \"label1\"}"
#define AUTH_MECH_REPLY_PASSWORD "{\"authSelection\": {" \
"\"status\": \"Ok\", \"password\": " \
PASSWORD_CONTENT "}}"
#define AUTH_MECH_REPLY_OAUTH2 "{\"authSelection\": {" \
"\"status\": \"Ok\", \"eidp\": {}}}"
-#define AUTH_MECH_REPLY_SMARTCARD "{\"auth-selection\": {" \
+#define AUTH_MECH_REPLY_SMARTCARD "{\"authSelection\": {" \
"\"status\": \"Ok\", \"smartcard:1\": " \
SMARTCARD_CONTENT "}}"
#define AUTH_MECH_ERRONEOUS "{\"authSelection\": {" \
@@ -735,20 +737,30 @@ void test_json_unpack_password_ok(void **state)
json_decref(jroot);
}
-void test_json_unpack_sc_ok(void **state)
+void test_json_unpack_smartcard_ok(void **state)
{
+ TALLOC_CTX *test_ctx = NULL;
json_t *jroot = NULL;
const char *pin = NULL;
+ struct cert_auth_info *cai = NULL;
json_error_t jret;
int ret;
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
jroot = json_loads(SMARTCARD_CONTENT, 0, &jret);
assert_non_null(jroot);
- ret = json_unpack_smartcard(jroot, &pin);
+ ret = json_unpack_smartcard(test_ctx, jroot, &pin, &cai);
assert_int_equal(ret, EOK);
assert_string_equal(pin, "ThePIN");
+ assert_string_equal(cai->token_name, "token_name1");
+ assert_string_equal(cai->module_name, "module_name1");
+ assert_string_equal(cai->key_id, "key_id1");
+ assert_string_equal(cai->label, "label1");
json_decref(jroot);
+
+ talloc_free(test_ctx);
}
void test_json_unpack_auth_reply_password(void **state)
@@ -821,11 +833,6 @@ void test_json_unpack_auth_reply_sc1(void **state)
pd->authtok = sss_authtok_new(pd);
assert_non_null(pd->authtok);
pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_SMARTCARD);
- len = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
- strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
- strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
- ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC1_STR));
- assert_int_equal(ret, EOK);
ret = json_unpack_auth_reply(pd);
assert_int_equal(ret, EOK);
@@ -864,16 +871,6 @@ void test_json_unpack_auth_reply_sc2(void **state)
pd->authtok = sss_authtok_new(pd);
assert_non_null(pd->authtok);
pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_SMARTCARD);
- len = strlen(SC1_CERT_USER)+1+strlen(SC1_TOKEN_NAME)+1+
- strlen(SC1_MODULE_NAME)+1+strlen(SC1_KEY_ID)+1+strlen(SC1_LABEL)+1+
- strlen(SC1_PROMPT_STR)+1+strlen(SC1_PAM_CERT_USER)+1;
- ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC1_STR));
- assert_int_equal(ret, EOK);
- len = strlen(SC2_CERT_USER)+1+strlen(SC2_TOKEN_NAME)+1+
- strlen(SC2_MODULE_NAME)+1+strlen(SC2_KEY_ID)+1+strlen(SC2_LABEL)+1+
- strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
- ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC2_STR));
- assert_int_equal(ret, EOK);
ret = json_unpack_auth_reply(pd);
assert_int_equal(ret, EOK);
@@ -994,7 +991,7 @@ int main(int argc, const char *argv[])
cmocka_unit_test_setup_teardown(test_json_format_auth_selection_failure, setup, teardown),
cmocka_unit_test(test_generate_json_message_integration),
cmocka_unit_test(test_json_unpack_password_ok),
- cmocka_unit_test(test_json_unpack_sc_ok),
+ cmocka_unit_test(test_json_unpack_smartcard_ok),
cmocka_unit_test(test_json_unpack_auth_reply_password),
cmocka_unit_test(test_json_unpack_auth_reply_oauth2),
cmocka_unit_test(test_json_unpack_auth_reply_sc1),
--
2.54.0
From ce9b0e9c6150727250b8e39e8d5d02b053f36aef Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 12 Feb 2025 18:17:25 +0100
Subject: [PATCH 21/36] Responder: make `decode_pam_passkey_msg()` public
This is needed by `pamsrv_json.c`, so let's make it public.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_passkey.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h
index 8d35cbb26..a52579ec5 100644
--- a/src/responder/pam/pamsrv_passkey.h
+++ b/src/responder/pam/pamsrv_passkey.h
@@ -72,6 +72,10 @@ struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx,
bool kerberos_pa);
errno_t pam_passkey_auth_recv(struct tevent_req *req,
int *child_status);
+errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx,
+ uint8_t *buf,
+ size_t len,
+ struct pk_child_user_data **_data);
errno_t pam_eval_passkey_response(struct pam_ctx *pctx,
struct pam_data *pd,
struct pam_auth_req *preq,
--
2.54.0
From a0bc5692815ec97adeaff86bddb3b41d4e29341f Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 10 Sep 2024 09:22:28 +0200
Subject: [PATCH 22/36] Responder: generate JSON message for passkey
Implement a set of functions to retrieve the passkey data and generate
the JSON message with it.
Implement new unit test and adapt the existing ones to take into account
the new data.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 162 ++++++++++++++++++++++++++++
src/responder/pam/pamsrv_json.h | 16 +++
src/tests/cmocka/test_pamsrv_json.c | 111 ++++++++++++++++---
3 files changed, 273 insertions(+), 16 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index 09b6c35d1..e532f3df4 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -32,6 +32,9 @@
#include <string.h>
#include "responder/pam/pamsrv.h"
+#ifdef BUILD_PASSKEY
+#include "responder/pam/pamsrv_passkey.h"
+#endif /* BUILD_PASSKEY */
#include "util/debug.h"
#include "pamsrv_json.h"
@@ -146,6 +149,79 @@ done:
return ret;
}
+#ifdef BUILD_PASSKEY
+static errno_t
+obtain_passkey_data(TALLOC_CTX *mem_ctx, struct pam_data *pd,
+ struct auth_data *_auth_data)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct pk_child_user_data *pk_data = NULL;
+ const char *crypto_challenge = NULL;
+ bool passkey_enabled = false;
+ bool passkey_kerberos = false;
+ bool user_verification = true;
+ uint8_t *buf = NULL;
+ int32_t len;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ENOMEM;
+ }
+
+ ret = pam_get_response_data(tmp_ctx, pd, SSS_PAM_PASSKEY_KRB_INFO, &buf, &len);
+ if (ret == EOK) {
+ passkey_enabled = true;
+ passkey_kerberos = true;
+ ret = decode_pam_passkey_msg(tmp_ctx, buf, len, &pk_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failure decoding PAM passkey msg, ret %d.\n",
+ ret);
+ goto done;
+ }
+
+ if (strcmp(pk_data->user_verification, "false") == 0) {
+ user_verification = false;
+ }
+ crypto_challenge = pk_data->crypto_challenge;
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_FUNC_DATA, "SSS_PAM_PASSKEY_KRB_INFO not found.\n");
+ ret = pam_get_response_data(tmp_ctx, pd, SSS_PAM_PASSKEY_INFO, &buf, &len);
+ if (ret == EOK) {
+ passkey_enabled = true;
+ crypto_challenge = talloc_strdup(tmp_ctx, "");
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_FUNC_DATA, "SSS_PAM_PASSKEY_INFO not found.\n");
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get SSS_PAM_PASSKEY_INFO, ret %d.\n",
+ ret);
+ goto done;
+ }
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Unable to get SSS_PAM_PASSKEY_KRB_INFO, ret %d.\n",
+ ret);
+ goto done;
+ }
+
+ _auth_data->passkey->enabled = passkey_enabled;
+ _auth_data->passkey->kerberos = passkey_kerberos;
+ _auth_data->passkey->key_connected = true;
+ _auth_data->passkey->pin_request = user_verification;
+ _auth_data->passkey->crypto_challenge = talloc_steal(mem_ctx, crypto_challenge);
+ /* Hardcoding of the following values for the moment */
+ _auth_data->passkey->pin_attempts = 8;
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+#endif /* BUILD_PASSKEY */
+
static errno_t
obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
struct prompt_config **pc_list, struct auth_data *_auth_data)
@@ -156,6 +232,9 @@ obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
char *oauth2_link_prompt = NULL;
char *sc_init_prompt = NULL;
char *sc_pin_prompt = NULL;
+ char *passkey_init_prompt = NULL;
+ char *passkey_pin_prompt = NULL;
+ char *passkey_touch_prompt = NULL;
errno_t ret;
tmp_ctx = talloc_new(NULL);
@@ -193,11 +272,32 @@ obtain_prompts(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx,
goto done;
}
+ passkey_init_prompt = talloc_strdup(tmp_ctx, PASSKEY_INIT_PROMPT);
+ if (passkey_init_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ passkey_pin_prompt = talloc_strdup(tmp_ctx, PASSKEY_PIN_PROMPT);
+ if (passkey_pin_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ passkey_touch_prompt = talloc_strdup(tmp_ctx, PASSKEY_TOUCH_PROMPT);
+ if (passkey_touch_prompt == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
_auth_data->pswd->prompt = talloc_steal(mem_ctx, password_prompt);
_auth_data->oauth2->init_prompt = talloc_steal(mem_ctx, oauth2_init_prompt);
_auth_data->oauth2->link_prompt = talloc_steal(mem_ctx, oauth2_link_prompt);
_auth_data->sc->init_prompt = talloc_steal(mem_ctx, sc_init_prompt);
_auth_data->sc->pin_prompt = talloc_steal(mem_ctx, sc_pin_prompt);
+ _auth_data->passkey->init_prompt = talloc_steal(mem_ctx, passkey_init_prompt);
+ _auth_data->passkey->pin_prompt = talloc_steal(mem_ctx, passkey_pin_prompt);
+ _auth_data->passkey->touch_prompt = talloc_steal(mem_ctx, passkey_touch_prompt);
ret = EOK;
done:
@@ -444,6 +544,14 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
}
(*_auth_data)->sc->enabled = true;
+ (*_auth_data)->passkey = talloc_zero(mem_ctx, struct passkey_data);
+ if ((*_auth_data)->passkey == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ (*_auth_data)->passkey->enabled = true;
+
ret = obtain_prompts(cdb, mem_ctx, pc_list, *_auth_data);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain the prompts.\n");
@@ -473,6 +581,16 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
goto done;
}
+#ifdef BUILD_PASSKEY
+ ret = obtain_passkey_data(mem_ctx, pd, *_auth_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain passkey data.\n");
+ goto done;
+ }
+#else
+ (*_auth_data)->passkey->enabled = false;
+#endif /* BUILD_PASSKEY */
+
done:
return ret;
}
@@ -603,6 +721,7 @@ json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech)
json_t *json_cert = NULL;
json_t *json_cert_array = NULL;
json_t *json_sc = NULL;
+ json_t *json_passkey = NULL;
int ret;
root = json_object();
@@ -706,6 +825,33 @@ json_format_mechanisms(struct auth_data *auth_data, json_t **_list_mech)
}
}
+ if (auth_data->passkey->enabled) {
+ json_passkey = json_pack("{s:s,s:s,s:s,s:b,s:b,s:i,s:s,s:s,s:b,s:s}",
+ "name", "Passkey",
+ "role", "passkey",
+ "initInstruction", auth_data->passkey->init_prompt,
+ "keyConnected", auth_data->passkey->key_connected,
+ "pinRequest", auth_data->passkey->pin_request,
+ "pinAttempts", auth_data->passkey->pin_attempts,
+ "pinPrompt", auth_data->passkey->pin_prompt,
+ "touchInstruction", auth_data->passkey->touch_prompt,
+ "kerberos", auth_data->passkey->kerberos,
+ "cryptoChallenge", auth_data->passkey->crypto_challenge);
+ if (json_passkey == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_pack failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = json_object_set_new(root, "passkey", json_passkey);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_passkey);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
*_list_mech = root;
ret = EOK;
@@ -750,6 +896,22 @@ json_format_priority(struct auth_data *auth_data, json_t **_priority)
}
}
+ if (auth_data->passkey->enabled) {
+ json_priority = json_string("passkey");
+ if (json_priority == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_string failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = json_array_append_new(root, json_priority);
+ if (ret == -1) {
+ DEBUG(SSSDBG_OP_FAILURE, "json_array_append failed.\n");
+ json_decref(json_priority);
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
if (auth_data->oauth2->enabled) {
json_priority = json_string("eidp");
if (json_priority == NULL) {
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 6e2c90d70..7b8b9a5f5 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -35,12 +35,16 @@
#define OAUTH2_LINK_PROMPT "Log in online with another device"
#define SC_INIT_PROMPT "Insert smartcard"
#define SC_PIN_PROMPT "PIN"
+#define PASSKEY_INIT_PROMPT "Insert key"
+#define PASSKEY_PIN_PROMPT "Security key PIN"
+#define PASSKEY_TOUCH_PROMPT "Touch security key"
struct auth_data {
struct password_data *pswd;
struct oauth2_data *oauth2;
struct sc_data *sc;
+ struct passkey_data *passkey;
};
struct password_data {
@@ -67,6 +71,18 @@ struct sc_data {
char **labels;
};
+struct passkey_data {
+ bool enabled;
+ char *init_prompt;
+ bool key_connected;
+ bool pin_request;
+ int pin_attempts;
+ char *pin_prompt;
+ char *touch_prompt;
+ bool kerberos;
+ const char *crypto_challenge;
+};
+
/**
* @brief Extract smartcard certificate list from pam_data structure
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 8e58222ed..671af0f0c 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -87,11 +87,21 @@
"\"moduleName\": \"module_name2\", " \
"\"keyId\": \"key_id2\", " \
"\"label\": \"label2\"}]}"
+#define BASIC_PASSKEY "\"passkey\": {" \
+ "\"name\": \"Passkey\", \"role\": \"passkey\", " \
+ "\"initInstruction\": \"" PASSKEY_INIT_PROMPT "\", " \
+ "\"keyConnected\": true, " \
+ "\"pinRequest\": true, \"pinAttempts\": 8, " \
+ "\"pinPrompt\": \"" PASSKEY_PIN_PROMPT "\", " \
+ "\"touchInstruction\": \"" PASSKEY_TOUCH_PROMPT "\", " \
+ "\"kerberos\": false, " \
+ "\"cryptoChallenge\": \"\"}"
#define MECHANISMS_PASSWORD "{" BASIC_PASSWORD "}"
#define MECHANISMS_OAUTH2 "{" BASIC_OAUTH2 "}"
#define MECHANISMS_SC1 "{" BASIC_SC "}"
#define MECHANISMS_SC2 "{" MULTIPLE_SC "}"
-#define PRIORITY_ALL "[\"smartcard\", \"eidp\", \"password\"]"
+#define MECHANISMS_PASSKEY "{" BASIC_PASSKEY "}"
+#define PRIORITY_ALL "[\"smartcard\", \"passkey\", \"eidp\", \"password\"]"
#define AUTH_SELECTION_PASSWORD "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_PASSWORD ", " \
"\"priority\": [\"password\"]}}"
@@ -101,10 +111,14 @@
#define AUTH_SELECTION_SC "{\"authSelection\": {\"mechanisms\": " \
MECHANISMS_SC2 ", " \
"\"priority\": [\"smartcard\"]}}"
+#define AUTH_SELECTION_PASSKEY "{\"authSelection\": {\"mechanisms\": " \
+ MECHANISMS_PASSKEY ", " \
+ "\"priority\": [\"passkey\"]}}"
#define AUTH_SELECTION_ALL "{\"authSelection\": {\"mechanisms\": {" \
BASIC_PASSWORD ", " \
BASIC_OAUTH2 ", " \
- MULTIPLE_SC "}, " \
+ MULTIPLE_SC ", " \
+ BASIC_PASSKEY "}, " \
"\"priority\": " PRIORITY_ALL "}}"
#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}"
@@ -163,10 +177,13 @@ static int setup(void **state)
assert_non_null(auth_data->sc->key_ids);
auth_data->sc->labels = talloc_array(auth_data->sc, char *, 3);
assert_non_null(auth_data->sc->labels);
+ auth_data->passkey = talloc_zero(auth_data, struct passkey_data);
+ assert_non_null(auth_data->passkey);
auth_data->pswd->enabled = false;
auth_data->oauth2->enabled = false;
auth_data->sc->enabled = false;
+ auth_data->passkey->enabled = false;
check_leaks_push(auth_data);
*state = (void *)auth_data;
@@ -433,6 +450,33 @@ void test_json_format_mechanisms_sc2(void **state)
talloc_free(auth_data->sc->labels[1]);
}
+void test_json_format_mechanisms_passkey(void **state)
+{
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
+ json_t *mechs = NULL;
+ char *string;
+ int ret;
+
+ auth_data->passkey->enabled = true;
+ auth_data->passkey->init_prompt = discard_const(PASSKEY_INIT_PROMPT);
+ auth_data->passkey->key_connected = true;
+ auth_data->passkey->pin_request = true;
+ auth_data->passkey->pin_attempts = 8;
+ auth_data->passkey->pin_prompt = discard_const(PASSKEY_PIN_PROMPT);
+ auth_data->passkey->touch_prompt = discard_const(PASSKEY_TOUCH_PROMPT);
+ auth_data->passkey->kerberos = false;
+ auth_data->passkey->crypto_challenge = discard_const("");
+
+ ret = json_format_mechanisms(auth_data, &mechs);
+ assert_int_equal(ret, EOK);
+
+ string = json_dumps(mechs, 0);
+ assert_string_equal(string, MECHANISMS_PASSKEY);
+
+ json_decref(mechs);
+ free(string);
+}
+
void test_json_format_priority_all(void **state)
{
struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
@@ -448,10 +492,9 @@ void test_json_format_priority_all(void **state)
auth_data->sc->names[1] = talloc_strdup(auth_data->sc->names, SC2_LABEL);
assert_non_null(auth_data->sc->names[1]);
auth_data->sc->names[2] = NULL;
+ auth_data->passkey->enabled = true;
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
+ will_return_count(__wrap_json_array_append_new, false, 4);
ret = json_format_priority(auth_data, &priority);
assert_int_equal(ret, EOK);
@@ -560,6 +603,33 @@ void test_json_format_auth_selection_sc(void **state)
talloc_free(test_ctx);
}
+void test_json_format_auth_selection_passkey(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data);
+ char *json_msg = NULL;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ auth_data->passkey->enabled = true;
+ auth_data->passkey->init_prompt = discard_const(PASSKEY_INIT_PROMPT);
+ auth_data->passkey->key_connected = true;
+ auth_data->passkey->pin_request = true;
+ auth_data->passkey->pin_attempts = 8;
+ auth_data->passkey->pin_prompt = discard_const(PASSKEY_PIN_PROMPT);
+ auth_data->passkey->touch_prompt = discard_const(PASSKEY_TOUCH_PROMPT);
+ auth_data->passkey->kerberos = false;
+ auth_data->passkey->crypto_challenge = discard_const("");
+
+ will_return(__wrap_json_array_append_new, false);
+ ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(json_msg, AUTH_SELECTION_PASSKEY);
+
+ talloc_free(test_ctx);
+}
+
void test_json_format_auth_selection_all(void **state)
{
TALLOC_CTX *test_ctx = NULL;
@@ -599,12 +669,17 @@ void test_json_format_auth_selection_all(void **state)
assert_non_null(auth_data->sc->labels[1]);
auth_data->sc->names[2] = NULL;
auth_data->sc->pin_prompt = discard_const(SC_PIN_PROMPT);
-
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
+ auth_data->passkey->enabled = true;
+ auth_data->passkey->init_prompt = discard_const(PASSKEY_INIT_PROMPT);
+ auth_data->passkey->key_connected = true;
+ auth_data->passkey->pin_request = true;
+ auth_data->passkey->pin_attempts = 8;
+ auth_data->passkey->pin_prompt = discard_const(PASSKEY_PIN_PROMPT);
+ auth_data->passkey->touch_prompt = discard_const(PASSKEY_TOUCH_PROMPT);
+ auth_data->passkey->kerberos = false;
+ auth_data->passkey->crypto_challenge = discard_const("");
+
+ will_return_count(__wrap_json_array_append_new, false, 6);
ret = json_format_auth_selection(test_ctx, auth_data, &json_msg);
assert_int_equal(ret, EOK);
assert_string_equal(json_msg, AUTH_SELECTION_ALL);
@@ -685,6 +760,7 @@ void test_generate_json_message_integration(void **state)
TALLOC_CTX *test_ctx = NULL;
struct pam_data *pd = NULL;
struct prompt_config **pc_list = NULL;
+ const char *prompt_pin = "true";
int len;
int ret;
@@ -707,12 +783,11 @@ void test_generate_json_message_integration(void **state)
strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC2_STR));
assert_int_equal(ret, EOK);
+ len = strlen(prompt_pin)+1;
+ ret = pam_add_response(pd, SSS_PAM_PASSKEY_INFO, len,
+ discard_const(prompt_pin));
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
- will_return(__wrap_json_array_append_new, false);
+ will_return_count(__wrap_json_array_append_new, false, 6);
ret = generate_json_auth_message(NULL, pc_list, pd);
assert_int_equal(ret, EOK);
assert_string_equal((char*) pd->resp_list->data, AUTH_SELECTION_ALL);
@@ -983,13 +1058,17 @@ int main(int argc, const char *argv[])
cmocka_unit_test_setup_teardown(test_json_format_mechanisms_oauth2, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_mechanisms_sc1, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_mechanisms_sc2, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_mechanisms_passkey, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_priority_all, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_auth_selection_password, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_auth_selection_oauth2, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_auth_selection_sc, setup, teardown),
+ cmocka_unit_test_setup_teardown(test_json_format_auth_selection_passkey, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_auth_selection_all, setup, teardown),
cmocka_unit_test_setup_teardown(test_json_format_auth_selection_failure, setup, teardown),
+#ifdef BUILD_PASSKEY
cmocka_unit_test(test_generate_json_message_integration),
+#endif
cmocka_unit_test(test_json_unpack_password_ok),
cmocka_unit_test(test_json_unpack_smartcard_ok),
cmocka_unit_test(test_json_unpack_auth_reply_password),
--
2.54.0
From b49fb1440308f549b78ecf41d40d2e80998d78bf Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 17 Sep 2024 10:21:10 +0200
Subject: [PATCH 23/36] util: implement function to set passkey PIN
`sss_authtok_set_local_passkey_pin` provides a way to set the passkey
PIN in the authtok structure for local passkey authentication.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/tests/cmocka/test_authtok.c | 32 ++++++++++++++++++++++++++++++++
src/util/authtok.c | 16 ++++++++++++++++
src/util/authtok.h | 15 +++++++++++++++
3 files changed, 63 insertions(+)
diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c
index c736fd5a3..53ad75d16 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -29,6 +29,8 @@
#include "util/authtok.h"
+#define PIN "ThePIN"
+
struct test_state {
struct sss_auth_token *authtoken;
@@ -751,6 +753,34 @@ static void test_sss_authtok_oauth2(void **state)
sss_authtok_set_empty(ts->authtoken);
}
+void test_sss_authtok_set_local_passkey_pin(void **state)
+{
+ struct test_state *ts = NULL;
+ enum sss_authtok_type type;
+ const char *pin = NULL;
+ char *data = NULL;
+ size_t len = 0;
+ int ret;
+
+ ts = talloc_get_type_abort(*state, struct test_state);
+ type = SSS_AUTHTOK_TYPE_PASSKEY;
+ data = talloc_strdup(ts, "passkey");
+ assert_non_null(data);
+ len = strlen(data) + 1;
+ ret = sss_authtok_set(ts->authtoken, type, (const uint8_t *)data, len);
+ assert_int_equal(ret, EOK);
+
+ ret = sss_authtok_set_local_passkey_pin(ts->authtoken, PIN);
+ assert_int_equal(ret, EOK);
+ ret = sss_authtok_get_passkey_pin(ts->authtoken, &pin, &len);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(len, strlen(PIN));
+ assert_string_equal(pin, PIN);
+
+ talloc_free(data);
+ sss_authtok_set_empty(ts->authtoken);
+}
+
int main(int argc, const char *argv[])
{
@@ -791,6 +821,8 @@ int main(int argc, const char *argv[])
setup, teardown),
cmocka_unit_test_setup_teardown(test_sss_authtok_oauth2,
setup, teardown),
+ cmocka_unit_test_setup_teardown(test_sss_authtok_set_local_passkey_pin,
+ setup, teardown),
};
/* Set debug level to invalid value so we can decide if -d 0 was used. */
diff --git a/src/util/authtok.c b/src/util/authtok.c
index df64ede57..4c2d7ca4d 100644
--- a/src/util/authtok.c
+++ b/src/util/authtok.c
@@ -818,6 +818,22 @@ done:
return ret;
}
+errno_t sss_authtok_set_local_passkey_pin(struct sss_auth_token *tok,
+ const char *pin)
+{
+ int ret;
+
+ if (!tok) {
+ return EINVAL;
+ }
+
+ sss_authtok_set_empty(tok);
+ ret = sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_PASSKEY,
+ "passkey", pin, strlen(pin));
+
+ return ret;
+}
+
errno_t sss_authtok_get_passkey(TALLOC_CTX *mem_ctx,
struct sss_auth_token *tok,
const char **_prompt,
diff --git a/src/util/authtok.h b/src/util/authtok.h
index acabb7078..b68101211 100644
--- a/src/util/authtok.h
+++ b/src/util/authtok.h
@@ -508,6 +508,21 @@ errno_t sss_authtok_get_passkey(TALLOC_CTX *mem_ctx,
errno_t sss_authtok_get_passkey_pin(struct sss_auth_token *tok,
const char **pin, size_t *len);
+/**
+ * @brief Set local passkey PIN in sss_auth_token structure
+ *
+ * @param tok A pointer to an sss_auth_token
+ * @param pin A pointer to a const char *, that will point to a null
+ * terminated string
+ *
+ * @return EOK on success
+ * EINVAL if there's no token
+ * ENOENT if the token is empty
+ * EACCESS if the token is not a passkey token
+ */
+errno_t sss_authtok_set_local_passkey_pin(struct sss_auth_token *tok,
+ const char *pin);
+
/**
* @brief Set passkey kerberos preauth credentials into an auth token,
* replacing any previous data.
--
2.54.0
From 9c67585687e985f78b07e95960f23e14d86a207a Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 17 Sep 2024 09:20:53 +0200
Subject: [PATCH 24/36] Responder: parse reply for passkey
Parse GUI reply for passkey and set the appropriate data in
`sss_auth_token` structure.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 72 +++++++++++++++++++++++++++++
src/responder/pam/pamsrv_json.h | 12 +++++
src/tests/cmocka/test_pamsrv_json.c | 54 ++++++++++++++++++++++
3 files changed, 138 insertions(+)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index e532f3df4..37d3c276a 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -1171,6 +1171,45 @@ done:
return ret;
}
+errno_t
+json_unpack_passkey(json_t *jroot, const char **_pin, bool *_kerberos,
+ char **_crypto_challenge)
+{
+ json_t *pin = NULL;
+ json_t *kerberos = NULL;
+ json_t *crypto = NULL;
+ int ret = EOK;
+
+ pin = json_object_get(jroot, "pin");
+ if (pin == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_object_get for pin failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ kerberos = json_object_get(jroot, "kerberos");
+ if (kerberos == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_object_get for kerberos failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ crypto = json_object_get(jroot, "cryptoChallenge");
+ if (crypto == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "json_object_get for crypto-challenge failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+
+ *_pin = discard_const(json_string_value(pin));
+ *_kerberos = json_boolean_value(kerberos);
+ *_crypto_challenge = discard_const(json_string_value(crypto));
+ ret = EOK;
+
+done:
+ return ret;
+}
+
errno_t
json_unpack_auth_reply(struct pam_data *pd)
{
@@ -1182,9 +1221,12 @@ json_unpack_auth_reply(struct pam_data *pd)
json_error_t jret;
const char *key = NULL;
const char *status = NULL;
+ const char *user_verification = NULL;
char *password = NULL;
char *oauth2_code = NULL;
const char *pin = NULL;
+ char *crypto_challenge = NULL;
+ bool passkey_kerberos = false;
int ret = EOK;
DEBUG(SSSDBG_TRACE_FUNC, "Received JSON message: %s.\n",
@@ -1270,6 +1312,36 @@ json_unpack_auth_reply(struct pam_data *pd)
}
goto done;
}
+
+ if (strcmp(key, "passkey") == 0) {
+ ret = json_unpack_passkey(jobj, &pin, &passkey_kerberos, &crypto_challenge);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (passkey_kerberos) {
+ if (pin != NULL && pin[0] != '\0') {
+ user_verification = talloc_strdup(tmp_ctx, "true");
+ } else {
+ user_verification = talloc_strdup(tmp_ctx, "false");
+ }
+ ret = sss_authtok_set_passkey_krb(pd->authtok, user_verification, crypto_challenge, pin);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_authtok_set_passkey_krb failed: %d.\n", ret);
+ goto done;
+ }
+ } else {
+ ret = sss_authtok_set_local_passkey_pin(pd->authtok, pin);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_authtok_set_local_passkey_pin failed: %d.\n",
+ ret);
+ goto done;
+ }
+ }
+ goto done;
+ }
}
DEBUG(SSSDBG_CRIT_FAILURE, "Unknown authentication mechanism\n");
diff --git a/src/responder/pam/pamsrv_json.h b/src/responder/pam/pamsrv_json.h
index 7b8b9a5f5..2f6c5cf36 100644
--- a/src/responder/pam/pamsrv_json.h
+++ b/src/responder/pam/pamsrv_json.h
@@ -207,6 +207,18 @@ errno_t
json_unpack_smartcard(TALLOC_CTX *mem_ctx, json_t *jroot,
const char **_pin, struct cert_auth_info **_cai);
+/**
+ * @brief Unpack passkey specific data reply
+ *
+ * @param[in] jroot jansson structure containing the data
+ * @param[out] _pin user PIN
+ * @param[out] _kerberos whether passkey auth is kerberos
+ * @param[out] _crypto_challenge cryptographic challenge
+ */
+errno_t
+json_unpack_passkey(json_t *jroot, const char **_pin, bool *_kerberos,
+ char **_crypto_challenge);
+
/**
* @brief Unpack GDM reply and check its value
*
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index 671af0f0c..d432ea065 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -34,6 +34,7 @@
#define OAUTH2_URI_COMP "\0"
#define OAUTH2_CODE "1234-5678"
#define OAUTH2_STR OAUTH2_URI OAUTH2_URI_COMP OAUTH2_CODE
+#define PASSKEY_CRYPTO_CHAL "6uDMvRKj3W5xJV3HaQjZrtXMNmUUAjRGklFG2MIhN5s="
#define SC1_CERT_USER "cert_user1\0"
#define SC1_TOKEN_NAME "token_name1\0"
@@ -125,6 +126,8 @@
#define SMARTCARD_CONTENT "{\"pin\": \"ThePIN\", \"tokenName\": \"token_name1\", " \
"\"moduleName\": \"module_name1\", \"keyId\": \"key_id1\", " \
"\"label\": \"label1\"}"
+#define PASSKEY_CONTENT "{\"pin\": \"ThePIN\", \"kerberos\": true, " \
+ "\"cryptoChallenge\": \"" PASSKEY_CRYPTO_CHAL "\"}"
#define AUTH_MECH_REPLY_PASSWORD "{\"authSelection\": {" \
"\"status\": \"Ok\", \"password\": " \
PASSWORD_CONTENT "}}"
@@ -133,6 +136,9 @@
#define AUTH_MECH_REPLY_SMARTCARD "{\"authSelection\": {" \
"\"status\": \"Ok\", \"smartcard:1\": " \
SMARTCARD_CONTENT "}}"
+#define AUTH_MECH_REPLY_PASSKEY "{\"authSelection\": {" \
+ "\"status\": \"Ok\", \"passkey\": " \
+ PASSKEY_CONTENT "}}"
#define AUTH_MECH_ERRONEOUS "{\"authSelection\": {" \
"\"status\": \"Ok\", \"lololo\": {}}}"
@@ -838,6 +844,26 @@ void test_json_unpack_smartcard_ok(void **state)
talloc_free(test_ctx);
}
+void test_json_unpack_passkey_ok(void **state)
+{
+ json_t *jroot = NULL;
+ const char *pin = NULL;
+ char *crypto_challenge = NULL;
+ bool kerberos = false;
+ json_error_t jret;
+ int ret;
+
+ jroot = json_loads(PASSKEY_CONTENT, 0, &jret);
+ assert_non_null(jroot);
+
+ ret = json_unpack_passkey(jroot, &pin, &kerberos, &crypto_challenge);
+ assert_int_equal(ret, EOK);
+ assert_string_equal(pin, "ThePIN");
+ assert_int_equal(kerberos, true);
+ assert_string_equal(crypto_challenge, PASSKEY_CRYPTO_CHAL);
+ json_decref(jroot);
+}
+
void test_json_unpack_auth_reply_password(void **state)
{
TALLOC_CTX *test_ctx = NULL;
@@ -965,6 +991,32 @@ void test_json_unpack_auth_reply_sc2(void **state)
talloc_free(test_ctx);
}
+void test_json_unpack_auth_reply_passkey(void **state)
+{
+ TALLOC_CTX *test_ctx = NULL;
+ struct pam_data *pd = NULL;
+ const char *pin = NULL;
+ size_t len = 0;
+ int ret;
+
+ test_ctx = talloc_new(NULL);
+ assert_non_null(test_ctx);
+ pd = talloc_zero(test_ctx, struct pam_data);
+ assert_non_null(pd);
+ pd->authtok = sss_authtok_new(pd);
+ assert_non_null(pd->authtok);
+ pd->json_auth_msg = discard_const(AUTH_SELECTION_PASSKEY);
+ pd->json_auth_selected = discard_const(AUTH_MECH_REPLY_PASSKEY);
+
+ ret = json_unpack_auth_reply(pd);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(sss_authtok_get_type(pd->authtok), SSS_AUTHTOK_TYPE_PASSKEY_KRB);
+ sss_authtok_get_passkey_pin(pd->authtok, &pin, &len);
+ assert_string_equal(pin, "ThePIN");
+
+ talloc_free(test_ctx);
+}
+
void test_json_unpack_auth_reply_failure(void **state)
{
TALLOC_CTX *test_ctx = NULL;
@@ -1071,10 +1123,12 @@ int main(int argc, const char *argv[])
#endif
cmocka_unit_test(test_json_unpack_password_ok),
cmocka_unit_test(test_json_unpack_smartcard_ok),
+ cmocka_unit_test(test_json_unpack_passkey_ok),
cmocka_unit_test(test_json_unpack_auth_reply_password),
cmocka_unit_test(test_json_unpack_auth_reply_oauth2),
cmocka_unit_test(test_json_unpack_auth_reply_sc1),
cmocka_unit_test(test_json_unpack_auth_reply_sc2),
+ cmocka_unit_test(test_json_unpack_auth_reply_passkey),
cmocka_unit_test(test_json_unpack_auth_reply_failure),
cmocka_unit_test(test_json_unpack_oauth2_code),
cmocka_unit_test(test_is_pam_json_enabled_service_in_list),
--
2.54.0
From 590caf2a3a25eea897f65599f4186d9a61245101 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Thu, 27 Mar 2025 10:20:12 +0100
Subject: [PATCH 25/36] krb5_child: advertise authentication methods
During the `preauthentication` phase krb5_child checks for the available
authentication methods for the given user, advertises them and the
process is kept alive. Once the state is change to `authentication` the
same krb5_child process processes the credentials and proceeds with the
authentication itself.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/providers/krb5/krb5_child.c | 611 ++++++++++++++++++++------------
1 file changed, 375 insertions(+), 236 deletions(-)
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 0506a3527..15bef2d00 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -564,6 +564,62 @@ static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
return ERR_CHECK_NEXT_AUTH_TYPE;
}
+static krb5_error_code request_otp(krb5_context ctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
+{
+ krb5_responder_otp_challenge *chl;
+ size_t i;
+ krb5_error_code kerr;
+
+ kerr = krb5_responder_otp_get_challenge(ctx, rctx, &chl);
+ if (kerr != EOK || chl == NULL) {
+ /* Either an error, or nothing to do. */
+ return kerr;
+ }
+
+ if (chl->tokeninfo == NULL || chl->tokeninfo[0] == NULL) {
+ /* No tokeninfos? Absurd! */
+ kerr = EINVAL;
+ goto done;
+ }
+
+ for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
+ i, chl->tokeninfo[i]->vendor);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
+ i, chl->tokeninfo[i]->token_id);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
+ i, chl->tokeninfo[i]->challenge);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n",
+ i, chl->tokeninfo[i]->flags);
+ }
+
+ if (chl->tokeninfo[0]->vendor != NULL) {
+ kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[0]->vendor);
+ }
+ if (chl->tokeninfo[0]->token_id != NULL) {
+ kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id);
+ }
+ if (chl->tokeninfo[0]->challenge != NULL) {
+ kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->challenge);
+ }
+ /* Allocation errors are ignored on purpose */
+
+ DEBUG(SSSDBG_TRACE_ALL, "Setting otp prompting.\n");
+ if (kr->otp) {
+ kerr = k5c_attach_otp_info_msg(kr);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to add otp prompting data.\n");
+ }
+ }
+
+done:
+ krb5_responder_otp_challenge_free(ctx, rctx, chl);
+ return kerr;
+}
+
static krb5_error_code answer_otp(krb5_context ctx,
struct krb5_req *kr,
krb5_responder_context rctx)
@@ -572,6 +628,15 @@ static krb5_error_code answer_otp(krb5_context ctx,
char *token = NULL, *pin = NULL;
krb5_error_code ret;
size_t i;
+ enum sss_authtok_type type;
+
+ type = sss_authtok_get_type(kr->pd->authtok);
+ if (type != SSS_AUTHTOK_TYPE_2FA_SINGLE
+ && type != SSS_AUTHTOK_TYPE_2FA) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected authentication token type [%s]\n",
+ sss_authtok_type_to_str(type));
+ return ERR_CHECK_NEXT_AUTH_TYPE;
+ }
ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl);
if (ret != EOK || chl == NULL) {
@@ -587,32 +652,27 @@ static krb5_error_code answer_otp(krb5_context ctx,
kr->otp = true;
- if (kr->pd->cmd == SSS_PAM_PREAUTH) {
- for (i = 0; chl->tokeninfo[i] != NULL; i++) {
- DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
- i, chl->tokeninfo[i]->vendor);
- DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
- i, chl->tokeninfo[i]->token_id);
- DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
- i, chl->tokeninfo[i]->challenge);
- DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n",
- i, chl->tokeninfo[i]->flags);
- }
-
- if (chl->tokeninfo[0]->vendor != NULL) {
- kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[0]->vendor);
- }
- if (chl->tokeninfo[0]->token_id != NULL) {
- kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id);
- }
- if (chl->tokeninfo[0]->challenge != NULL) {
- kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->challenge);
- }
- /* Allocation errors are ignored on purpose */
+ for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
+ i, chl->tokeninfo[i]->vendor);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
+ i, chl->tokeninfo[i]->token_id);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
+ i, chl->tokeninfo[i]->challenge);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n",
+ i, chl->tokeninfo[i]->flags);
+ }
- DEBUG(SSSDBG_TRACE_INTERNAL, "Exit answer_otp during pre-auth.\n");
- return ERR_CHECK_NEXT_AUTH_TYPE;
+ if (chl->tokeninfo[0]->vendor != NULL) {
+ kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[0]->vendor);
}
+ if (chl->tokeninfo[0]->token_id != NULL) {
+ kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id);
+ }
+ if (chl->tokeninfo[0]->challenge != NULL) {
+ kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->challenge);
+ }
+ /* Allocation errors are ignored on purpose */
/* Find the first supported tokeninfo which matches our authtoken. */
for (i = 0; chl->tokeninfo[i] != NULL; i++) {
@@ -705,6 +765,19 @@ done:
return res;
}
+static krb5_error_code request_pkinit(struct krb5_req *kr)
+{
+ krb5_error_code kerr;
+
+ DEBUG(SSSDBG_TRACE_ALL, "Setting pkinit prompting.\n");
+ kerr = pam_add_response(kr->pd, SSS_CERT_AUTH_PROMPTING, 0, NULL);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add pkinit prompting data.\n");
+ }
+
+ return kerr;
+}
+
static krb5_error_code answer_pkinit(krb5_context ctx,
struct krb5_req *kr,
krb5_responder_context rctx)
@@ -715,6 +788,15 @@ static krb5_error_code answer_pkinit(krb5_context ctx,
const char *module_name = NULL;
krb5_responder_pkinit_challenge *chl = NULL;
size_t c;
+ enum sss_authtok_type type;
+
+ type = sss_authtok_get_type(kr->pd->authtok);
+ if (type != SSS_AUTHTOK_TYPE_SC_PIN && type != SSS_AUTHTOK_TYPE_SC_KEYPAD) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected authentication token type [%s]\n",
+ sss_authtok_type_to_str(type));
+ kerr = ERR_CHECK_NEXT_AUTH_TYPE;
+ goto done;
+ }
kerr = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl);
if (kerr != EOK || chl == NULL) {
@@ -737,58 +819,38 @@ static krb5_error_code answer_pkinit(krb5_context ctx,
DEBUG(SSSDBG_TRACE_ALL, "Setting pkinit_prompting.\n");
kr->pkinit_prompting = true;
- if (kr->pd->cmd == SSS_PAM_AUTHENTICATE) {
- if ((sss_authtok_get_type(kr->pd->authtok)
- == SSS_AUTHTOK_TYPE_SC_PIN
- || sss_authtok_get_type(kr->pd->authtok)
- == SSS_AUTHTOK_TYPE_SC_KEYPAD)) {
- kerr = sss_authtok_get_sc(kr->pd->authtok, &pin, NULL,
- &token_name, NULL,
- &module_name, NULL,
- NULL, NULL, NULL, NULL);
- if (kerr != EOK) {
- DEBUG(SSSDBG_OP_FAILURE,
- "sss_authtok_get_sc failed.\n");
- goto done;
- }
-
- for (c = 0; chl->identities[c] != NULL; c++) {
- if (chl->identities[c]->identity != NULL
- && pkinit_identity_matches(chl->identities[c]->identity,
- token_name, module_name)) {
- break;
- }
- }
+ kerr = sss_authtok_get_sc(kr->pd->authtok, &pin, NULL,
+ &token_name, NULL,
+ &module_name, NULL,
+ NULL, NULL, NULL, NULL);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "sss_authtok_get_sc failed.\n");
+ goto done;
+ }
- if (chl->identities[c] == NULL) {
- DEBUG(SSSDBG_CRIT_FAILURE,
- "No matching identity for [%s][%s] found in pkinit "
- "challenge.\n", token_name, module_name);
- kerr = EINVAL;
- goto done;
- }
+ for (c = 0; chl->identities[c] != NULL; c++) {
+ if (chl->identities[c]->identity != NULL
+ && pkinit_identity_matches(chl->identities[c]->identity,
+ token_name, module_name)) {
+ break;
+ }
+ }
- kerr = krb5_responder_pkinit_set_answer(ctx, rctx,
- chl->identities[c]->identity,
- pin);
- if (kerr != 0) {
- DEBUG(SSSDBG_OP_FAILURE,
- "krb5_responder_set_answer failed.\n");
- }
+ if (chl->identities[c] == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No matching identity for [%s][%s] found in pkinit "
+ "challenge.\n", token_name, module_name);
+ kerr = EINVAL;
+ goto done;
+ }
- goto done;
- } else {
- DEBUG(SSSDBG_MINOR_FAILURE,
- "Unexpected authentication token type [%s]\n",
- sss_authtok_type_to_str(sss_authtok_get_type(kr->pd->authtok)));
- kerr = ERR_CHECK_NEXT_AUTH_TYPE;
- goto done;
- }
- } else {
- /* We only expect SSS_PAM_PREAUTH here, but also for all other
- * commands the graceful solution would be to let the caller
- * check other authentication methods as well. */
- kerr = ERR_CHECK_NEXT_AUTH_TYPE;
+ kerr = krb5_responder_pkinit_set_answer(ctx, rctx,
+ chl->identities[c]->identity,
+ pin);
+ if (kerr != 0) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "krb5_responder_set_answer failed.\n");
}
done:
@@ -820,11 +882,9 @@ static errno_t krb5_req_update(struct krb5_req *dest, struct krb5_req *src)
return EOK;
}
-static krb5_error_code idp_oauth2_preauth(struct krb5_req *kr,
- struct sss_idp_oauth2 *oauth2)
+static krb5_error_code idp_oauth2_method_req(struct krb5_req *kr,
+ struct sss_idp_oauth2 *oauth2)
{
- struct krb5_req *tmpkr = NULL;
- uint32_t offline;
errno_t ret;
if (oauth2->verification_uri == NULL || oauth2->user_code == NULL) {
@@ -842,6 +902,19 @@ static krb5_error_code idp_oauth2_preauth(struct krb5_req *kr,
return ret;
}
+done:
+ return ret;
+}
+
+static krb5_error_code k5c_send_and_recv(struct krb5_req *kr)
+{
+ struct krb5_req *tmpkr = NULL;
+ uint32_t offline;
+ errno_t ret;
+
+ /* Challenge was presented. We need to continue the authentication
+ * with this exact child process in order to maintain internal Kerberos
+ * state so we are able to respond to this particular challenge. */
ret = k5c_attach_keep_alive_msg(kr);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_keep_alive_msg failed.\n");
@@ -878,15 +951,13 @@ done:
return ret;
}
-static krb5_error_code answer_idp_oauth2(krb5_context kctx,
- struct krb5_req *kr,
- krb5_responder_context rctx)
+static krb5_error_code request_idp_oauth2(krb5_context kctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx,
+ struct sss_idp_oauth2 **_data)
{
- enum sss_authtok_type type;
struct sss_idp_oauth2 *data;
const char *challenge;
- const char *token;
- size_t token_len;
krb5_error_code kerr;
challenge = krb5_responder_get_challenge(kctx, rctx,
@@ -902,18 +973,33 @@ static krb5_error_code answer_idp_oauth2(krb5_context kctx,
}
if (kr->pd->cmd == SSS_PAM_PREAUTH) {
- kerr = idp_oauth2_preauth(kr, data);
+ kerr = idp_oauth2_method_req(kr, data);
if (kerr != EOK) {
goto done;
}
}
- if (kr->pd->cmd != SSS_PAM_AUTHENTICATE) {
- DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd);
- kerr = EINVAL;
- goto done;
+ *_data = data;
+ kerr = EOK;
+
+done:
+ if (kerr != EOK) {
+ sss_idp_oauth2_free(data);
}
+ return kerr;
+}
+
+static krb5_error_code answer_idp_oauth2(krb5_context kctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx,
+ struct sss_idp_oauth2 *data)
+{
+ enum sss_authtok_type type;
+ const char *token;
+ size_t token_len;
+ krb5_error_code kerr;
+
type = sss_authtok_get_type(kr->pd->authtok);
if (type != SSS_AUTHTOK_TYPE_OAUTH2) {
DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected authentication token type [%s]\n",
@@ -952,8 +1038,6 @@ static krb5_error_code answer_idp_oauth2(krb5_context kctx,
kerr = EOK;
done:
- sss_idp_oauth2_free(data);
-
return kerr;
}
@@ -1038,11 +1122,9 @@ static errno_t k5c_attach_passkey_msg(struct krb5_req *kr,
return ret;
}
-static krb5_error_code passkey_preauth(struct krb5_req *kr,
- struct sss_passkey_challenge *passkey)
+static krb5_error_code passkey_method_req(struct krb5_req *kr,
+ struct sss_passkey_challenge *passkey)
{
- struct krb5_req *tmpkr = NULL;
- uint32_t offline;
errno_t ret;
if (passkey->domain == NULL || passkey->credential_id_list == NULL
@@ -1057,64 +1139,22 @@ static krb5_error_code passkey_preauth(struct krb5_req *kr,
return ret;
}
- /* Challenge was presented. We need to continue the authentication
- * with this exact child process in order to maintain internal Kerberos
- * state so we are able to respond to this particular challenge. */
- ret = k5c_attach_keep_alive_msg(kr);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "k5c_attach_keep_alive_msg failed.\n");
- return ret;
- }
-
- tmpkr = talloc_zero(NULL, struct krb5_req);
- if (tmpkr == NULL) {
- DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
- ret = ENOMEM;
- goto done;
- }
-
- /* Send reply and wait for next step. */
- ret = k5c_send_data(kr, STDOUT_FILENO, ret);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "Failed to send reply\n");
- }
-
- ret = k5c_recv_data(tmpkr, STDIN_FILENO, &offline);
- if (ret != EOK) {
- goto done;
- }
-
- ret = krb5_req_update(kr, tmpkr);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "Unable to update krb request [%d]: %s\n",
- ret, sss_strerror(ret));
- goto done;
- }
-
done:
- talloc_free(tmpkr);
return ret;
}
#endif /* BUILD_PASSKEY */
-static krb5_error_code answer_passkey(krb5_context kctx,
- struct krb5_req *kr,
- krb5_responder_context rctx)
+static krb5_error_code request_passkey(krb5_context kctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
{
#ifndef BUILD_PASSKEY
DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n");
return EINVAL;
#else
- enum sss_authtok_type type;
struct sss_passkey_message *msg;
- struct sss_passkey_message *reply_msg = NULL;
const char *challenge;
- const char *reply;
- char *reply_str = NULL;
- enum sss_passkey_phase phase;
- const char *state;
- size_t reply_len;
- krb5_error_code kerr;
+ krb5_error_code kerr = EINVAL;
challenge = krb5_responder_get_challenge(kctx, rctx,
SSSD_PASSKEY_QUESTION);
@@ -1129,17 +1169,33 @@ static krb5_error_code answer_passkey(krb5_context kctx,
}
if (kr->pd->cmd == SSS_PAM_PREAUTH) {
- kerr = passkey_preauth(kr, msg->data.challenge);
+ kerr = passkey_method_req(kr, msg->data.challenge);
if (kerr != EOK) {
goto done;
}
}
- if (kr->pd->cmd != SSS_PAM_AUTHENTICATE) {
- DEBUG(SSSDBG_OP_FAILURE, "Unexpected command [%d]\n", kr->pd->cmd);
- kerr = EINVAL;
- goto done;
- }
+done:
+ return kerr;
+#endif /* BUILD_PASSKEY */
+}
+
+static krb5_error_code answer_passkey(krb5_context kctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx)
+{
+#ifndef BUILD_PASSKEY
+ DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n");
+ return EINVAL;
+#else
+ enum sss_authtok_type type;
+ struct sss_passkey_message *reply_msg = NULL;
+ const char *reply;
+ char *reply_str = NULL;
+ enum sss_passkey_phase phase;
+ const char *state;
+ size_t reply_len;
+ krb5_error_code kerr;
type = sss_authtok_get_type(kr->pd->authtok);
if (type != SSS_AUTHTOK_TYPE_PASSKEY_REPLY) {
@@ -1199,44 +1255,185 @@ done:
#endif /* BUILD_PASSKEY */
}
+static krb5_error_code request_password(struct krb5_req *kr)
+{
+ krb5_error_code kerr;
+
+ DEBUG(SSSDBG_TRACE_ALL, "Setting password prompting.\n");
+ kerr = pam_add_response(kr->pd, SSS_PASSWORD_PROMPTING, 0, NULL);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add password prompting data.\n");
+ }
+
+ return kerr;
+}
+
static krb5_error_code answer_password(krb5_context kctx,
struct krb5_req *kr,
krb5_responder_context rctx)
{
- krb5_error_code kerr;
- int ret;
+ krb5_error_code kerr = EINVAL;
const char *pwd;
+ enum sss_authtok_type type;
kr->password_prompting = true;
- if ((kr->pd->cmd == SSS_PAM_AUTHENTICATE
- || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM
- || kr->pd->cmd == SSS_PAM_CHAUTHTOK)
- && (sss_authtok_get_type(kr->pd->authtok)
- == SSS_AUTHTOK_TYPE_PASSWORD
- || sss_authtok_get_type(kr->pd->authtok)
- == SSS_AUTHTOK_TYPE_PAM_STACKED)) {
- ret = sss_authtok_get_password(kr->pd->authtok, &pwd, NULL);
- if (ret != EOK) {
+ type = sss_authtok_get_type(kr->pd->authtok);
+ if (type != SSS_AUTHTOK_TYPE_PASSWORD
+ && type != SSS_AUTHTOK_TYPE_PAM_STACKED) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected authentication token type [%s]\n",
+ sss_authtok_type_to_str(type));
+ kerr = ERR_CHECK_NEXT_AUTH_TYPE;
+ goto done;
+ }
+
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE
+ || kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM
+ || kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ kerr = sss_authtok_get_password(kr->pd->authtok, &pwd, NULL);
+ if (kerr != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
- "sss_authtok_get_password failed.\n");
- return ret;
+ "sss_authtok_get_password failed.\n");
+ goto done;
}
kerr = krb5_responder_set_answer(kctx, rctx,
- KRB5_RESPONDER_QUESTION_PASSWORD,
- pwd);
+ KRB5_RESPONDER_QUESTION_PASSWORD,
+ pwd);
if (kerr != 0) {
DEBUG(SSSDBG_OP_FAILURE,
- "krb5_responder_set_answer failed.\n");
+ "krb5_responder_set_answer failed.\n");
}
+ }
- return kerr;
+done:
+ return kerr;
+}
+
+static krb5_error_code sss_krb5_auth_methods_request(krb5_context ctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx,
+ const char * const *question_list,
+ struct sss_idp_oauth2 **_oath2_data)
+{
+ size_t c;
+ int count = 0;
+ krb5_error_code kerr = EINVAL;
+
+ if (kr->pd->cmd != SSS_PAM_PREAUTH) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Unexpected state [%d], skipping methods request\n",
+ kr->pd->cmd);
+ kerr = EOK;
+ goto done;
}
- /* For SSS_PAM_PREAUTH and the other remaining commands the caller should
- * continue to iterate over the available authentication methods. */
- return ERR_CHECK_NEXT_AUTH_TYPE;
+ for (c = 0; question_list[c] != NULL; c++) {
+ kerr = EINVAL;
+ DEBUG(SSSDBG_TRACE_ALL, "Got request [%s].\n", question_list[c]);
+
+ if (strcmp(question_list[c], KRB5_RESPONDER_QUESTION_PASSWORD) == 0) {
+ kerr = request_password(kr);
+ } else if (strcmp(question_list[c], KRB5_RESPONDER_QUESTION_PKINIT) == 0) {
+ kerr = request_pkinit(kr);
+ } else if (strcmp(question_list[c], SSSD_IDP_OAUTH2_QUESTION) == 0) {
+ kerr = request_idp_oauth2(ctx, kr, rctx, _oath2_data);
+ } else if (strcmp(question_list[c], SSSD_PASSKEY_QUESTION) == 0) {
+ kerr = request_passkey(ctx, kr, rctx);
+ } else if (strcmp(question_list[c], KRB5_RESPONDER_QUESTION_OTP) == 0) {
+ kerr = request_otp(ctx, kr, rctx);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unknown question type [%s]\n", question_list[c]);
+ kerr = EINVAL;
+ }
+
+ if (kerr == EOK) {
+ DEBUG(SSSDBG_TRACE_ALL, "Request found for %s\n", question_list[c]);
+ count++;
+ } else if (kerr == ENOENT) {
+ DEBUG(SSSDBG_TRACE_ALL, "Request not found for %s\n", question_list[c]);
+ }
+ }
+
+ if (count == 0 && (kerr != EOK && kerr != ENOENT)) {
+ DEBUG(SSSDBG_OP_FAILURE, "Authentication method request error\n");
+ goto done;
+ }
+
+ kerr = k5c_send_and_recv(kr);
+ if (kerr != EOK) {
+ goto done;
+ }
+
+done:
+ return kerr;
+}
+
+static krb5_error_code sss_krb5_auth_methods_answer(krb5_context ctx,
+ struct krb5_req *kr,
+ krb5_responder_context rctx,
+ const char * const *question_list,
+ struct sss_idp_oauth2 *oath2_data)
+{
+ size_t c;
+ krb5_error_code kerr = EINVAL;
+
+ if (kr->pd->cmd != SSS_PAM_AUTHENTICATE
+ && kr->pd->cmd != SSS_PAM_CHAUTHTOK_PRELIM
+ && kr->pd->cmd != SSS_PAM_CHAUTHTOK) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Unexpected state [%d], skipping methods answer\n",
+ kr->pd->cmd);
+ kerr = EOK;
+ goto done;
+ }
+
+ for (c = 0; question_list[c] != NULL; c++) {
+ DEBUG(SSSDBG_TRACE_ALL, "Got question [%s].\n", question_list[c]);
+
+ if (strcmp(question_list[c],
+ KRB5_RESPONDER_QUESTION_PASSWORD) == 0) {
+ kerr = answer_password(ctx, kr, rctx);
+ } else if (strcmp(question_list[c],
+ KRB5_RESPONDER_QUESTION_PKINIT) == 0) {
+ /* Skip answer_pkinit for expired password changes, e.g. user with auth types
+ * passkey AND password set */
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ continue;
+ }
+ kerr = answer_pkinit(ctx, kr, rctx);
+ } else if (strcmp(question_list[c], SSSD_IDP_OAUTH2_QUESTION) == 0) {
+ kerr = answer_idp_oauth2(ctx, kr, rctx, oath2_data);
+ } else if (strcmp(question_list[c], SSSD_PASSKEY_QUESTION) == 0) {
+ /* Skip answer_passkey for expired password changes, e.g. user with auth types
+ * passkey AND password set */
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ continue;
+ }
+ kerr = answer_passkey(ctx, kr, rctx);
+ } else if (strcmp(question_list[c], KRB5_RESPONDER_QUESTION_OTP) == 0) {
+ kerr = answer_otp(ctx, kr, rctx);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Unknown question type [%s]\n", question_list[c]);
+ kerr = EINVAL;
+ }
+
+ /* Continue to the next question when the given authtype cannot be
+ * handled by the answer_* function. This allows fallback between auth
+ * types, such as passkey -> password. */
+ if (kerr == ERR_CHECK_NEXT_AUTH_TYPE) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "Auth type [%s] could not be handled by answer "
+ "function, continuing to next question.\n",
+ question_list[c]);
+ continue;
+ } else {
+ goto done;
+ }
+ }
+
+done:
+ return kerr;
}
static krb5_error_code sss_krb5_responder(krb5_context ctx,
@@ -1244,8 +1441,8 @@ static krb5_error_code sss_krb5_responder(krb5_context ctx,
krb5_responder_context rctx)
{
struct krb5_req *kr = talloc_get_type(data, struct krb5_req);
+ struct sss_idp_oauth2 *oath2_data = NULL;
const char * const *question_list;
- size_t c;
krb5_error_code kerr = EINVAL;
if (kr == NULL) {
@@ -1255,80 +1452,22 @@ static krb5_error_code sss_krb5_responder(krb5_context ctx,
question_list = krb5_responder_list_questions(ctx, rctx);
if (question_list != NULL) {
- for (c = 0; question_list[c] != NULL; c++) {
- DEBUG(SSSDBG_TRACE_ALL, "Got question [%s].\n", question_list[c]);
-
- /* It is expected that the answer_*() functions only return EOK
- * (success) if the authentication was successful, i.e. during
- * SSS_PAM_AUTHENTICATE. In all other cases, e.g. during
- * SSS_PAM_PREAUTH either ERR_CHECK_NEXT_AUTH_TYPE should be
- * returned to indicate that the other available authentication
- * methods should be checked as well. Or some other error code to
- * indicate a fatal error where no other methods should be tried.
- * Especially if setting the answer failed neither EOK nor
- * ERR_CHECK_NEXT_AUTH_TYPE should be returned. */
- if (strcmp(question_list[c],
- KRB5_RESPONDER_QUESTION_PASSWORD) == 0) {
- kerr = answer_password(ctx, kr, rctx);
- } else if (strcmp(question_list[c],
- KRB5_RESPONDER_QUESTION_PKINIT) == 0
- && (sss_authtok_get_type(kr->pd->authtok)
- == SSS_AUTHTOK_TYPE_SC_PIN
- || sss_authtok_get_type(kr->pd->authtok)
- == SSS_AUTHTOK_TYPE_SC_KEYPAD)) {
- kerr = answer_pkinit(ctx, kr, rctx);
- } else if (strcmp(question_list[c], SSSD_IDP_OAUTH2_QUESTION) == 0) {
- kerr = answer_idp_oauth2(ctx, kr, rctx);
- } else if (strcmp(question_list[c], SSSD_PASSKEY_QUESTION) == 0) {
- /* Skip answer_passkey for expired password changes, e.g. user with auth types
- * passkey AND password set */
- if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM || kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
- continue;
- }
- kerr = answer_passkey(ctx, kr, rctx);
- } else if (strcmp(question_list[c], KRB5_RESPONDER_QUESTION_OTP) == 0) {
- kerr = answer_otp(ctx, kr, rctx);
- } else {
- DEBUG(SSSDBG_MINOR_FAILURE, "Unknown question type [%s]\n", question_list[c]);
- kerr = EINVAL;
- }
-
- /* Continue to the next question when the given authtype cannot be
- * handled by the answer_* function. This allows fallback between auth
- * types, such as passkey -> password. */
- if (kerr == ERR_CHECK_NEXT_AUTH_TYPE) {
- /* During pre-auth iterating over all authentication methods
- * is expected and no message will be displayed. */
- if (kr->pd->cmd == SSS_PAM_AUTHENTICATE) {
- DEBUG(SSSDBG_TRACE_ALL,
- "Auth type [%s] could not be handled by answer "
- "function, continuing to next question.\n",
- question_list[c]);
- }
- continue;
- } else {
- return kerr;
- }
+ kerr = sss_krb5_auth_methods_request(ctx, kr, rctx, question_list, &oath2_data);
+ if (kerr != EOK) {
+ goto done;
+ }
+ kerr = sss_krb5_auth_methods_answer(ctx, kr, rctx, question_list, oath2_data);
+ if (kerr != EOK) {
+ goto done;
}
} else {
kerr = answer_password(ctx, kr, rctx);
}
- /* During SSS_PAM_PREAUTH 'ERR_CHECK_NEXT_AUTH_TYPE' is expected because we
- * will run through all offered authentication methods and all are expect to
- * return 'ERR_CHECK_NEXT_AUTH_TYPE' in the positive case to indicate that
- * the other methods should be checked as well. If all methods are checked
- * we are done and should return success.
- * In the other steps, especially SSS_PAM_AUTHENTICATE, having
- * 'ERR_CHECK_NEXT_AUTH_TYPE' at this stage would mean that no method feels
- * responsible for the provided credentials i.e. authentication failed and
- * we should return an error.
- */
- if (kr->pd->cmd == SSS_PAM_PREAUTH) {
- return kerr == ERR_CHECK_NEXT_AUTH_TYPE ? 0 : kerr;
- } else {
- return kerr;
- }
+done:
+ sss_idp_oauth2_free(oath2_data);
+
+ return kerr;
}
#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER */
--
2.54.0
From e6956137b7ba64f3da41adb504b9c70c7a93994c Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Fri, 7 Nov 2025 09:38:29 +0100
Subject: [PATCH 26/36] Responder: add `gdm-switchable-auth` to
`pam_p11_allowed_services` defaults
The `pam_p11_allowed_services` option now includes `gdm-switchable-auth`
as one of the default allowed PAM services for smartcard authentication.
The service was added alongside the other GDM-related services
(gdm-smartcard and gdm-password) for logical grouping.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/man/sssd.conf.5.xml | 5 +++++
src/responder/pam/pamsrv_p11.c | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index a4829366a..70c99da30 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -1849,6 +1849,11 @@ pam_p11_allowed_services = +my_pam_service, -login
gdm-password
</para>
</listitem>
+ <listitem>
+ <para>
+ gdm-switchable-auth
+ </para>
+ </listitem>
<listitem>
<para>
kdm
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
index 1490ca28d..cd41bf3e9 100644
--- a/src/responder/pam/pamsrv_p11.c
+++ b/src/responder/pam/pamsrv_p11.c
@@ -276,7 +276,7 @@ static errno_t get_sc_services(TALLOC_CTX *mem_ctx, struct pam_ctx *pctx,
const char *default_sc_services[] = {
"login", "su", "su-l", "gdm-smartcard", "gdm-password", "kdm", "sudo",
- "sudo-i", "gnome-screensaver", "polkit-1", NULL,
+ "sudo-i", "gnome-screensaver", "gdm-switchable-auth", "polkit-1", NULL,
};
const int default_sc_services_size =
sizeof(default_sc_services) / sizeof(default_sc_services[0]);
--
2.54.0
From 03ebcb71fa69e15d1724d8f4a6ac8fbbe0f36a9d Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Wed, 12 Nov 2025 14:26:27 +0100
Subject: [PATCH 27/36] sss_client: prevent JSON auth during password change
preauth
When a user's password expires after successful JSON authentication, the
fallback to traditional password change fails. Add
PAM_CLI_FLAGS_CHAUTHTOK_PREAUTH flag to distinguish password change
preauth from normal authentication preauth. When this flag is set, the
PAM responder skips JSON message generation and returns traditional
preauth data instead.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_cmd.c | 4 ++--
src/sss_client/pam_sss.c | 2 ++
src/sss_client/sss_cli.h | 2 ++
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 6b45c47c7..c09c9b987 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1566,8 +1566,8 @@ void pam_reply(struct pam_auth_req *preq)
#endif /* BUILD_PASSKEY */
#ifdef HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION
- if (is_pam_json_enabled(pctx->json_services,
- pd->service)) {
+ if (is_pam_json_enabled(pctx->json_services, pd->service) &&
+ !(pd->cli_flags & PAM_CLI_FLAGS_CHAUTHTOK_PREAUTH)) {
ret = generate_json_auth_message(pctx->rctx->cdb, pc_list, pd);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index ba25959d5..8d104f4fd 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -3156,6 +3156,8 @@ static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh,
&& (pi.pam_authtok == NULL
|| (flags & PAM_CLI_FLAGS_PROMPT_ALWAYS))
&& access(PAM_PREAUTH_INDICATOR, F_OK) == 0) {
+ /* Set flag to indicate this preauth is for password change */
+ pi.flags |= PAM_CLI_FLAGS_CHAUTHTOK_PREAUTH;
pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH,
quiet_mode);
if (pam_status != PAM_SUCCESS) {
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 4e3c58c31..cadf9be07 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -429,6 +429,8 @@ enum pam_item_type {
#define PAM_CLI_FLAGS_PROMPT_ALWAYS (1 << 7)
#define PAM_CLI_FLAGS_TRY_CERT_AUTH (1 << 8)
#define PAM_CLI_FLAGS_REQUIRE_CERT_AUTH (1 << 9)
+#define PAM_CLI_FLAGS_ALLOW_CHAUTHTOK_BY_ROOT (1 << 10)
+#define PAM_CLI_FLAGS_CHAUTHTOK_PREAUTH (1 << 11)
#define SSS_NSS_MAX_ENTRIES 256
#define SSS_NSS_HEADER_SIZE (sizeof(uint32_t) * 4)
--
2.54.0
From d87b3fd6eedf2e142e8ced3fdd1b101a4b397543 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Thu, 13 Nov 2025 16:17:05 +0100
Subject: [PATCH 28/36] Responder: change authentication mechanism detection
Use `pam_get_auth_types()` to detect the available mechanisms for a
user.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/responder/pam/pamsrv_json.c | 49 +++++++++++++++++------------
src/tests/cmocka/test_pamsrv_json.c | 4 +++
2 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c
index 37d3c276a..7f7e03bf3 100644
--- a/src/responder/pam/pamsrv_json.c
+++ b/src/responder/pam/pamsrv_json.c
@@ -511,6 +511,7 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
struct auth_data **_auth_data)
{
struct cert_auth_info *cert_list = NULL;
+ struct pam_resp_auth_type types;
errno_t ret = EOK;
*_auth_data = talloc_zero(mem_ctx, struct auth_data);
@@ -526,7 +527,6 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
ret = ENOMEM;
goto done;
}
- (*_auth_data)->pswd->enabled = true;
(*_auth_data)->oauth2 = talloc_zero(mem_ctx, struct oauth2_data);
if ((*_auth_data)->oauth2 == NULL) {
@@ -534,7 +534,6 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
ret = ENOMEM;
goto done;
}
- (*_auth_data)->oauth2->enabled = true;
(*_auth_data)->sc = talloc_zero(mem_ctx, struct sc_data);
if ((*_auth_data)->sc == NULL) {
@@ -542,7 +541,6 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
ret = ENOMEM;
goto done;
}
- (*_auth_data)->sc->enabled = true;
(*_auth_data)->passkey = talloc_zero(mem_ctx, struct passkey_data);
if ((*_auth_data)->passkey == NULL) {
@@ -550,7 +548,16 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
ret = ENOMEM;
goto done;
}
- (*_auth_data)->passkey->enabled = true;
+
+ ret = pam_get_auth_types(pd, &types);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n");
+ goto done;
+ }
+ (*_auth_data)->pswd->enabled = types.password_auth;
+ (*_auth_data)->oauth2->enabled = true;
+ (*_auth_data)->sc->enabled = types.cert_auth;
+ (*_auth_data)->passkey->enabled = types.passkey_auth;
ret = obtain_prompts(cdb, mem_ctx, pc_list, *_auth_data);
if (ret != EOK) {
@@ -566,26 +573,28 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb,
goto done;
}
- ret = get_cert_list(mem_ctx, pd, &cert_list);
- if (ret == ENOENT) {
- (*_auth_data)->sc->enabled = false;
- } else if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE,
- "Failure to obtain smartcard certificate list.\n");
- goto done;
- }
+ if ((*_auth_data)->sc->enabled) {
+ ret = get_cert_list(mem_ctx, pd, &cert_list);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failure to obtain smartcard certificate list.\n");
+ goto done;
+ }
- ret = get_cert_data(mem_ctx, cert_list, *_auth_data);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain smartcard labels.\n");
- goto done;
+ ret = get_cert_data(mem_ctx, cert_list, *_auth_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain smartcard labels.\n");
+ goto done;
+ }
}
#ifdef BUILD_PASSKEY
- ret = obtain_passkey_data(mem_ctx, pd, *_auth_data);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain passkey data.\n");
- goto done;
+ if ((*_auth_data)->passkey->enabled) {
+ ret = obtain_passkey_data(mem_ctx, pd, *_auth_data);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failure to obtain passkey data.\n");
+ goto done;
+ }
}
#else
(*_auth_data)->passkey->enabled = false;
diff --git a/src/tests/cmocka/test_pamsrv_json.c b/src/tests/cmocka/test_pamsrv_json.c
index d432ea065..0430d2651 100644
--- a/src/tests/cmocka/test_pamsrv_json.c
+++ b/src/tests/cmocka/test_pamsrv_json.c
@@ -775,6 +775,8 @@ void test_generate_json_message_integration(void **state)
pd = talloc_zero(test_ctx, struct pam_data);
assert_non_null(pd);
+ ret = pam_add_response(pd, SSS_PASSWORD_PROMPTING, 0, NULL);
+ assert_int_equal(ret, EOK);
len = strlen(OAUTH2_URI)+1+strlen(OAUTH2_URI_COMP)+1+strlen(OAUTH2_CODE)+1;
ret = pam_add_response(pd, SSS_PAM_OAUTH2_INFO, len,
discard_const(OAUTH2_STR));
@@ -789,6 +791,8 @@ void test_generate_json_message_integration(void **state)
strlen(SC2_PROMPT_STR)+1+strlen(SC2_PAM_CERT_USER)+1;
ret = pam_add_response(pd, SSS_PAM_CERT_INFO, len, discard_const(SC2_STR));
assert_int_equal(ret, EOK);
+ ret = pam_add_response(pd, SSS_CERT_AUTH_PROMPTING, 0, NULL);
+ assert_int_equal(ret, EOK);
len = strlen(prompt_pin)+1;
ret = pam_add_response(pd, SSS_PAM_PASSKEY_INFO, len,
discard_const(prompt_pin));
--
2.54.0
From 8395c756ebfabc631fb21836d269b748cd1723d4 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Tue, 18 Nov 2025 16:08:15 +0100
Subject: [PATCH 29/36] man: clarify and fix `pam_json_services` compilation
Add a note to clarify that 2FA isn't supported in JSON protocol and fix
man page compilation for `pam_json_services` option.
:feature: Unified passwordless login in the GUI. SSSD now supports a
rich authentication selection interface. Users can login with
smartcards, passkey, External IdPs and passwords directly
within the graphical user interface.
:packaging: SSSD now supports authentication mechanism selection through
PAM using a JSON-based protocol. This feature enables
passwordless authentication mechanisms in GUI login
environments that support the protocol.
Feature will be supported by GNOME Display Manager (GDM)
starting with GNOME 50. While currently optimized for GNOME,
the JSON protocol design allows for future support in other
display managers.
authselect is the recommended approach and will handle the
necessary PAM stack modifications automatically starting
with version 1.7 through the new option `with-switch-auth`
which provides a new PAM service called `switchable-auth`.
Manual PAM configuration is also possible.
For more technical details and implementation specifications,
see the design documentation:
https://github.com/SSSD/sssd.io/pull/79
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/man/Makefile.am | 2 +-
src/man/sssd.conf.5.xml | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/man/Makefile.am b/src/man/Makefile.am
index 876561289..012194180 100644
--- a/src/man/Makefile.am
+++ b/src/man/Makefile.am
@@ -72,7 +72,7 @@ JSON_PAM_CONDS = ;build_json_pam
endif
-CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SYSTEMD_CONDS)$(KCM_CONDS)$(STAP_CONDS)$(KCM_RENEWAL_CONDS)$(LOCKFREE_CLIENT_CONDS)$(HAVE_INOTIFY_CONDS)$(SUBID_CONDS)$(PASSKEY_CONDS)$(FILES_PROVIDER_CONDS)$(SSSD_NON_ROOT_USER_CONDS)$(LIBNL_CONDS)$(SAMBA_CONDS)
+CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SYSTEMD_CONDS)$(KCM_CONDS)$(STAP_CONDS)$(KCM_RENEWAL_CONDS)$(LOCKFREE_CLIENT_CONDS)$(HAVE_INOTIFY_CONDS)$(SUBID_CONDS)$(PASSKEY_CONDS)$(FILES_PROVIDER_CONDS)$(SSSD_NON_ROOT_USER_CONDS)$(LIBNL_CONDS)$(SAMBA_CONDS)$(JSON_PAM_CONDS)
#Special Rules:
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 70c99da30..d817a6b11 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -2131,6 +2131,11 @@ pam_json_services = gdm-switchable-auth
<para>
Default: - (JSON protocol is disabled)
</para>
+ <para>
+ Note: 2-Factor Authentication (2FA) is not
+ supported. If 2FA is required, do not
+ activate the JSON protocol.
+ </para>
</listitem>
</varlistentry>
</variablelist>
--
2.54.0
From c354593c4ad5d2032215639f7bd7903c5e4602bd Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Thu, 20 Nov 2025 16:33:41 +0100
Subject: [PATCH 30/36] krb5: port pre-authentication retry logic
Port the pre-authentication retry logic from the IPA provider to the
krb5 provider, making it available to all krb5-based authentication
flows.
Relates: 6c1272edf1 ("krb5: Add fallback password change support")
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
---
src/providers/krb5/krb5_auth.c | 50 ++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index fb2f58869..0eacb5523 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -1282,10 +1282,14 @@ int krb5_auth_recv(struct tevent_req *req, int *pam_status, int *dp_err)
}
struct krb5_pam_handler_state {
+ struct tevent_context *ev;
+ struct be_ctx *be_ctx;
struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
};
static void krb5_pam_handler_auth_done(struct tevent_req *subreq);
+static void krb5_pam_handler_auth_retry_done(struct tevent_req *subreq);
static void krb5_pam_handler_access_done(struct tevent_req *subreq);
struct tevent_req *
@@ -1305,7 +1309,10 @@ krb5_pam_handler_send(TALLOC_CTX *mem_ctx,
return NULL;
}
+ state->ev = params->ev;
+ state->be_ctx = params->be_ctx;
state->pd = pd;
+ state->krb5_ctx = krb5_ctx;
switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
@@ -1372,6 +1379,49 @@ static void krb5_pam_handler_auth_done(struct tevent_req *subreq)
state->pd->pam_status = PAM_SYSTEM_ERR;
}
+ if (state->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM
+ && state->pd->pam_status == PAM_TRY_AGAIN) {
+ /* Reset this to fork a new krb5_child in handle_child_send() */
+ state->pd->child_pid = 0;
+ subreq = krb5_auth_queue_send(state, state->ev, state->be_ctx, state->pd,
+ state->krb5_ctx);
+ if (subreq == NULL) {
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, krb5_pam_handler_auth_retry_done, req);
+ return;
+ }
+
+ /* PAM_CRED_ERR is used to indicate to the IPA provider that trying
+ * password migration would make sense. From this point on it isn't
+ * necessary to keep this status, so it can be translated to PAM_AUTH_ERR.
+ */
+ if (state->pd->pam_status == PAM_CRED_ERR) {
+ state->pd->pam_status = PAM_AUTH_ERR;
+ }
+
+done:
+ /* TODO For backward compatibility we always return EOK to DP now. */
+ tevent_req_done(req);
+}
+
+static void krb5_pam_handler_auth_retry_done(struct tevent_req *subreq)
+{
+ struct krb5_pam_handler_state *state;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct krb5_pam_handler_state);
+
+ ret = krb5_auth_queue_recv(subreq, &state->pd->pam_status, NULL);
+ talloc_free(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "krb5_auth_recv request failed.\n");
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
/* PAM_CRED_ERR is used to indicate to the IPA provider that trying
* password migration would make sense. From this point on it isn't
* necessary to keep this status, so it can be translated to PAM_AUTH_ERR.
--
2.54.0
From 314233a2ebfe75f12f34e7a953a526949cca6f3f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Thu, 11 Dec 2025 11:03:17 +0100
Subject: [PATCH 31/36] krb5: fix OTP authentication
Resolves: https://github.com/SSSD/sssd/issues/8292
Reviewed-by: Justin Stephenson <jstephen@redhat.com>
(cherry picked from commit 60ba493e9664caecd9496901e8b9639c874b04ee)
---
src/providers/krb5/krb5_child.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 15bef2d00..ddb8b6f46 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -584,6 +584,8 @@ static krb5_error_code request_otp(krb5_context ctx,
goto done;
}
+ kr->otp = true;
+
for (i = 0; chl->tokeninfo[i] != NULL; i++) {
DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
i, chl->tokeninfo[i]->vendor);
@@ -607,12 +609,9 @@ static krb5_error_code request_otp(krb5_context ctx,
/* Allocation errors are ignored on purpose */
DEBUG(SSSDBG_TRACE_ALL, "Setting otp prompting.\n");
- if (kr->otp) {
- kerr = k5c_attach_otp_info_msg(kr);
- if (kerr != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE,
- "Failed to add otp prompting data.\n");
- }
+ kerr = k5c_attach_otp_info_msg(kr);
+ if (kerr != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add otp prompting data.\n");
}
done:
--
2.54.0
From 08b512d85af642a7735741bb660f0902d4d21dd5 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Thu, 11 Dec 2025 12:25:00 +0100
Subject: [PATCH 32/36] krb5_child: fix OTP authentication for PAM stacked
tokens
The `tokeninfo_matches()` function already handles PAM stacked tokens
correctly by processing them through the 2FA single path, so the
`answer_otp()` function should allow this token type to proceed.
Add SSS_AUTHTOK_TYPE_PAM_STACKED to the allowed authentication token
types in `answer_otp()` to restore previous functionality.
Fixes: 4cb99a248 ("krb5_child: advertise authentication methods").
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Justin Stephenson <jstephen@redhat.com>
(cherry picked from commit df15165db43eeb380c2c2af0eb4492c647c266d5)
---
src/providers/krb5/krb5_child.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index ddb8b6f46..d74ea0745 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -631,7 +631,8 @@ static krb5_error_code answer_otp(krb5_context ctx,
type = sss_authtok_get_type(kr->pd->authtok);
if (type != SSS_AUTHTOK_TYPE_2FA_SINGLE
- && type != SSS_AUTHTOK_TYPE_2FA) {
+ && type != SSS_AUTHTOK_TYPE_2FA
+ && type != SSS_AUTHTOK_TYPE_PAM_STACKED) {
DEBUG(SSSDBG_MINOR_FAILURE, "Unexpected authentication token type [%s]\n",
sss_authtok_type_to_str(type));
return ERR_CHECK_NEXT_AUTH_TYPE;
--
2.54.0
From fb2a7d0479457ecc173e58c187c28169e1df2d27 Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Fri, 9 Jan 2026 13:33:42 +0100
Subject: [PATCH 33/36] KRB5_CHILD: allow `k5c_ccache_setup()` during
SSS_PAM_PREAUTH
This should cover a case when a single execution of 'krb5_child'
handles both PREAUTH and AUTH
Resolves: https://github.com/SSSD/sssd/issues/8331
Reviewed-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Sumit Bose <sbose@redhat.com>
(cherry picked from commit e2273e09a8e539fdb156bd1aba56c2d4210a7ad3)
---
src/providers/krb5/krb5_child.c | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index d74ea0745..23808a397 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -864,7 +864,10 @@ static errno_t krb5_req_update(struct krb5_req *dest, struct krb5_req *src)
/* Check request validity. This should never happen, but it is better to
* be little paranoid. */
if (strcmp(dest->ccname, src->ccname) != 0) {
- return EINVAL;
+ /* Let's check if 'old_ccname' was reused during PREAUTH */
+ if (!src->old_ccname || (strcmp(dest->ccname, src->old_ccname) != 0)) {
+ return EINVAL;
+ }
}
if (strcmp(dest->upn, src->upn) != 0) {
@@ -4205,13 +4208,11 @@ static krb5_error_code privileged_krb5_setup(struct krb5_req *kr,
}
/* For ccache types FILE: and DIR: we might need to create some directory
- * components as root. Cache files are not needed during preauth. */
- if (kr->pd->cmd != SSS_PAM_PREAUTH) {
- ret = k5c_ccache_setup(kr, offline);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
- return ret;
- }
+ * components as root. */
+ ret = k5c_ccache_setup(kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup() failed.\n");
+ return ret;
}
if (!(offline ||
--
2.54.0
From abbd851d2a5e776bcc4d6978731079ecfb7d5c68 Mon Sep 17 00:00:00 2001
From: Iker Pedrosa <ipedrosa@redhat.com>
Date: Mon, 12 Jan 2026 11:47:17 +0100
Subject: [PATCH 34/36] krb5_child: fix enterprise principal parsing in
keep-alive sessions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When keep-alive sessions transition between command types (e.g., from
SSS_PAM_PREAUTH to SSS_PAM_AUTHENTICATE), enterprise principal settings
were not being updated, causing parsing inconsistencies in complex AD
environments.
This change ensures that when the backend sends updated enterprise
principal settings for different command types, the principals are
correctly re-parsed with the appropriate flags, fixing UPN handling in
multi-domain AD environments.
Signed-off-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Alejandro López <allopez@redhat.com>
Reviewed-by: Sumit Bose <sbose@redhat.com>
(cherry picked from commit dd3cd958d5b06e2429921ae46feeddfe137a1f7e)
---
src/providers/krb5/krb5_child.c | 51 +++++++++++++++++++++++++++------
1 file changed, 42 insertions(+), 9 deletions(-)
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 23808a397..d485f263b 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -144,6 +144,7 @@ static errno_t k5c_attach_passkey_msg(struct krb5_req *kr, struct sss_passkey_ch
static errno_t k5c_attach_keep_alive_msg(struct krb5_req *kr);
static errno_t k5c_recv_data(struct krb5_req *kr, int fd, uint32_t *offline);
static errno_t k5c_send_data(struct krb5_req *kr, int fd, errno_t error);
+static int k5c_setup(struct krb5_req *kr, uint32_t offline);
static errno_t k5c_become_user(uid_t uid, gid_t gid, bool is_posix)
{
@@ -882,6 +883,12 @@ static errno_t krb5_req_update(struct krb5_req *dest, struct krb5_req *src)
talloc_free(dest->pd);
dest->pd = talloc_steal(dest, src->pd);
+ /* Update settings that may change between commands */
+ dest->use_enterprise_princ = src->use_enterprise_princ;
+ dest->validate = src->validate;
+ dest->posix_domain = src->posix_domain;
+ dest->send_pac = src->send_pac;
+
return EOK;
}
@@ -949,6 +956,13 @@ static krb5_error_code k5c_send_and_recv(struct krb5_req *kr)
goto done;
}
+ ret = k5c_setup(kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_setup failed during keep-alive [%d]: %s\n",
+ ret, sss_strerror(ret));
+ goto done;
+ }
+
done:
talloc_free(tmpkr);
return ret;
@@ -4037,6 +4051,7 @@ static int k5c_ccache_setup(struct krb5_req *kr, uint32_t offline)
static int k5c_setup(struct krb5_req *kr, uint32_t offline)
{
+ krb5_principal princ;
krb5_error_code kerr;
int parse_flags;
@@ -4064,28 +4079,46 @@ static int k5c_setup(struct krb5_req *kr, uint32_t offline)
}
parse_flags = kr->use_enterprise_princ ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
- kerr = sss_krb5_parse_name_flags(kr->ctx, kr->upn, parse_flags, &kr->princ);
+ kerr = sss_krb5_parse_name_flags(kr->ctx, kr->upn, parse_flags, &princ);
if (kerr != 0) {
KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
return kerr;
}
+ if (kr->princ == NULL || !krb5_principal_compare(kr->ctx, kr->princ, princ)) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Updating principal\n");
+ if (kr->princ != NULL) {
+ krb5_free_principal(kr->ctx, kr->princ);
+ }
+ kr->princ = princ;
+ } else {
+ DEBUG(SSSDBG_TRACE_FUNC, "Principal unchanged, keeping existing\n");
+ krb5_free_principal(kr->ctx, princ);
+ }
- kerr = krb5_parse_name(kr->ctx, kr->upn, &kr->princ_orig);
- if (kerr != 0) {
- KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
- return kerr;
+ if (kr->princ_orig == NULL) {
+ kerr = krb5_parse_name(kr->ctx, kr->upn, &kr->princ_orig);
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
}
+ sss_krb5_free_unparsed_name(kr->ctx, kr->name);
+ kr->name = NULL;
kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name);
if (kerr != 0) {
KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
return kerr;
}
- kr->creds = calloc(1, sizeof(krb5_creds));
- if (kr->creds == NULL) {
- DEBUG(SSSDBG_CRIT_FAILURE, "calloc failed.\n");
- return ENOMEM;
+ if (kr->creds != NULL) {
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ } else {
+ kr->creds = calloc(1, sizeof(krb5_creds));
+ if (kr->creds == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "calloc failed.\n");
+ return ENOMEM;
+ }
}
#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_RESPONDER
--
2.54.0
From f7ed80de34e389c8587d8d8a24c19a13ae38f692 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Wed, 22 Apr 2026 12:33:17 +0200
Subject: [PATCH 35/36] krb5: restart krb5_child for Smartcard authentication
In contrast to other authentication methods for PKINIT some information
about the used Smartcard and certificate are already needed for the
pre-authentication step to trigger the MIT Kerberos PKINIT module to get
back the information if PKINIT is possible or not and if the Smartcard
can be used for authentication. If krb5_child is kept running between
the pre-authentication and the authentication step the information given
during pre-authentication is used if Smartcard authentication was
selected.
As long as only a single certificate is available there is no issue. But
if there are multiple certificates which all apply to the given mapping
and matching rules for the user trying to log in and the user can choose
a certificate for authentication the authentication might fail if the
certificate use during pre-authentication and the one selected by the
user differ. Before the change to keep krb5_child running for all
authentication methods this was not an issue since the fresh instance
started during the authentication step was using the certificate
selected by the user.
With this patch krb5_child is restart during the authentication step is
Smartcard authentication was selected.
Reviewed-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Justin Stephenson <jstephen@redhat.com>
(cherry picked from commit f3a36bec2a6c9fe11076c8f4673775a0d4221ad1)
---
src/providers/krb5/krb5_auth.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 0eacb5523..ac02dfbd7 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -866,6 +866,14 @@ static void krb5_auth_resolve_done(struct tevent_req *subreq)
kr->is_offline = false;
}
+ /* Restart krb5_child for Smartcard authentication in case a different
+ * certificate was selected by the user */
+ if (kr->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(kr->pd->authtok)
+ && kr->pd->child_pid != 0) {
+ soft_terminate_krb5_child(state, kr->pd, kr->krb5_ctx);
+ kr->pd->child_pid = 0;
+ }
+
subreq = handle_child_send(state, state->ev, kr);
if (subreq == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "handle_child_send failed.\n");
--
2.54.0
From 8b960460475ad4dbe10ab6306ed1c81d4a9441db Mon Sep 17 00:00:00 2001
From: Paul Adelsbach <paul.adelsbach@wolfssl.com>
Date: Tue, 21 Apr 2026 09:30:51 -0700
Subject: [PATCH 36/36] pam: gate PAC indicator code on BUILD_SAMBA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Commit 1f680edad023c8c57343447b156f6b34696e8221 added ad_pac_common.c and
$(NDR_KRB5PAC_LIBS) to sssd_pam unconditionally. So when building --without-samba, sssd_pam fails to link with undefined references to ndr_pull_init_blob and ndr_pull_PAC_DATA.
This change qualifies those additions with `BUILD_SAMBA` so the PAC
indicator feature is compiled in only when samba support is enabled.
Reviewed-by: Sumit Bose <sbose@redhat.com>
Reviewed-by: Tomáš Halman <thalman@redhat.com>
(cherry picked from commit d0beceaa17b94fa07a2ee7af8165392b90e83d94)
---
Makefile.am | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Makefile.am b/Makefile.am
index e052c3079..962ccca49 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2716,10 +2716,16 @@ test_pamsrv_json_SOURCES = \
if BUILD_PASSKEY
test_pamsrv_json_SOURCES += src/responder/pam/pamsrv_passkey.c
endif # BUILD_PASSKEY
+if BUILD_SAMBA
+ test_pamsrv_json_SOURCES += src/providers/ad/ad_pac_common.c
+endif
test_pamsrv_json_CFLAGS = \
$(AM_CFLAGS) \
$(CMOCKA_CFLAGS) \
$(NULL)
+if BUILD_SAMBA
+test_pamsrv_json_CFLAGS += $(NDR_KRB5PAC_CFLAGS)
+endif
test_pamsrv_json_LDFLAGS = \
-Wl,-wrap,json_array_append_new \
$(NULL)
@@ -2738,6 +2744,9 @@ test_pamsrv_json_LDADD = \
libsss_iface.la \
libsss_sbus.la \
$(NULL)
+if BUILD_SAMBA
+test_pamsrv_json_LDADD += $(NDR_KRB5PAC_LIBS)
+endif
test_sss_pam_data_SOURCES = \
src/util/sss_pam_data.c \
--
2.54.0