From 2e4570d8a843db225dd4228510f2cfc781ba07c4 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 18 Jan 2024 15:46:33 +0100 Subject: [PATCH 01/12] 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 2ed5a866b74..ce33e4383a6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -302,6 +302,7 @@ if HAVE_CMOCKA test_sssd_krb5_locator_plugin \ test_confdb \ test_krb5_idp_plugin \ + test_sss_pam_data \ $(NULL) @@ -2673,6 +2674,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 00000000000..442b3737297 --- /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 f09b9c5eb2c..75421d8e041 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 e9b90a8a4e5..a7efba7915a 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_ */ From 637c535e1f21043a6b5b523090163b6d84476b30 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 8 May 2024 10:46:47 +0200 Subject: [PATCH 02/12] 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 f3360544b85..891fcd60cbe 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 @@ errno_t pc_list_add_passkey(struct prompt_config ***pc_list, 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 ee45e1d576c..727aafd2779 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -662,6 +662,7 @@ enum prompt_config_type { PC_TYPE_2FA_SINGLE, PC_TYPE_PASSKEY, PC_TYPE_SC_PIN, + PC_TYPE_EIDP, PC_TYPE_LAST }; @@ -674,6 +675,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); @@ -684,6 +687,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 0b761ae4c31..70b27875ad0 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), }; From ba2db5760e0bb97aa18fdbb367f000f8b6778734 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 8 May 2024 11:55:49 +0200 Subject: [PATCH 03/12] Add new options to tune EIdP prompting These new options are needed by the GDM integration, but they can be reused for CLI prompting. :config: New options to tune EIdP prompting: 'init_prompt' and 'link_prompt'. Signed-off-by: Iker Pedrosa --- src/confdb/confdb.h | 3 ++ src/config/cfg_rules.ini | 7 ++++ src/man/sssd.conf.5.xml | 26 ++++++++++++++ src/responder/pam/pam_prompting_config.c | 46 ++++++++++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 39f4ab63d3e..43b2f181b56 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -324,6 +324,9 @@ #define CONFDB_PC_PASSKEY_INTERACTIVE_PROMPT "interactive_prompt" #define CONFDB_PC_PASSKEY_TOUCH "touch" #define CONFDB_PC_PASSKEY_TOUCH_PROMPT "touch_prompt" +#define CONFDB_PC_TYPE_EIDP "eidp" +#define CONFDB_PC_EIDP_INIT_PROMPT "init_prompt" +#define CONFDB_PC_EIDP_LINK_PROMPT "link_prompt" struct confdb_ctx; diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini index 0fa06d586ab..d0ae9298c3f 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -326,6 +326,13 @@ option = interactive_prompt option = touch option = touch_prompt +[rule/allowed_prompting_eidp_options] +validator = ini_allowed_options +section_re = ^prompting/eidp$ + +option = init_prompt +option = link_prompt + [rule/allowed_prompting_password_subsec_options] validator = ini_allowed_options section_re = ^prompting/password/[^/\@]\+$ diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 6aeb362557e..cf7614750dc 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -4547,6 +4547,32 @@ ldap_user_extra_attrs = phone:telephoneNumber + + + [prompting/eidp] + + to configure External Identity Provider + authentication prompting, allowed options are: + + + init_prompt + + to change the message of the initial prompt. + + + + + link_prompt + + to change the message of the link prompt. + + + + + + + + It is possible to add a subsection for specific PAM services, diff --git a/src/responder/pam/pam_prompting_config.c b/src/responder/pam/pam_prompting_config.c index 7d0362fbbf5..fddae87fe56 100644 --- a/src/responder/pam/pam_prompting_config.c +++ b/src/responder/pam/pam_prompting_config.c @@ -27,6 +27,8 @@ #define DEFAULT_PASSKEY_PROMPT_INTERACTIVE _("Insert your Passkey device, then press ENTER.") #define DEFAULT_PASSKEY_PROMPT_TOUCH _("Please touch the device.") +#define DEFAULT_EIDP_PROMPT_INIT _("Log In.") +#define DEFAULT_EIDP_PROMPT_LINK _("Log in online with another device.") typedef errno_t (pam_set_prompting_fn_t)(TALLOC_CTX *, struct confdb_ctx *, const char *, @@ -147,6 +149,36 @@ static errno_t pam_set_passkey_prompting_options(TALLOC_CTX *tmp_ctx, return ret; } + +static errno_t pam_set_eidp_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + char *init_prompt = NULL; + char *link_prompt = NULL; + int ret; + + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_EIDP_INIT_PROMPT, + DEFAULT_EIDP_PROMPT_INIT, &init_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_EIDP_LINK_PROMPT, + DEFAULT_EIDP_PROMPT_LINK, &link_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + + ret = pc_list_add_eidp(pc_list, init_prompt, link_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_eidp failed.\n"); + } + + return ret; +} + static errno_t pam_set_prompting_options(struct confdb_ctx *cdb, const char *service_name, char **sections, @@ -245,6 +277,20 @@ errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd) } } + /* There's no option to enable/disable EIdP in PAM config, thus prompt + * options are always checked. */ + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_EIDP, + pam_set_eidp_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + if (types.cert_auth) { /* If certificate based authentication is possilbe, i.e. a Smartcard * or similar with the mapped certificate is available we currently From 424f6ba156b01f9a02f20aae330e60a2e8b0d0b8 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 8 May 2024 10:30:54 +0200 Subject: [PATCH 04/12] 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 fddae87fe56..8c3eada6a51 100644 --- a/src/responder/pam/pam_prompting_config.c +++ b/src/responder/pam/pam_prompting_config.c @@ -244,7 +244,8 @@ static errno_t pam_set_prompting_options(struct confdb_ctx *cdb, 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; @@ -346,10 +347,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 61883618944..bbe743c5098 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); @@ -172,7 +175,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 a7c18173358..de95f5680a9 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1213,6 +1213,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; #endif /* BUILD_PASSKEY */ @@ -1481,7 +1482,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"); From 9e2156c55403ced9823fbd75848d3a73c358e10a Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 19 Jan 2024 09:27:24 +0100 Subject: [PATCH 05/12] 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 | 39 +++ src/responder/pam/pamsrv_json.c | 431 ++++++++++++++++++++++++++++ src/responder/pam/pamsrv_json.h | 108 +++++++ src/sss_client/sss_cli.h | 5 + src/tests/cmocka/test_pamsrv_json.c | 296 +++++++++++++++++++ 5 files changed, 879 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 ce33e4383a6..e0f5b5a976c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -303,6 +303,7 @@ if HAVE_CMOCKA test_confdb \ test_krb5_idp_plugin \ test_sss_pam_data \ + test_pamsrv_json \ $(NULL) @@ -744,6 +745,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 \ @@ -2674,6 +2676,43 @@ 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) \ + $(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 00000000000..d6a569c20f1 --- /dev/null +++ b/src/responder/pam/pamsrv_json.c @@ -0,0 +1,431 @@ +/* + 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; + 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; + } + + uri = talloc_strdup(tmp_ctx, (const char *)oauth2); + if (uri == NULL) { + ret = ENOMEM; + goto done; + } + offset = strlen((const char *)uri); + offset++; + + if (offset > len) { + DEBUG(SSSDBG_OP_FAILURE, + "Trying to access data outside of the boundaries.\n"); + ret = EPERM; + goto done; + } + uri_complete = talloc_strdup(tmp_ctx, (const char *)oauth2+offset); + if (uri_complete == NULL) { + ret = ENOMEM; + goto done; + } + offset += strlen((const char *)uri_complete); + offset++; + + if (offset > len) { + DEBUG(SSSDBG_OP_FAILURE, + "Trying to access data outside of the boundaries.\n"); + ret = EPERM; + goto done; + } + code = talloc_strdup(tmp_ctx, (const char *)oauth2+offset); + 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; + const char *oauth2_init_prompt = NULL; + const char *oauth2_link_prompt = NULL; + const char *tmp = NULL; + int prompt_type; + size_t c; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (pc_list != NULL) { + for (c = 0; pc_list[c] != NULL; c++) { + prompt_type = pc_get_type(pc_list[c]); + switch(prompt_type) { + case PC_TYPE_PASSWORD: + tmp = pc_get_password_prompt(pc_list[c]); + if (tmp == NULL) { + ret = ENOENT; + } + password_prompt = talloc_strdup(tmp_ctx, tmp); + if (password_prompt == NULL) { + ret = ENOMEM; + } + break; + case PC_TYPE_EIDP: + tmp = pc_get_eidp_init_prompt(pc_list[c]); + if (tmp == NULL) { + ret = ENOENT; + } + oauth2_init_prompt = talloc_strdup(tmp_ctx, tmp); + if (oauth2_init_prompt == NULL) { + ret = ENOMEM; + } + tmp = pc_get_eidp_link_prompt(pc_list[c]); + if (tmp == NULL) { + ret = ENOENT; + } + oauth2_link_prompt = talloc_strdup(tmp_ctx, tmp); + if (oauth2_link_prompt == NULL) { + ret = ENOMEM; + } + break; + default: + ret = EPERM; + goto done; + } + } + } + + if (password_prompt == NULL) { + ret = confdb_get_string(cdb, tmp_ctx, CONFDB_PC_CONF_ENTRY, + CONFDB_PC_PASSWORD_PROMPT, "", + &password_prompt); + if (ret != EOK) { + goto done; + } + } + + if (oauth2_init_prompt == NULL) { + oauth2_init_prompt = talloc_strdup(tmp_ctx, "Log In"); + if (oauth2_init_prompt == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (oauth2_link_prompt == NULL) { + oauth2_link_prompt = talloc_strdup(tmp_ctx, + "Log in online with another device"); + if (oauth2_init_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; + } + 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 00000000000..7b8b046071d --- /dev/null +++ b/src/responder/pam/pamsrv_json.h @@ -0,0 +1,108 @@ +/* + 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" + + +/** + * @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 727aafd2779..dd449e2823c 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -554,6 +554,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 00000000000..2e166e3568b --- /dev/null +++ b/src/tests/cmocka/test_pamsrv_json.c @@ -0,0 +1,296 @@ +/* + 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 PASSWORD_PROMPT "Password" +#define OAUTH2_INIT_PROMPT "Init" +#define OAUTH2_LINK_PROMPT "Link" +#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; + + ret = pc_list_add_password(&pc_list, PASSWORD_PROMPT); + assert_int_equal(ret, EOK); + ret = pc_list_add_eidp(&pc_list, OAUTH2_INIT_PROMPT, OAUTH2_LINK_PROMPT); + assert_int_equal(ret, EOK); + + 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); +} From 16816267226724c43dfbb6a5b978ba3889c5890c Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 30 Jan 2024 11:21:59 +0100 Subject: [PATCH 06/12] 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 | 167 ++++++++++++++++++++++++++++ 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, 323 insertions(+) diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c index d6a569c20f1..ed5b0b71e71 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -429,3 +429,170 @@ generate_json_auth_message(struct confdb_ctx *cdb, 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; + + 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 7b8b046071d..e8419a842a7 100644 --- a/src/responder/pam/pamsrv_json.h +++ b/src/responder/pam/pamsrv_json.h @@ -105,4 +105,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 2e166e3568b..760ffe666d3 100644 --- a/src/tests/cmocka/test_pamsrv_json.c +++ b/src/tests/cmocka/test_pamsrv_json.c @@ -60,6 +60,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 @@ -240,6 +249,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) { /* @@ -269,6 +380,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 a7efba7915a..441720e97a5 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; }; /** From 18e46ed6a4ba1fa2760033b260366c9affde45de Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 6 Mar 2024 12:25:55 +0100 Subject: [PATCH 07/12] 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 ed5b0b71e71..9da0bd86bf2 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -596,3 +596,20 @@ json_unpack_auth_reply(struct pam_data *pd) 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 e8419a842a7..c48fc4e20a5 100644 --- a/src/responder/pam/pamsrv_json.h +++ b/src/responder/pam/pamsrv_json.h @@ -143,4 +143,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 760ffe666d3..74d60836592 100644 --- a/src/tests/cmocka/test_pamsrv_json.c +++ b/src/tests/cmocka/test_pamsrv_json.c @@ -351,6 +351,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) { /* @@ -385,6 +416,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. */ From 6cb908933d1235325140a0ebe7c75d8fb806e4d5 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 20 Feb 2024 12:19:24 +0100 Subject: [PATCH 08/12] 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: Add pam_json_services option 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 43b2f181b56..5be250a7d7a 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -158,6 +158,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 ec0e276cbda..34394b7003c 100644 --- a/src/config/SSSDConfig/sssdoptions.py +++ b/src/config/SSSDConfig/sssdoptions.py @@ -115,6 +115,7 @@ def __init__(self): '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 d0ae9298c3f..63e86bf4521 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -145,6 +145,7 @@ option = pam_gssapi_indicators_map 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 d4d79c96966..e83783b062e 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -88,6 +88,7 @@ pam_gssapi_indicators_map = 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 38666e9c104..f16c9e657cc 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -65,6 +65,9 @@ SSSD_NON_ROOT_USER_CONDS = ;with_non_root_user_support else SSSD_NON_ROOT_USER_CONDS = ;without_non_root_user_support 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)$(PASSKEY_CONDS)$(FILES_PROVIDER_CONDS)$(SSSD_NON_ROOT_USER_CONDS)$(ENUM_CONDS) diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index cf7614750dc..69502519568 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -2062,6 +2062,29 @@ pam_gssapi_indicators_map = sudo:pkinit, sudo-i: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 cd49c40664b..5e2cd6bfe48 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -404,6 +404,30 @@ static int pam_process_init(TALLOC_CTX *mem_ctx, } } + /* 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_register_service(rctx, rctx->sbus_conn, SSS_PAM_SBUS_SERVICE_NAME, diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index bbe743c5098..8acb53d4611 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -76,6 +76,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 { From ff16734b7aa4a2f45f2f6a0eeabb8c3205b6f757 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 22 Jan 2024 10:30:16 +0100 Subject: [PATCH 09/12] 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 e0f5b5a976c..49788f93083 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1564,6 +1564,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 \ @@ -1586,6 +1587,7 @@ sssd_pam_LDADD = \ $(PAM_LIBS) \ $(SYSTEMD_DAEMON_LIBS) \ $(GSSAPI_KRB5_LIBS) \ + $(JANSSON_LIBS) \ libsss_certmap.la \ $(SSSD_INTERNAL_LTLIBS) \ libsss_iface.la \ @@ -2637,6 +2639,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 \ @@ -2666,6 +2669,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 de95f5680a9..aa49be9f680 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -35,6 +35,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" @@ -1504,6 +1505,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); } /* From b1f7d82f96607103d6819315ee65c51389688575 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 22 Jan 2024 10:32:48 +0100 Subject: [PATCH 10/12] 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 0dc7f19d0df..844a0a71172 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 e3a09f50100..e98192c1188 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 d6fb254f208..c145b8a51f5 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 47f3f6bd38e..b088f3937b0 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" @@ -1349,6 +1353,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)); } @@ -1463,6 +1480,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; } @@ -1988,6 +2009,65 @@ static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi, 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 @@ -2986,6 +3066,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 dd449e2823c..38786c839af 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -413,6 +413,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) From a4197b79cdacb9eb0b3e68911ae9e43f958909df Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 30 Jan 2024 11:32:25 +0100 Subject: [PATCH 11/12] 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 aa49be9f680..fe0036e0615 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -286,6 +286,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"); @@ -363,6 +365,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; @@ -377,6 +387,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); @@ -1706,6 +1734,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, From 2a93af22f15b386d046036c1e90fba9207148694 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 5 Mar 2024 15:45:40 +0100 Subject: [PATCH 12/12] 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 9da0bd86bf2..c9353538646 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -217,9 +217,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"); @@ -237,9 +238,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 faa434328fd..6d4ba6f9615 100644 --- a/src/tests/cmocka/test_pam_srv.c +++ b/src/tests/cmocka/test_pam_srv.c @@ -612,6 +612,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); @@ -707,6 +709,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); @@ -788,6 +792,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 74d60836592..7705f432721 100644 --- a/src/tests/cmocka/test_pamsrv_json.c +++ b/src/tests/cmocka/test_pamsrv_json.c @@ -39,9 +39,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\", " \