diff --git a/Makefile.am b/Makefile.am index abc371f1293..03db18825e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -296,6 +296,8 @@ if HAVE_CMOCKA test_sssd_krb5_locator_plugin \ test_confdb \ test_krb5_idp_plugin \ + test_sss_pam_data \ + test_pamsrv_json \ $(NULL) @@ -737,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 \ @@ -1551,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 \ @@ -1573,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 \ @@ -2621,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 \ @@ -2650,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 \ @@ -2660,6 +2667,60 @@ 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 \ + $(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/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/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/man/Makefile.am b/src/man/Makefile.am index 366bdbb32e7..31be3b08ef8 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -64,9 +64,12 @@ 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) +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 0e4cf137f4d..ae826c39b97 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 @@ -2073,6 +2078,34 @@ 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) + + + Note: 2-Factor Authentication (2FA) is not + supported. If 2FA is required, do not + activate the JSON protocol. + + + 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. diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 0506a3527f0..d74ea0745fa 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -564,6 +564,61 @@ 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; + } + + kr->otp = true; + + 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"); + 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 +627,16 @@ 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 + && 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; + } 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 */ 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.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 2aa14ae02ff..cee29125bd0 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); @@ -73,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 { @@ -173,7 +177,8 @@ errno_t filter_responses(struct pam_ctx *pctx, errno_t pam_get_auth_types(struct pam_data *pd, struct pam_resp_auth_type *_auth_types); -errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd); +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd, + struct prompt_config ***_pc_list); enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str); const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme); diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 1b58439e169..8a7c6a6a14c 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" @@ -286,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"); @@ -363,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; @@ -377,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); @@ -1229,6 +1258,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 +1538,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"); @@ -1532,6 +1562,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) && + !(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, + "failed to generate JSON message.\n"); + goto done; + } + } +#endif /* HAVE_GDM_CUSTOM_JSON_PAM_EXTENSION */ + pc_list_free(pc_list); } /* @@ -1720,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, diff --git a/src/responder/pam/pamsrv_json.c b/src/responder/pam/pamsrv_json.c new file mode 100644 index 00000000000..9b2ed19f47a --- /dev/null +++ b/src/responder/pam/pamsrv_json.c @@ -0,0 +1,1381 @@ +/* + 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 . +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#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" + +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, + struct auth_data *_auth_data) +{ + 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; + } + + _auth_data->oauth2->uri = talloc_steal(mem_ctx, uri); + _auth_data->oauth2->code = talloc_steal(mem_ctx, code); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + 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) +{ + 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; + char *passkey_init_prompt = NULL; + char *passkey_pin_prompt = NULL; + char *passkey_touch_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; + } + + 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; + } + + 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: + 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; +} + +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; + struct pam_resp_auth_type types; + 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)->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)->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)->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; + } + + 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) { + 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; + } + + 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; + } + } + +#ifdef BUILD_PASSKEY + 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; +#endif /* BUILD_PASSKEY */ + +done: + return ret; +} + +errno_t +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; + + 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; + } + + 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->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: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +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; + json_t *json_passkey = NULL; + int ret; + + root = json_object(); + if (root == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "json_array failed.\n"); + ret = ENOMEM; + goto done; + } + + if (auth_data->pswd->enabled) { + json_pass = json_pack("{s:s,s:s,s:s}", + "name", "Password", + "role", "password", + "prompt", auth_data->pswd->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 (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", 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"); + 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; + } + } + + 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_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 = json_array_append_new(json_cert_array, json_cert); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "json_array_append_new failed.\n"); + json_decref(json_cert); + ret = ENOMEM; + goto done; + } + } + + 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; + } + } + + 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; + +done: + if (ret != EOK) { + json_decref(root); + if (json_cert_array != NULL) { + json_decref(json_cert_array); + } + } + + return ret; +} + +errno_t +json_format_priority(struct auth_data *auth_data, 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 (auth_data->sc->enabled) { + json_priority = json_string("smartcard"); + 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->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) { + 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->pswd->enabled) { + 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"); + 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, struct auth_data *auth_data, + 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(auth_data, &json_mech); + if (ret != EOK) { + goto done; + } + + ret = json_format_priority(auth_data, &json_priority); + if (ret != EOK) { + json_decref(json_mech); + goto done; + } + + root = json_pack("{s:{s:o,s:o}}", + "authSelection", + "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; + struct auth_data *auth_data = NULL; + char *result = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + 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, auth_data, &result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to format JSON message.\n"); + 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; +} + +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}}", + "authSelection", + "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_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; + + 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 smartcard failed.\n"); + ret = EINVAL; + goto done; + } + + *_pin = pin; + *_cai = talloc_steal(mem_ctx, cai); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + 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) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct cert_auth_info *cai = 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; + 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", + 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}", "authSelection", &jauth_selection); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "json_unpack for authSelection 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; + } + + if (strncmp(key, "smartcard", strlen("smartcard")) == 0) { + ret = json_unpack_smartcard(tmp_ctx, jobj, &pin, &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; + } + + 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"); + ret = EINVAL; + +done: + if (jroot != NULL) { + json_decref(jroot); + } + talloc_free(tmp_ctx); + + 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 new file mode 100644 index 00000000000..2f6c5cf362d --- /dev/null +++ b/src/responder/pam/pamsrv_json.h @@ -0,0 +1,246 @@ +/* + 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" +#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 { + 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 **cert_instructions; + char *pin_prompt; + char **module_names; + char **key_ids; + 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 + * + * @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 data from the certificate list + * + * @param[in] mem_ctx Memory context + * @param[in] cert_list Certificate 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_data(TALLOC_CTX *mem_ctx, struct cert_auth_info *cert_list, + struct auth_data *_auth_data); + +/** + * @brief Format authentication mechanisms to JSON + * + * @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(struct auth_data *auth_data, json_t **_list_mech); + +/** + * @brief Format priority to JSON + * + * @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(struct auth_data *auth_data, json_t **_priority); + +/** + * @brief Format data to JSON + * + * @param[in] mem_ctx Memory context + * @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, struct auth_data *auth_data, + 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); + + +/** + * @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 smartcard specific data reply + * + * @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(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 + * + * @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); + +/** + * @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/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]); 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, 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..8d104f4fd7b 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 @@ -2524,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; @@ -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 @@ -3063,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/pam_sss_prompt_config.c b/src/sss_client/pam_sss_prompt_config.c index f3360544b85..caf308c8265 100644 --- a/src/sss_client/pam_sss_prompt_config.c +++ b/src/sss_client/pam_sss_prompt_config.c @@ -45,8 +45,14 @@ 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 { + char *prompt_init; + char *prompt_link; }; struct prompt_config { @@ -56,7 +62,8 @@ 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; }; @@ -116,6 +123,38 @@ 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; +} + +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) { @@ -156,11 +195,24 @@ 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; +} + +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; } @@ -185,12 +237,15 @@ 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]); break; + case PC_TYPE_EIDP: + pc_free_eidp(pc_list[c]); + break; default: return; } @@ -396,6 +451,100 @@ 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 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) { @@ -433,7 +582,17 @@ 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); + 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; @@ -492,7 +651,29 @@ 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], + 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); @@ -679,7 +860,95 @@ 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)) { + 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; diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index 29b496e1d52..cadf9be07ae 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) @@ -427,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) @@ -556,6 +560,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 + */ }; /** @@ -663,7 +672,8 @@ 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 }; @@ -676,6 +686,10 @@ 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); +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); @@ -686,6 +700,10 @@ 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 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_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/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 new file mode 100644 index 00000000000..0430d2651a1 --- /dev/null +++ b/src/tests/cmocka/test_pamsrv_json.c @@ -0,0 +1,1165 @@ +/* + 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.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 PASSKEY_CRYPTO_CHAL "6uDMvRKj3W5xJV3HaQjZrtXMNmUUAjRGklFG2MIhN5s=" + +#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\"}" +#define BASIC_OAUTH2 "\"eidp\": {" \ + "\"name\": \"Web Login\", \"role\": \"eidp\", " \ + "\"initPrompt\": \"" OAUTH2_INIT_PROMPT "\", " \ + "\"linkPrompt\": \"" OAUTH2_LINK_PROMPT "\", " \ + "\"uri\": \"short.url.com/tmp\", \"code\": \"1234-5678\", " \ + "\"timeout\": 300}" +#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 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 MECHANISMS_PASSKEY "{" BASIC_PASSKEY "}" +#define PRIORITY_ALL "[\"smartcard\", \"passkey\", \"eidp\", \"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\"]}}" +#define AUTH_SELECTION_PASSKEY "{\"authSelection\": {\"mechanisms\": " \ + MECHANISMS_PASSKEY ", " \ + "\"priority\": [\"passkey\"]}}" +#define AUTH_SELECTION_ALL "{\"authSelection\": {\"mechanisms\": {" \ + BASIC_PASSWORD ", " \ + BASIC_OAUTH2 ", " \ + MULTIPLE_SC ", " \ + BASIC_PASSKEY "}, " \ + "\"priority\": " PRIORITY_ALL "}}" + +#define PASSWORD_CONTENT "{\"password\": \"ThePassword\"}" +#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 "}}" +#define AUTH_MECH_REPLY_OAUTH2 "{\"authSelection\": {" \ + "\"status\": \"Ok\", \"eidp\": {}}}" +#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\": {}}}" + +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; +}; + +/*********************** + * 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->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->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; + 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 + **********************/ +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_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_data(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; + 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->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_data(test_ctx, cert_list, auth_data); + assert_int_equal(ret, EOK); + 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); +} + +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; + + 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; + + 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) +{ + struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data); + json_t *mechs = NULL; + char *string; + int ret; + + auth_data->sc->enabled = true; + 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->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); + + string = json_dumps(mechs, 0); + assert_string_equal(string, MECHANISMS_SC1); + + 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) +{ + struct auth_data *auth_data = talloc_get_type_abort(*state, struct auth_data); + json_t *mechs = NULL; + char *string; + int ret; + + auth_data->sc->enabled = true; + 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] = 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->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); + + string = json_dumps(mechs, 0); + assert_string_equal(string, MECHANISMS_SC2); + + 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_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); + json_t *priority = NULL; + char *string; + int ret; + + 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; + auth_data->passkey->enabled = true; + + will_return_count(__wrap_json_array_append_new, false, 4); + ret = json_format_priority(auth_data, &priority); + assert_int_equal(ret, EOK); + + string = json_dumps(priority, 0); + assert_string_equal(string, PRIORITY_ALL); + + json_decref(priority); + free(string); + 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 = 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, auth_data, &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) +{ + 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, auth_data, &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 = 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->sc->enabled = true; + 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] = 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->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); + 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->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); +} + +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; + 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); + 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_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] = 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->pin_prompt = discard_const(SC_PIN_PROMPT); + 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); + + 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); +} + +void test_json_format_auth_selection_failure(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->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_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] = 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->pin_prompt = discard_const(SC_PIN_PROMPT); + + will_return(__wrap_json_array_append_new, true); + 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->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); +} + +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; + + test_ctx = talloc_new(NULL); + assert_non_null(test_ctx); + 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)); + 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); + 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)); + + 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); + + pc_list_free(pc_list); + 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_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(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_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; + 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_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); + + 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); + + 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_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; + 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); +} + +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) +{ + /* + * 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_get_cert_list), + 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), + 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_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), + 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. */ + 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/tests/cmocka/test_prompt_config.c b/src/tests/cmocka/test_prompt_config.c index 0b761ae4c31..99f2ebc4bf2 100644 --- a/src/tests/cmocka/test_prompt_config.c +++ b/src/tests/cmocka/test_prompt_config.c @@ -100,6 +100,40 @@ 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_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; @@ -116,15 +150,27 @@ 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 = 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, 57); + assert_int_equal(len, 96); #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, "\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\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\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); @@ -146,10 +192,16 @@ 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 = 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, 57); + assert_int_equal(len, 96); pc_list = NULL; @@ -171,7 +223,17 @@ 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_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); } @@ -190,6 +252,8 @@ 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_pc_list_add_smartcard), cmocka_unit_test(test_pam_get_response_prompt_config), cmocka_unit_test(test_pc_list_from_response), }; 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..ce4d328c781 --- /dev/null +++ b/src/tests/cmocka/test_sss_pam_data.c @@ -0,0 +1,244 @@ +/* + 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:" + +#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 + **********************/ +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); +} + +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) +{ + /* + * 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), + 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. */ + 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/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. diff --git a/src/util/sss_pam_data.c b/src/util/sss_pam_data.c index f09b9c5eb2c..da7a9f19f3c 100644 --- a/src/util/sss_pam_data.c +++ b/src/util/sss_pam_data.c @@ -203,3 +203,103 @@ 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; +} + +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 e9b90a8a4e5..fa83c4a1cf2 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; }; /** @@ -96,4 +98,40 @@ 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); + +/** + * @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_ */