From fa5f361a8d5469e12703fc39b73ce0332b2f51bf Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 18 Jan 2024 15:46:33 +0100 Subject: [PATCH 01/32] 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 abc371f1293..fd7e6fa71ac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -296,6 +296,7 @@ if HAVE_CMOCKA test_sssd_krb5_locator_plugin \ test_confdb \ test_krb5_idp_plugin \ + test_sss_pam_data \ $(NULL) @@ -2660,6 +2661,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 6df2dfa5cb0c0231ae4153fc69203f2a9eb8ce95 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 8 May 2024 10:46:47 +0200 Subject: [PATCH 02/32] 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 29b496e1d52..792c34cb9fc 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 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 9955c81c68fd85cb6a8268621b1e1a88f7cec568 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 8 May 2024 10:30:54 +0200 Subject: [PATCH 03/32] 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 7d0362fbbf5..27f794b929e 100644 --- a/src/responder/pam/pam_prompting_config.c +++ b/src/responder/pam/pam_prompting_config.c @@ -212,7 +212,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; @@ -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 2aa14ae02ff..4c87383797e 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); @@ -173,7 +176,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 1b58439e169..6acfde3ea3c 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1229,6 +1229,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; @@ -1508,7 +1509,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 bd214fd5579aa54704a5b7c49f904ab8141da8eb Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 19 Jan 2024 09:27:24 +0100 Subject: [PATCH 04/32] 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 | 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, 851 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 fd7e6fa71ac..c5a9bab6445 100644 --- a/Makefile.am +++ b/Makefile.am @@ -297,6 +297,7 @@ if HAVE_CMOCKA test_confdb \ test_krb5_idp_plugin \ test_sss_pam_data \ + test_pamsrv_json \ $(NULL) @@ -738,6 +739,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 \ @@ -2661,6 +2663,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..c952474203e --- /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 00000000000..57fce53e2d9 --- /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 792c34cb9fc..b99c145c430 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 00000000000..7f391732d75 --- /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); +} From ed85727004e7ef52056797d0e22f692a94f9cc29 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 30 Jan 2024 11:21:59 +0100 Subject: [PATCH 05/32] 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 c952474203e..f0ed24f7a9f 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -405,3 +405,173 @@ 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; + + 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 57fce53e2d9..d861120df1f 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 7f391732d75..6d00d0bd0f7 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 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 0192c5b9380dc1772b0b8fdbe86bcc784d4f7c13 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 6 Mar 2024 12:25:55 +0100 Subject: [PATCH 06/32] 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 f0ed24f7a9f..e59f218b273 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -575,3 +575,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 d861120df1f..4c8c3aef332 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 6d00d0bd0f7..3e1b8b0d136 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. */ From 418416f027bf74f9091e57e759eb9e8ce0129899 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 20 Feb 2024 12:19:24 +0100 Subject: [PATCH 07/32] 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 afb7e7d0f21..1e53f8be6b3 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -160,6 +160,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 ffb6797db95..1cb7376168b 100644 --- a/src/config/SSSDConfig/sssdoptions.py +++ b/src/config/SSSDConfig/sssdoptions.py @@ -117,6 +117,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 82807104156..be66d0f87e0 100644 --- a/src/config/cfg_rules.ini +++ b/src/config/cfg_rules.ini @@ -147,6 +147,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 b6c30d432af..5b65d59b5ed 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -89,6 +89,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 366bdbb32e7..a8d443795ad 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -64,6 +64,9 @@ endif if HAVE_LIBNL LIBNL_CONDS = ;have_libnl 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) diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 0e4cf137f4d..d0b3559aad2 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -2073,6 +2073,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 4fd5a6aa03f..be00063276e 100644 --- a/src/responder/pam/pamsrv.c +++ b/src/responder/pam/pamsrv.c @@ -406,6 +406,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_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 4c87383797e..cee29125bd0 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 aa304214c2442221bd25362f47d88320c435efe9 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 22 Jan 2024 10:30:16 +0100 Subject: [PATCH 08/32] 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 c5a9bab6445..03db18825e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1554,6 +1554,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 \ @@ -1576,6 +1577,7 @@ sssd_pam_LDADD = \ $(PAM_LIBS) \ $(SYSTEMD_DAEMON_LIBS) \ $(GSSAPI_KRB5_LIBS) \ + $(JANSSON_LIBS) \ libsss_certmap.la \ $(SSSD_INTERNAL_LTLIBS) \ libsss_iface.la \ @@ -2624,6 +2626,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 \ @@ -2653,6 +2656,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 6acfde3ea3c..e92432f0b74 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" @@ -1533,6 +1534,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 e1c30581c1e82a263547ba209cc726bec6893825 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 22 Jan 2024 10:32:48 +0100 Subject: [PATCH 09/32] 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 969ff366d22..ca400b03b83 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 @@ 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 @@ -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 b99c145c430..0b00c44528b 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) From a459ce4a775e8800c98a0e536f0d0cfe6b75b253 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 30 Jan 2024 11:32:25 +0100 Subject: [PATCH 10/32] 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 e92432f0b74..927107459b3 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -287,6 +287,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"); @@ -364,6 +366,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; @@ -378,6 +388,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); @@ -1735,6 +1763,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 9982da2ec52f91da4ce10fcf29626981de502992 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 5 Mar 2024 15:45:40 +0100 Subject: [PATCH 11/32] 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 e59f218b273..98bc3cbaa5d 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 35ebfbafae2..12ae8bea703 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 3e1b8b0d136..7322c533dd2 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\", " \ From 00cf1e8cd21285e38c1de82d3ad3a338765864f6 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 13 Jun 2024 14:18:46 +0200 Subject: [PATCH 12/32] 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 98bc3cbaa5d..898cd4cf548 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"); From ae3344cd7ae52fb039c1b056adc02415db7ff68d Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 22 Sep 2025 10:19:51 +0200 Subject: [PATCH 13/32] 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 898cd4cf548..a434e660a05 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 7322c533dd2..fcc7928f6c1 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\": {}}}" From 85176c4d887a21a5db854ea0d044e64bf41d8de0 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 4 Jun 2024 10:51:36 +0200 Subject: [PATCH 14/32] 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 ca400b03b83..ba25959d5da 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 891fcd60cbe..caf308c8265 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 @@ errno_t pc_list_add_eidp(struct prompt_config ***pc_list, 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 0b00c44528b..4e3c58c3178 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 70b27875ad0..99f2ebc4bf2 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), }; From d61c7513ee1e938673347f3cd00b8228493c0737 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 12 Jun 2024 15:37:31 +0200 Subject: [PATCH 15/32] 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 442b3737297..ce4d328c781 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 75421d8e041..da7a9f19f3c 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 441720e97a5..fa83c4a1cf2 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_ */ From 55f38e1777918bc4b32dc45a696e356920807135 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 9 Apr 2024 12:05:56 +0200 Subject: [PATCH 16/32] 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 | 385 +++++++++++++++++++++++++++- src/responder/pam/pamsrv_json.h | 49 +++- src/tests/cmocka/test_pamsrv_json.c | 307 +++++++++++++++++++++- 3 files changed, 723 insertions(+), 18 deletions(-) diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c index a434e660a05..1b7b010a66e 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -22,8 +22,11 @@ along with this program. If not, see . */ +#define _GNU_SOURCE + #include #include +#include #include #include "responder/pam/pamsrv.h" @@ -31,6 +34,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 +147,15 @@ obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, char **_uri, 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 +181,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 +459,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 +522,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 +566,12 @@ json_format_mechanisms(bool password_auth, const char *password_prompt, } 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 +597,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 +655,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 +667,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 +718,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 +738,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 +752,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 4c8c3aef332..380bd65f730 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 fcc7928f6c1..0abefa32439 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), From 9e1f8dd49c32679f0fd34e7621b2241f970da239 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 10 Apr 2024 17:35:22 +0200 Subject: [PATCH 17/32] 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 1b7b010a66e..e02a7617e04 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -144,6 +144,27 @@ obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, char **_uri, 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, @@ -867,10 +888,33 @@ json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg, 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; @@ -879,6 +923,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", @@ -945,6 +992,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 380bd65f730..2441510d008 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 0abefa32439..2d4d6e4e1f7 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), From b9aadb57bffcc414c7a6e3d44b21a7cd11a5898a Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 6 Sep 2024 11:23:14 +0200 Subject: [PATCH 18/32] 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 e02a7617e04..f0f6842ce05 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -50,8 +50,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; @@ -134,8 +134,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: @@ -167,9 +167,7 @@ find_certificate_in_list(struct cert_auth_info *cert_list, int cert_num, 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; @@ -214,11 +212,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: @@ -426,9 +424,81 @@ get_cert_list(TALLOC_CTX *mem_ctx, struct pam_data *pd, 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; @@ -466,7 +536,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: @@ -476,14 +546,7 @@ get_cert_names(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list, } 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; @@ -499,11 +562,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; @@ -519,14 +582,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"); @@ -543,14 +606,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; @@ -587,8 +650,7 @@ json_format_mechanisms(bool password_auth, const char *password_prompt, } 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; @@ -602,7 +664,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"); @@ -618,8 +680,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"); @@ -643,7 +705,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"); @@ -671,14 +733,7 @@ json_format_priority(bool password_auth, bool oauth2_auth, bool sc_auth, } 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; @@ -687,18 +742,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; @@ -739,18 +788,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); @@ -758,40 +797,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 2441510d008..2c894926197 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 2d4d6e4e1f7..6dc85e38498 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), From 484cbd6f8faefea8ecbd603d31158d64a1b2f76f Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 29 Jan 2025 10:42:40 +0100 Subject: [PATCH 19/32] 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 f0f6842ce05..e06ea76fcec 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -486,7 +486,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; @@ -497,12 +497,16 @@ init_auth_data(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, } 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; @@ -524,19 +528,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: @@ -551,8 +619,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(); @@ -607,34 +676,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; } } @@ -644,6 +731,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; @@ -654,7 +744,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(); @@ -664,8 +753,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; @@ -680,28 +769,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 2c894926197..ddc1c442aaa 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 6dc85e38498..bb4d9c07137 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), From 8036cf53c7f2053534abca8f03cf0dc35ae96ecb Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 29 Jan 2025 17:27:06 +0100 Subject: [PATCH 20/32] 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 e06ea76fcec..6b6d49c2a3a 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -144,27 +144,6 @@ obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, 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) @@ -985,23 +964,46 @@ json_unpack_oauth2_code(TALLOC_CTX *mem_ctx, char *json_auth_msg, } 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; } @@ -1009,7 +1011,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; @@ -1020,8 +1021,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", @@ -1090,27 +1089,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 ddc1c442aaa..6e2c90d700a 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 bb4d9c07137..8e58222ed56 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), From b9b878b034470962f03d3847b41d7aecbbaa29e7 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 12 Feb 2025 18:17:25 +0100 Subject: [PATCH 21/32] 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 8d35cbb2695..a52579ec560 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, From 575caf0b6f75f2e8f3a04f7d492518c1287b530d Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 10 Sep 2024 09:22:28 +0200 Subject: [PATCH 22/32] 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 6b6d49c2a3a..7f034461d5d 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -30,6 +30,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" @@ -144,6 +147,79 @@ obtain_oauth2_data(TALLOC_CTX *mem_ctx, struct pam_data *pd, 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) @@ -154,6 +230,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); @@ -191,11 +270,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: @@ -442,6 +542,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"); @@ -471,6 +579,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; } @@ -601,6 +719,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(); @@ -704,6 +823,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; @@ -748,6 +894,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 6e2c90d700a..7b8b9a5f54c 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 8e58222ed56..671af0f0cb9 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), From c11cf2eaf96f271839525a1eed3b112c63c633a6 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 17 Sep 2024 10:21:10 +0200 Subject: [PATCH 23/32] 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 c736fd5a336..53ad75d16a3 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 df64ede57e3..4c2d7ca4d24 100644 --- a/src/util/authtok.c +++ b/src/util/authtok.c @@ -818,6 +818,22 @@ static errno_t sss_authtok_set_passkey_from_blob(struct sss_auth_token *tok, 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 acabb707896..b681012112b 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. From 380e0567bafb1bfa47522d61b966aebc656fd5e6 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 17 Sep 2024 09:20:53 +0200 Subject: [PATCH 24/32] 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 7f034461d5d..b667cb7694a 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -1169,6 +1169,45 @@ json_unpack_smartcard(TALLOC_CTX *mem_ctx, json_t *jroot, 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) { @@ -1180,9 +1219,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", @@ -1268,6 +1310,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 7b8b9a5f54c..2f6c5cf362d 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 671af0f0cb9..d432ea06584 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), From c54787549e5f3f902a132214436c08b656f1adbe Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 27 Mar 2025 10:20:12 +0100 Subject: [PATCH 25/32] 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 0506a3527f0..15bef2d0003 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 @@ static bool pkinit_identity_matches(const char *identity, 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 @@ static krb5_error_code idp_oauth2_preauth(struct krb5_req *kr, 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 @@ static krb5_error_code answer_passkey(krb5_context kctx, #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 */ From 098439ede49c756936dc497a39c32329ed6d26fc Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 7 Nov 2025 09:38:29 +0100 Subject: [PATCH 26/32] 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 d0b3559aad2..7d9f541b40c 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 1490ca28dcc..cd41bf3e9bc 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]); From c03f37c1a750317699079b9d4065bba81a61ca54 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 12 Nov 2025 14:26:27 +0100 Subject: [PATCH 27/32] 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 927107459b3..8a7c6a6a14c 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1564,8 +1564,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 ba25959d5da..8d104f4fd7b 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 4e3c58c3178..cadf9be07ae 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) From ce0baa649ce455d19475be8a4bf40a7cc8748750 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 13 Nov 2025 16:17:05 +0100 Subject: [PATCH 28/32] 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 b667cb7694a..9b2ed19f47a 100644 --- a/src/responder/pam/pamsrv_json.c +++ b/src/responder/pam/pamsrv_json.c @@ -509,6 +509,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); @@ -524,7 +525,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) { @@ -532,7 +532,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) { @@ -540,7 +539,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) { @@ -548,7 +546,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) { @@ -564,26 +571,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 d432ea06584..0430d2651a1 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)); From 3b9ca38d866800a12fac738a9bcca4d45d4b5c36 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 18 Nov 2025 16:08:15 +0100 Subject: [PATCH 29/32] 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 a8d443795ad..31be3b08ef8 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -69,7 +69,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) +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)$(JSON_PAM_CONDS) #Special Rules: diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 7d9f541b40c..ae826c39b97 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -2099,6 +2099,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. + From 4c4af07b7bf452db9a62cc3ce63cb5e5fc039786 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 20 Nov 2025 16:33:41 +0100 Subject: [PATCH 30/32] 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 fb2f5886943..0eacb552360 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. From 6520d22022cadd15ddc67ec5258d77f8a44e5298 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Thu, 11 Dec 2025 11:03:17 +0100 Subject: [PATCH 31/32] 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 15bef2d0003..ddb8b6f46ec 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: From 9c8d54fedf06693cdb7bb215968cd87fc2dff2b0 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 11 Dec 2025 12:25:00 +0100 Subject: [PATCH 32/32] 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 ddb8b6f46ec..d74ea0745fa 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;