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_ */