diff --git a/.gitignore b/.gitignore index 121439a..9fa3380 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ sssd-1.2.91.tar.gz /sssd-2.9.6.tar.gz /sssd-2.9.7.tar.gz /sssd-2.9.8.tar.gz +/sssd-2.9.9.tar.gz diff --git a/0001-do-not-require-GID-for-non-POSIX-group.patch b/0001-do-not-require-GID-for-non-POSIX-group.patch deleted file mode 100644 index bb5601b..0000000 --- a/0001-do-not-require-GID-for-non-POSIX-group.patch +++ /dev/null @@ -1,81 +0,0 @@ -Based on 57918755aa87a943ff451bfde6794da513e71d8d commit -From: Sumit Bose -Date: Mon, 9 Feb 2026 14:10:29 +0100 -Subject: [PATCH] sdap: do not require GID for non-POSIX group - -diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c -index 6d0c7e49907..9e0eaf8a4d6 100644 ---- a/src/providers/ldap/sdap_async_groups.c -+++ b/src/providers/ldap/sdap_async_groups.c -@@ -620,15 +620,17 @@ static int sdap_save_group(TALLOC_CTX *memctx, - goto done; - } - -- ret = sysdb_attrs_get_uint32_t(attrs, -- opts->group_map[SDAP_AT_GROUP_GID].sys_name, -- &gid); -- if (ret != EOK) { -- DEBUG(SSSDBG_CRIT_FAILURE, -- "no gid provided for [%s] in domain [%s].\n", -- group_name, dom->name); -- ret = EINVAL; -- goto done; -+ if (posix_group) { -+ ret = sysdb_attrs_get_uint32_t(attrs, -+ opts->group_map[SDAP_AT_GROUP_GID].sys_name, -+ &gid); -+ if (ret != EOK) { -+ DEBUG(SSSDBG_CRIT_FAILURE, -+ "no gid provided for [%s] in domain [%s].\n", -+ group_name, dom->name); -+ ret = EINVAL; -+ goto done; -+ } - } - } - } -diff --git a/src/tests/tests/system/tests/test_identity.py b/src/tests/tests/system/tests/test_identity.py -index 3e38637b0a5..68894958fe7 100644 ---- a/src/tests/tests/system/tests/test_identity.py -+++ b/src/tests/tests/system/tests/test_identity.py -@@ -718,3 +718,40 @@ def test_identity__filter_groups_by_name_and_lookup_by_gid(client: Client, ldap: - - result = client.tools.getent.group(20001) - assert result is None, "Filtered group was found" -+ -+ -+@pytest.mark.importance("critical") -+@pytest.mark.topology(KnownTopologyGroup.AnyAD) -+def test_identity__nested_non_posix_group(client: Client, provider: GenericADProvider): -+ """ -+ :title: Lookup indirect group-members of a nested non-POSIX group -+ :setup: -+ 1. Add a new POSIX user and two new groups, one POSIX the other non-POSIX -+ 2. Add the user to the non-POSIX group and the non-POSIX group to the POSIX group -+ 3. Set 'ldap_id_mapping = false' to allow non-POSIX groups, because -+ with POSIX id-mapping enabled all groups will get POSIX ID and hence -+ there are no non-POSIX groups, and start SSSD -+ :steps: -+ 1. Lookup the POSIX group with getent -+ :expectedresults: -+ 1. Group is present and the new user is a member -+ :customerscenario: False -+ """ -+ user = provider.user("nesteduser").add( -+ uid=10001, gid=20001, password="Secret123", gecos="User for tests", shell="/bin/bash" -+ ) -+ nested_group = provider.group("nested_nonposix_group").add().add_member(user) -+ base_group = provider.group("posix_group").add(gid=30001).add_member(nested_group) -+ -+ client.sssd.domain["ldap_id_mapping"] = "false" -+ client.sssd.start() -+ -+ result = client.tools.getent.group(base_group.name) -+ assert result is not None, f"Group '{base_group.name}' not found!" -+ assert ( -+ len(result.members) == 1 -+ ), f"Group '{base_group.name}' has unexpected number of members [{len(result.members)}]!" -+ assert f"{user.name}" in result.members, f"Member '{user.name}' of group '{base_group.name}' not found!" -+ -+ result = client.tools.getent.group(nested_group.name) -+ assert result is None, f"Non-POSIX Group '{nested_group.name}' was found with 'getent group'!" diff --git a/0001-passwordless-gdm.patch b/0001-passwordless-gdm.patch new file mode 100644 index 0000000..5b48613 --- /dev/null +++ b/0001-passwordless-gdm.patch @@ -0,0 +1,9565 @@ +From 7d47dd3bd38d068d848d24fa36886173ce9568bf Mon Sep 17 00:00:00 2001 +From: Iker Pedrosa +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 +Signed-off-by: Ray Strode +--- + 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 ++ ++ 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 . ++*/ ++ ++#include ++ ++#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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 ++ ++ 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 . ++*/ ++ ++#include ++#include ++#include ++ ++#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 ++ ++ 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 . ++*/ ++ ++#ifndef __PAMSRV_JSON__H__ ++#define __PAMSRV_JSON__H__ ++ ++#include ++#include ++ ++#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 ++ ++ 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 . ++*/ ++ ++#include ++#include ++ ++#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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 + + + ++ ++ pam_json_services (string) ++ ++ ++ Comma separated list of PAM services which can ++ handle the JSON protocol for selecting ++ authentication mechanisms ++ ++ ++ To disable JSON protocol, set this option ++ to - (dash). ++ ++ ++ Example: ++ ++pam_json_services = gdm-switchable-auth ++ ++ ++ ++ Default: - (JSON protocol is disabled) ++ ++ ++ + + + +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 +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 +--- + 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 +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 +Signed-off-by: Ray Strode +--- + 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 + #endif + ++#ifdef HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION ++#include ++#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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 +Date: Mon, 22 Sep 2025 10:19:51 +0200 +Subject: [PATCH 13/36] Responder: update JSON message format + +Signed-off-by: Iker Pedrosa +--- + 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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 . + */ + ++#ifndef _GNU_SOURCE ++#define _GNU_SOURCE ++#endif ++ + #include + #include ++#include + #include + + #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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 + + #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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 + + ++ ++ ++ gdm-switchable-auth ++ ++ + + + 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 +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 +--- + 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 +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 +--- + 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 +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 +--- + 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 + + Default: - (JSON protocol is disabled) + ++ ++ Note: 2-Factor Authentication (2FA) is not ++ supported. If 2FA is required, do not ++ activate the JSON protocol. ++ + + + +-- +2.54.0 + + +From c354593c4ad5d2032215639f7bd7903c5e4602bd Mon Sep 17 00:00:00 2001 +From: Iker Pedrosa +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 +--- + 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 +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 +(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 +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 +Reviewed-by: Justin Stephenson +(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 +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 +Reviewed-by: Sumit Bose +(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 +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 +Reviewed-by: Alejandro López +Reviewed-by: Sumit Bose +(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 +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 +Reviewed-by: Justin Stephenson +(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 +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 +Reviewed-by: Tomáš Halman +(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 + diff --git a/0002-fix-use-after-free-in-kcm_read_options.patch b/0002-fix-use-after-free-in-kcm_read_options.patch deleted file mode 100644 index ba11482..0000000 --- a/0002-fix-use-after-free-in-kcm_read_options.patch +++ /dev/null @@ -1,16 +0,0 @@ -KCM: fix use-after-free in `kcm_read_options()` -Based on commit c5a2b48f13af893ae6c7c9fe63e41f64eb77cade - -diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c -index 39e9470fa22..32eccf4b48a 100644 ---- a/src/responder/kcm/kcm_renew.c -+++ b/src/responder/kcm/kcm_renew.c -@@ -228,7 +228,7 @@ static errno_t kcm_read_options(TALLOC_CTX *mem_ctx, - *_validate = validate; - *_canonicalize = canonicalize; - *_timeout = timeout; -- *_renew_intv = renew_intv; -+ *_renew_intv = talloc_steal(mem_ctx, renew_intv); - - ret = EOK; - diff --git a/0002-sysdb-enumpwent-replacement.patch b/0002-sysdb-enumpwent-replacement.patch new file mode 100644 index 0000000..f798f2a --- /dev/null +++ b/0002-sysdb-enumpwent-replacement.patch @@ -0,0 +1,13 @@ +diff --git a/src/providers/files/files_ops.c b/src/providers/files/files_ops.c +index 556d56d50..610bd6c91 100644 +--- a/src/providers/files/files_ops.c ++++ b/src/providers/files/files_ops.c +@@ -492,7 +492,7 @@ static const char **get_cached_user_names(TALLOC_CTX *mem_ctx, + const char **user_names = NULL; + unsigned c = 0; + +- ret = sysdb_enumpwent(mem_ctx, dom, &res); ++ ret = sysdb_enumpwent_filter(mem_ctx, dom, NULL, NULL, NULL, &res); + if (ret != EOK) { + goto done; + } diff --git a/0003-add-missing-include.patch b/0003-add-missing-include.patch deleted file mode 100644 index 5e83949..0000000 --- a/0003-add-missing-include.patch +++ /dev/null @@ -1,28 +0,0 @@ -commit ca662958218f4484a89be94015066ff6a14875a8 -Author: Alexey Tikhonov -Date: Wed Apr 15 09:42:34 2026 +0200 - - Add missing include - - Original patch f3af8c89af656767333410b0e94da9288dd8ade8 didn't include - "config.h" that provides `HAVE_PTHREAD_EXT` - It works in some branches accidentally because of transitive include - via "sss_cli.h" but that's fragile (and in some branches "sss_cli.h" - doesn't include "config.h") - - Reviewed-by: Tomáš Halman - (cherry picked from commit a809b9236250e6f20e9a9ff1452708cd288b705f) - -diff --git a/src/sss_client/autofs/sss_autofs.c b/src/sss_client/autofs/sss_autofs.c -index f5986767f..45e2ce460 100644 ---- a/src/sss_client/autofs/sss_autofs.c -+++ b/src/sss_client/autofs/sss_autofs.c -@@ -18,6 +18,8 @@ - along with this program. If not, see . - */ - -+#include "config.h" -+ - #include - #include - #include diff --git a/sources b/sources index fb1721c..9e021d0 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (sssd-2.9.8.tar.gz) = 9b10cb5e343d32402a437dab3304c16596e9eb7b51a452ca3e2b3fea4aa8dc879abe06a57ccc716bece8024847211abf5affa83e1d2ca2cac101132133a6619a +SHA512 (sssd-2.9.9.tar.gz) = 218d2f60bfa64d496c26df9d02ee950ed27aff46651ea2c85f123ae7f642f1e9c93f4133a4ab8606066cdb6390799f7643806ba4ac27322aea7838ebf2793545 diff --git a/sssd.spec b/sssd.spec index 8f00a3e..06674cf 100644 --- a/sssd.spec +++ b/sssd.spec @@ -26,17 +26,16 @@ %global samba_package_version %(rpm -q samba-devel --queryformat %{version}) Name: sssd -Version: 2.9.8 -Release: 4%{?dist} +Version: 2.9.9 +Release: 1%{?dist} Summary: System Security Services Daemon License: GPLv3+ URL: https://github.com/SSSD/sssd/ Source0: https://github.com/SSSD/sssd/releases/download/%{version}/sssd-%{version}.tar.gz ### Patches ### -Patch1: 0001-do-not-require-GID-for-non-POSIX-group.patch -Patch2: 0002-fix-use-after-free-in-kcm_read_options.patch -Patch3: 0003-add-missing-include.patch +Patch1: 0001-passwordless-gdm.patch +Patch2: 0002-sysdb-enumpwent-replacement.patch ### Dependencies ### @@ -1086,6 +1085,17 @@ fi %systemd_postun_with_restart sssd.service %changelog +* Wed May 6 2026 Iker Pedrosa - 2.9.9-1 +- 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] + * Wed Apr 15 2026 Tomas Halman - 2.9.8-4 - Resolves: RHEL-154804 Crash in 'sss_client/autofs/sss_autofs.c'