From 3b87a716a2ddb11226d8acdc99098754b0fb9875 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 9 Oct 2024 10:42:25 +0200 Subject: [PATCH 1/6] man: add `sssd-passkey.5` Include a new man page for passkey to explain the behaviour of `user_verification` option in the different scenarios. It is a complex option, so it has been decided to add a table to simplify its understanding. :packaging: Install new sssd-passkey man page Signed-off-by: Iker Pedrosa --- contrib/sssd.spec.in | 4 ++ src/man/Makefile.am | 4 ++ src/man/include/seealso.xml | 6 ++ src/man/po/po4a.cfg | 1 + src/man/sssd-passkey.5.xml | 128 ++++++++++++++++++++++++++++++++++++ src/man/sssd.conf.5.xml | 15 +++-- 6 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 src/man/sssd-passkey.5.xml diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 871bdb566b2..4d69a9f8c52 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -640,6 +640,9 @@ do sss-certmap*) echo \%lang\(${lang}\) \%{_mandir}/${man}\* >> libsss_certmap.lang ;; + sssd-passkey*) + echo \%lang\(${lang}\) \%{_mandir}/${man}\* >> sssd_passkey.lang + ;; *) echo \%lang\(${lang}\) \%{_mandir}/${man}\* >> sssd.lang ;; @@ -960,6 +963,7 @@ install -D -p -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/sssd.conf %{_datadir}/sssd/krb5-snippets/sssd_enable_passkey %{_udevrulesdir}/90-sssd-token-access.rules %config(noreplace) %{_sysconfdir}/krb5.conf.d/sssd_enable_passkey +%{_mandir}/man5/sssd-passkey.5* %pre common ! getent passwd sssd >/dev/null || usermod sssd -d /run/sssd >/dev/null 2>&1 || true diff --git a/src/man/Makefile.am b/src/man/Makefile.am index 4e1cbd311ed..3880c669f1f 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -120,6 +120,10 @@ if BUILD_ID_PROVIDER_IDP man_MANS += sssd-idp.5 endif +if BUILD_PASSKEY +man_MANS += sssd-passkey.5 +endif + $(builddir)/src/man/sssd_user_name.include: @mkdir -p $(builddir)/src/man @echo -n $(SSSD_USER) > $(builddir)/src/man/sssd_user_name.include diff --git a/src/man/include/seealso.xml b/src/man/include/seealso.xml index 2b79668d490..410c82110d0 100644 --- a/src/man/include/seealso.xml +++ b/src/man/include/seealso.xml @@ -37,6 +37,12 @@ 5 , + + + sssd-passkey + 5 + , + sssd-session-recording 5 diff --git a/src/man/po/po4a.cfg b/src/man/po/po4a.cfg index 41af1c22159..94dc729f3c4 100644 --- a/src/man/po/po4a.cfg +++ b/src/man/po/po4a.cfg @@ -29,6 +29,7 @@ [type:docbook] sssd-systemtap.5.xml $lang:$(builddir)/$lang/sssd-systemtap.5.xml [type:docbook] sssd-ldap-attributes.5.xml $lang:$(builddir)/$lang/sssd-ldap-attributes.5.xml [type:docbook] sssd_krb5_localauth_plugin.8.xml $lang:$(builddir)/$lang/sssd_krb5_localauth_plugin.8.xml +[type:docbook] sssd-passkey.5.xml $lang:$(builddir)/$lang/sssd-passkey.5.xml [type:docbook] include/autofs_attributes.xml $lang:$(builddir)/$lang/include/autofs_attributes.xml opt:"-k 0" [type:docbook] include/service_discovery.xml $lang:$(builddir)/$lang/include/service_discovery.xml opt:"-k 0" [type:docbook] include/upstream.xml $lang:$(builddir)/$lang/include/upstream.xml opt:"-k 0" diff --git a/src/man/sssd-passkey.5.xml b/src/man/sssd-passkey.5.xml new file mode 100644 index 00000000000..bcaff71f703 --- /dev/null +++ b/src/man/sssd-passkey.5.xml @@ -0,0 +1,128 @@ + + + +SSSD Manual pages + + + + + sssd-passkey + 5 + File Formats and Conventions + + + + sssd-passkey + SSSD passkey options + + + + DESCRIPTION + + This manual page describes the specifics for configuration of + passkey for + + sssd + 8 + . + Refer to the FILE FORMAT section of the + + sssd.conf + 5 + manual page for detailed syntax information. + + + + CONFIGURATION OPTIONS + + + + user_verification (boolean) + + + Enable or disable the requirement for user + verification (i.e. PIN, fingerprint) on the passkey + device during authentication. + + + Three different actors come into play when deciding + whether to request user verification: LDAP server, + sssd.conf + 5 + option and the passkey device itself. + + + If the IPA provider is used and online, Kerberos + based passkey authentication is available, the + server-side settings are applied for all passkey + usages. For other cases the settings in + sssd.conf are used. This + includes passkey authentication with any other + provider; and the IPA provider in case online + authentication is not available and local passkey + is allowed by the option + local_auth_policy = enable:passkey. + + + The interaction of the + user_verification option and + the passkey device option is explained in the + following table: + + + + + + + + + user_verificationDevice + Result + + + + True + User verification is configured + User verification is requested + + + True + User verification is not configured + + User verification is requested; however, the + authentication is expected to fail if the device is + not replaced with a device where user verification + is configured during the authentication process. + + + + False + User verification is configured + + sssd automatically detects it during device query + and user verification is requested + + + + False + User verification is not configured + User verification is not requested + + + + If 'enter' is pressed at the PIN prompt for user + verification without typing any characters, then + SSSD falls back from passkey to password + authentication. + + + + + + + + + + + diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 1a2d761af39..6afc965bf42 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -682,15 +682,16 @@ Enable or disable the user verification (i.e. PIN, fingerprint) - during authentication. If enabled, the - PIN will always be requested. + during authentication. - The default is that the key settings - decide what to do. In the IPA or - kerberos pre-authentication case, - this value will be overwritten by the - server. + See + + sssd-passkey + 5 + to + understand the behaviour of this option + in the different scenarios. From 41c85cf6af9c83bd115b093e2e2b07d92441a313 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 20 Sep 2024 09:52:50 +0200 Subject: [PATCH 2/6] passkey: timeout argument refactor Timeout to search for a device should be imposed by the action that wants to be performed. Thus, refactor the high level functions and list_devices() to take it as an argument. Signed-off-by: Iker Pedrosa --- src/passkey_child/passkey_child.c | 6 +++--- src/passkey_child/passkey_child.h | 15 ++++++++++----- src/passkey_child/passkey_child_common.c | 16 ++++++++-------- src/passkey_child/passkey_child_devices.c | 6 +++--- src/tests/cmocka/test_passkey_child.c | 14 +++++++------- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c index 320996608ec..ef9b06c65aa 100644 --- a/src/passkey_child/passkey_child.c +++ b/src/passkey_child/passkey_child.c @@ -64,13 +64,13 @@ int main(int argc, const char *argv[]) fido_init(init_flags); if (data.action == ACTION_REGISTER) { - ret = register_key(&data); + ret = register_key(&data, TIMEOUT); if (ret != EOK) { ERROR("Error registering key.\n"); goto done; } } else if (data.action == ACTION_AUTHENTICATE) { - ret = authenticate(&data); + ret = authenticate(&data, TIMEOUT); if (ret == EOK) { PRINT("Authentication success.\n"); goto done; @@ -79,7 +79,7 @@ int main(int argc, const char *argv[]) goto done; } } else if (data.action == ACTION_GET_ASSERT) { - ret = get_assert_data(&data); + ret = get_assert_data(&data, TIMEOUT); if (ret != EOK) { ERROR("Error getting assertion data.\n"); goto done; diff --git a/src/passkey_child/passkey_child.h b/src/passkey_child/passkey_child.h index 185e7e63af7..853dd44d7e5 100644 --- a/src/passkey_child/passkey_child.h +++ b/src/passkey_child/passkey_child.h @@ -103,12 +103,13 @@ check_arguments(const struct passkey_data *data); * @brief Register a key for a user * * @param[in] data passkey data + * @param[in] timeout Timeout to stop looking for a device * * @return 0 if the key was registered properly, * another value on error. */ errno_t -register_key(struct passkey_data *data); +register_key(struct passkey_data *data, int timeout); /** * @brief Translate COSE type from string to int @@ -139,13 +140,14 @@ prepare_credentials(struct passkey_data *data, fido_dev_t *dev, /** * @brief List connected passkey devices * + * @param[in] timeout Timeout to stop looking for a device * @param[out] dev_list passkey device list * @param[out] dev_number Number of passkey devices * * @return 0 if the list was retrieved properly, another value on error. */ errno_t -list_devices(fido_dev_info_t *dev_list, size_t *dev_number); +list_devices(int timeout, fido_dev_info_t *dev_list, size_t *dev_number); /** * @brief Select passkey device @@ -322,18 +324,20 @@ public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, * key, request the assert and verify it. * * @param[in] data passkey data + * @param[in] timeout Timeout to stop looking for a device * * @return 0 if the user was authenticated properly, * error code otherwise. */ errno_t -authenticate(struct passkey_data *data); +authenticate(struct passkey_data *data, int timeout); /* * @brief Select authenticator for verification * * * @param[in] data passkey data + * @param[in] timeout Timeout to stop looking for a device * @param[out] _dev Device information * @param[out] _assert Assert * @param[out] _index Index for key handle list @@ -342,7 +346,7 @@ authenticate(struct passkey_data *data); * error code otherwise. */ errno_t -select_authenticator(struct passkey_data *data, fido_dev_t **_dev, +select_authenticator(struct passkey_data *data, int timeout, fido_dev_t **_dev, fido_assert_t **_assert, int *_index); /** @@ -533,12 +537,13 @@ print_assert_data(const char *key_handle, const char *crypto_challenge, * and print this all information. * * @param[in] data passkey data + * @param[in] timeout Timeout to stop looking for a device * * @return 0 if the assertion was obtained properly, * error code otherwise. */ errno_t -get_assert_data(struct passkey_data *data); +get_assert_data(struct passkey_data *data, int timeout); /** * @brief Verify assertion data diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index 7a2cd9d56d5..76c006d5258 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -427,7 +427,7 @@ check_arguments(const struct passkey_data *data) } errno_t -register_key(struct passkey_data *data) +register_key(struct passkey_data *data, int timeout) { TALLOC_CTX *tmp_ctx = NULL; fido_cred_t *cred = NULL; @@ -463,7 +463,7 @@ register_key(struct passkey_data *data) goto done; } - ret = list_devices(dev_list, &dev_number); + ret = list_devices(timeout, dev_list, &dev_number); if (ret != EOK) { goto done; } @@ -574,7 +574,7 @@ public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, } errno_t -select_authenticator(struct passkey_data *data, fido_dev_t **_dev, +select_authenticator(struct passkey_data *data, int timeout, fido_dev_t **_dev, fido_assert_t **_assert, int *_index) { fido_dev_info_t *dev_list = NULL; @@ -592,7 +592,7 @@ select_authenticator(struct passkey_data *data, fido_dev_t **_dev, } DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); - ret = list_devices(dev_list, &dev_list_len); + ret = list_devices(timeout, dev_list, &dev_list_len); if (ret != EOK) { goto done; } @@ -709,7 +709,7 @@ public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) } errno_t -authenticate(struct passkey_data *data) +authenticate(struct passkey_data *data, int timeout) { TALLOC_CTX *tmp_ctx = NULL; fido_assert_t *assert = NULL; @@ -724,7 +724,7 @@ authenticate(struct passkey_data *data) return ENOMEM; } - ret = select_authenticator(data, &dev, &assert, &index); + ret = select_authenticator(data, timeout, &dev, &assert, &index); if (ret != EOK) { goto done; } @@ -782,7 +782,7 @@ authenticate(struct passkey_data *data) } errno_t -get_assert_data(struct passkey_data *data) +get_assert_data(struct passkey_data *data, int timeout) { TALLOC_CTX *tmp_ctx = NULL; fido_dev_t *dev = NULL; @@ -798,7 +798,7 @@ get_assert_data(struct passkey_data *data) return ENOMEM; } - ret = select_authenticator(data, &dev, &assert, &index); + ret = select_authenticator(data, timeout, &dev, &assert, &index); if (ret != EOK) { goto done; } diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c index 2011b12f661..84586f8e543 100644 --- a/src/passkey_child/passkey_child_devices.c +++ b/src/passkey_child/passkey_child_devices.c @@ -28,11 +28,11 @@ #include "passkey_child.h" errno_t -list_devices(fido_dev_info_t *dev_list, size_t *dev_number) +list_devices(int timeout, fido_dev_info_t *dev_list, size_t *dev_number) { errno_t ret; - for (int i = 0; i < TIMEOUT; i += FREQUENCY) { + for (int i = 0; i < timeout; i += FREQUENCY) { ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); if (ret != FIDO_OK) { DEBUG(SSSDBG_OP_FAILURE, @@ -45,7 +45,7 @@ list_devices(fido_dev_info_t *dev_list, size_t *dev_number) break; } - if (i < (TIMEOUT - 1)) { + if (i < (timeout - 1)) { DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); sleep(FREQUENCY); } diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index 5003152c453..b45ba9008b2 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -658,7 +658,7 @@ void test_list_devices_one_device(void **state) will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, 1); - ret = list_devices(ts->dev_list, &ts->dev_number); + ret = list_devices(TIMEOUT, ts->dev_list, &ts->dev_number); assert_int_equal(ret, FIDO_OK); assert_int_equal(ts->dev_number, 1); @@ -677,7 +677,7 @@ void test_list_devices_no_device(void **state) } } - ret = list_devices(ts->dev_list, &ts->dev_number); + ret = list_devices(TIMEOUT, ts->dev_list, &ts->dev_number); assert_int_equal(ret, FIDO_OK); assert_int_equal(ts->dev_number, 0); @@ -696,7 +696,7 @@ void test_list_devices_error(void **state) } } - ret = list_devices(ts->dev_list, &ts->dev_number); + ret = list_devices(TIMEOUT, ts->dev_list, &ts->dev_number); assert_int_equal(ret, FIDO_ERR_INVALID_ARGUMENT); } @@ -907,7 +907,7 @@ void test_register_key_integration(void **state) will_return(__wrap_fido_cred_pubkey_ptr, TEST_ES256_HEX_PUBLIC_KEY); will_return(__wrap_fido_cred_pubkey_len, 64); - ret = register_key(&data); + ret = register_key(&data, TIMEOUT); assert_int_equal(ret, EOK); } @@ -941,7 +941,7 @@ void test_select_authenticator(void **state) will_return(__wrap_fido_dev_is_fido2, true); will_return(__wrap_fido_dev_get_assert, FIDO_OK); - ret = select_authenticator(&data, &dev, &assert, &index); + ret = select_authenticator(&data, TIMEOUT, &dev, &assert, &index); assert_int_equal(ret, FIDO_OK); @@ -1244,7 +1244,7 @@ void test_authenticate_integration(void **state) will_return(__wrap_fido_assert_set_uv, FIDO_OK); will_return(__wrap_fido_assert_verify, FIDO_OK); - ret = authenticate(&data); + ret = authenticate(&data, TIMEOUT); assert_int_equal(ret, EOK); talloc_free(tmp_ctx); @@ -1297,7 +1297,7 @@ void test_get_assert_data_integration(void **state) will_return(__wrap_fido_assert_sig_ptr, TEST_HEX_SIGNATURE); will_return(__wrap_fido_assert_sig_len, TEST_SIGNATURE_LEN); - ret = get_assert_data(&data); + ret = get_assert_data(&data, TIMEOUT); assert_int_equal(ret, EOK); talloc_free(tmp_ctx); From db35aa0ccf062e686446423ad5e37aeb0c300363 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 5 Aug 2025 10:18:10 +0200 Subject: [PATCH 3/6] passkey: refactor list_devices() Account both for time consumed by `fido_dev_info_manifest()` and `sleep()`, which can return in no time if interrupted. Signed-off-by: Iker Pedrosa --- Makefile.am | 1 + src/passkey_child/passkey_child_devices.c | 9 +++--- src/tests/cmocka/test_passkey_child.c | 34 +++++++++++++++++++---- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Makefile.am b/Makefile.am index 173f88800a9..3b471819a65 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3774,6 +3774,7 @@ test_passkey_CFLAGS = \ $(NULL) test_passkey_LDFLAGS = \ -Wl,-wrap,sleep \ + -Wl,-wrap,time \ -Wl,-wrap,tcgetattr \ -Wl,-wrap,tcsetattr \ -Wl,-wrap,getline \ diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c index 84586f8e543..329688ddbbf 100644 --- a/src/passkey_child/passkey_child_devices.c +++ b/src/passkey_child/passkey_child_devices.c @@ -30,9 +30,10 @@ errno_t list_devices(int timeout, fido_dev_info_t *dev_list, size_t *dev_number) { + time_t end_time = time(NULL) + timeout; errno_t ret; - for (int i = 0; i < timeout; i += FREQUENCY) { + while (time(NULL) < end_time) { ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); if (ret != FIDO_OK) { DEBUG(SSSDBG_OP_FAILURE, @@ -45,10 +46,8 @@ list_devices(int timeout, fido_dev_info_t *dev_list, size_t *dev_number) break; } - if (i < (timeout - 1)) { - DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); - sleep(FREQUENCY); - } + DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); + sleep(FREQUENCY); } return ret; diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index b45ba9008b2..002c2e15642 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -193,6 +193,16 @@ __wrap_sleep(unsigned int seconds) return ret; } +unsigned int +__wrap_time(time_t *time) +{ + int ret; + + ret = mock(); + + return ret; +} + int __wrap_tcgetattr(int fd, struct termios *termios_p) { @@ -655,8 +665,10 @@ void test_list_devices_one_device(void **state) struct test_state *ts = talloc_get_type_abort(*state, struct test_state); errno_t ret; + will_return(__wrap_time, 0); will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, 1); + will_return(__wrap_time, 0); ret = list_devices(TIMEOUT, ts->dev_list, &ts->dev_number); @@ -669,13 +681,14 @@ void test_list_devices_no_device(void **state) struct test_state *ts = talloc_get_type_abort(*state, struct test_state); errno_t ret; + will_return(__wrap_time, 0); for (int i = 0; i < TIMEOUT; i += FREQUENCY) { will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, 0); - if (i < (TIMEOUT - 1)) { - will_return(__wrap_sleep, 0); - } + will_return(__wrap_sleep, 0); + will_return(__wrap_time, 0); } + will_return(__wrap_time, 30); ret = list_devices(TIMEOUT, ts->dev_list, &ts->dev_number); @@ -688,13 +701,14 @@ void test_list_devices_error(void **state) struct test_state *ts = talloc_get_type_abort(*state, struct test_state); errno_t ret; + will_return(__wrap_time, 0); for (int i = 0; i < TIMEOUT; i += FREQUENCY) { will_return(__wrap_fido_dev_info_manifest, FIDO_ERR_INVALID_ARGUMENT); will_return(__wrap_fido_dev_info_manifest, 0); - if (i < (TIMEOUT - 1)) { - will_return(__wrap_sleep, 0); - } + will_return(__wrap_sleep, 0); + will_return(__wrap_time, 0); } + will_return(__wrap_time, 30); ret = list_devices(TIMEOUT, ts->dev_list, &ts->dev_number); @@ -892,6 +906,8 @@ void test_register_key_integration(void **state) data.cred_type = CRED_SERVER_SIDE; data.mapping_file = NULL; data.quiet = false; + will_return(__wrap_time, 0); + will_return(__wrap_time, 0); will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, 1); will_return(__wrap_fido_dev_info_path, TEST_PATH); @@ -930,6 +946,8 @@ void test_select_authenticator(void **state) data.key_handle_list = &key_handle; data.key_handle_size = 1; data.crypto_challenge = TEST_CRYPTO_CHALLENGE; + will_return(__wrap_time, 0); + will_return(__wrap_time, 0); will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, 1); will_return(__wrap_fido_assert_set_rp, FIDO_OK); @@ -1217,6 +1235,8 @@ void test_authenticate_integration(void **state) data.user_verification = FIDO_OPT_FALSE; data.user_id = NULL; data.quiet = false; + will_return(__wrap_time, 0); + will_return(__wrap_time, 0); will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, dev_number); will_return(__wrap_fido_assert_set_rp, FIDO_OK); @@ -1268,6 +1288,8 @@ void test_get_assert_data_integration(void **state) data.crypto_challenge = TEST_CRYPTO_CHALLENGE; data.user_verification = FIDO_OPT_FALSE; data.user_id = NULL; + will_return(__wrap_time, 0); + will_return(__wrap_time, 0); will_return(__wrap_fido_dev_info_manifest, FIDO_OK); will_return(__wrap_fido_dev_info_manifest, dev_number); will_return(__wrap_fido_assert_set_rp, FIDO_OK); From ef8153245d0c2aba97aed27358d16fbb9a1210a7 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 3 Oct 2024 11:51:25 +0200 Subject: [PATCH 4/6] passkey: implement preflight option Expand the passkey_child to detect whether a FIDO2 device needs a PIN and the number of attempts left for the PIN. This is needed to improve the overall user experience by providing a way for SSSD to detect these features before the user is requested to interact with them. Expected input to run passkey_child: `--preflight`, `--domain` and `--key-handle`. Output: whether PIN is needed (boolean) and number of PIN attempts left (integer) in JSON. Example: {"pin_required": true, "attempts": 8} Signed-off-by: Iker Pedrosa --- Makefile.am | 3 +- src/passkey_child/passkey_child.c | 6 ++ src/passkey_child/passkey_child.h | 47 ++++++++- src/passkey_child/passkey_child_assert.c | 3 +- src/passkey_child/passkey_child_common.c | 65 +++++++++++++ src/passkey_child/passkey_child_credentials.c | 38 ++++++++ src/passkey_child/passkey_child_devices.c | 24 ++++- src/tests/cmocka/test_passkey_child.c | 96 +++++++++++++++++++ 8 files changed, 278 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index 3b471819a65..cc4429d161b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3806,7 +3806,8 @@ test_passkey_LDFLAGS = \ -Wl,-wrap,fido_assert_sig_len \ -Wl,-wrap,fido_assert_set_count \ -Wl,-wrap,fido_assert_set_authdata \ - -Wl,-wrap,fido_assert_set_sig + -Wl,-wrap,fido_assert_set_sig \ + -Wl,-wrap,fido_dev_get_retry_count test_passkey_LDADD = \ $(CMOCKA_LIBS) \ $(SSSD_LIBS) \ diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c index ef9b06c65aa..0e9f7220875 100644 --- a/src/passkey_child/passkey_child.c +++ b/src/passkey_child/passkey_child.c @@ -93,6 +93,12 @@ int main(int argc, const char *argv[]) ERROR("Verification error.\n"); goto done; } + } else if (data.action == ACTION_PREFLIGHT) { + ret = preflight(&data, 1); + /* Errors are ignored, as in most cases they are due to the device not + * being connected to the system. If an error occurs, the default + * values are returned, and that is sufficient for the time being. + */ } done: diff --git a/src/passkey_child/passkey_child.h b/src/passkey_child/passkey_child.h index 853dd44d7e5..e91bf032ec6 100644 --- a/src/passkey_child/passkey_child.h +++ b/src/passkey_child/passkey_child.h @@ -34,13 +34,15 @@ #define USER_ID_SIZE 32 #define TIMEOUT 15 #define FREQUENCY 1 +#define MAX_PIN_RETRIES 8 enum action_opt { ACTION_NONE, ACTION_REGISTER, ACTION_AUTHENTICATE, ACTION_GET_ASSERT, - ACTION_VERIFY_ASSERT + ACTION_VERIFY_ASSERT, + ACTION_PREFLIGHT }; enum credential_type { @@ -559,4 +561,47 @@ get_assert_data(struct passkey_data *data, int timeout); errno_t verify_assert_data(struct passkey_data *data); +/** + * @brief Obtain PIN retries in the device + * + * @param[in] dev Device information + * @param[in] data passkey data + * @param[in] _pin_retries Number of PIN retries + * + * @return 0 if the PIN retries were obtained properly, + * error code otherwise. + */ +errno_t +get_device_pin_retries(fido_dev_t *dev, const struct passkey_data *data, + int *_pin_retries); + +/** + * @brief Print preflight information + * + * Print user-verification and pin retries + * + * @param[in] data passkey data + * @param[in] _pin_retries Number of PIN retries + * + * @return EOK + * + */ +errno_t +print_preflight(const struct passkey_data *data, int pin_retries); + +/** + * @brief Obtain authentication data prior to processing + * + * Prepare the assertion request data, select the device to use, get the device + * options and compare them with the organization policy, get the PIN retries + * and print the preflight data. + * + * @param[in] data passkey data + * @param[in] timeout Timeout in seconds to stop looking for a device + * + * @return EOK + */ +errno_t +preflight(struct passkey_data *data, int timeout); + #endif /* __PASSKEY_CHILD_H__ */ diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c index 5139dc82ef0..7920e6fd7fb 100644 --- a/src/passkey_child/passkey_child_assert.c +++ b/src/passkey_child/passkey_child_assert.c @@ -51,7 +51,8 @@ set_assert_client_data_hash(const struct passkey_data *data, return ENOMEM; } - if (data->action == ACTION_AUTHENTICATE) { + if (data->action == ACTION_AUTHENTICATE + || data->action == ACTION_PREFLIGHT) { ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c index 76c006d5258..1f588542446 100644 --- a/src/passkey_child/passkey_child_common.c +++ b/src/passkey_child/passkey_child_common.c @@ -180,6 +180,8 @@ parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], _("Obtain assertion data"), NULL }, {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', _("Verify assertion data"), NULL }, + {"preflight", 0, POPT_ARG_NONE, NULL, 'p', + _("Obtain authentication data prior to processing"), NULL }, {"username", 0, POPT_ARG_STRING, &data->shortname, 0, _("Shortname"), NULL }, {"domain", 0, POPT_ARG_STRING, &data->domain, 0, @@ -262,6 +264,17 @@ parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], } data->action = ACTION_VERIFY_ASSERT; break; + case 'p': + if (data->action != ACTION_NONE + && data->action != ACTION_PREFLIGHT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_PREFLIGHT; + break; case 'q': data->quiet = true; break; @@ -422,6 +435,14 @@ check_arguments(const struct passkey_data *data) goto done; } + if (data->action == ACTION_PREFLIGHT + && (data->domain == NULL || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for preflight action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + done: return ret; } @@ -890,3 +911,47 @@ verify_assert_data(struct passkey_data *data) return ret; } + +errno_t +preflight(struct passkey_data *data, int timeout) +{ + fido_assert_t *assert = NULL; + fido_dev_t *dev = NULL; + int index = 0; + int pin_retries = 0; + errno_t ret; + + ret = select_authenticator(data, timeout, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking the number of remaining PIN retries.\n"); + ret = get_device_pin_retries(dev, data, &pin_retries); + if (ret != FIDO_OK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + data->user_verification = FIDO_OPT_TRUE; + pin_retries = MAX_PIN_RETRIES; + } + print_preflight(data, pin_retries); + + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + + return EOK; +} diff --git a/src/passkey_child/passkey_child_credentials.c b/src/passkey_child/passkey_child_credentials.c index 82a8cf58b0a..d5659ead748 100644 --- a/src/passkey_child/passkey_child_credentials.c +++ b/src/passkey_child/passkey_child_credentials.c @@ -23,6 +23,7 @@ */ #include +#include #include #include @@ -678,3 +679,40 @@ evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) done: return ret; } + +errno_t +print_preflight(const struct passkey_data *data, int pin_retries) +{ + json_t *jroot = NULL; + char* string = NULL; + bool user_verification; + + if (data->user_verification == FIDO_OPT_TRUE) { + user_verification = true; + } else { + user_verification = false; + } + + jroot = json_pack("{s:b, s:i}", + "pin_required", user_verification, + "attempts", pin_retries); + + if (jroot == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create jroot object.\n"); + goto done; + } + + string = json_dumps(jroot, 0); + if (string == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); + goto done; + } + + puts(string); + free(string); + +done: + json_decref(jroot); + + return EOK; +} diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c index 329688ddbbf..f601a5987a0 100644 --- a/src/passkey_child/passkey_child_devices.c +++ b/src/passkey_child/passkey_child_devices.c @@ -31,7 +31,7 @@ errno_t list_devices(int timeout, fido_dev_info_t *dev_list, size_t *dev_number) { time_t end_time = time(NULL) + timeout; - errno_t ret; + errno_t ret = EOK; while (time(NULL) < end_time) { ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); @@ -243,3 +243,25 @@ get_device_options(fido_dev_t *dev, struct passkey_data *_data) return ret; } + +errno_t +get_device_pin_retries(fido_dev_t *dev, const struct passkey_data *data, + int *_pin_retries) +{ + int ret = EOK; + + if (data->user_verification == FIDO_OPT_TRUE) { + ret = fido_dev_get_retry_count(dev, _pin_retries); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_dev_get_retry_count failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else { + *_pin_retries = MAX_PIN_RETRIES; + } + +done: + return ret; +} diff --git a/src/tests/cmocka/test_passkey_child.c b/src/tests/cmocka/test_passkey_child.c index 002c2e15642..b404153116f 100644 --- a/src/tests/cmocka/test_passkey_child.c +++ b/src/tests/cmocka/test_passkey_child.c @@ -534,6 +534,17 @@ __wrap_fido_assert_set_sig(fido_assert_t *assert, size_t idx, return ret; } +int +__wrap_fido_dev_get_retry_count(fido_dev_t *dev, int *pin_retries) +{ + int ret; + + ret = mock(); + (*pin_retries) = mock(); + + return ret; +} + /*********************** * TEST **********************/ @@ -1361,6 +1372,38 @@ void test_verify_assert_data_integration(void **state) talloc_free(tmp_ctx); } +void test_get_device_pin_retries_success(void **state) +{ + struct passkey_data data; + fido_dev_t *dev = NULL; + int pin_retries = 0; + errno_t ret; + + data.user_verification = FIDO_OPT_TRUE; + will_return(__wrap_fido_dev_get_retry_count, FIDO_OK); + will_return(__wrap_fido_dev_get_retry_count, 8); + + ret = get_device_pin_retries(dev, &data, &pin_retries); + assert_int_equal(ret, FIDO_OK); + assert_int_equal(pin_retries, 8); +} + +void test_get_device_pin_retries_failure(void **state) +{ + struct passkey_data data; + fido_dev_t *dev = NULL; + int pin_retries = 0; + errno_t ret; + + data.user_verification = FIDO_OPT_TRUE; + will_return(__wrap_fido_dev_get_retry_count, FIDO_ERR_INVALID_ARGUMENT); + will_return(__wrap_fido_dev_get_retry_count, 8); + + ret = get_device_pin_retries(dev, &data, &pin_retries); + assert_int_equal(ret, FIDO_ERR_INVALID_ARGUMENT); + assert_int_equal(pin_retries, 8); +} + static void test_parse_supp_valgrind_args(void) { /* @@ -1371,6 +1414,56 @@ static void test_parse_supp_valgrind_args(void) DEBUG_CLI_INIT(debug_level); } +void test_preflight_integration(void **state) +{ + TALLOC_CTX *tmp_ctx; + struct passkey_data data; + size_t dev_number = 3; + char *key_handle; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + assert_non_null(tmp_ctx); + data.action = ACTION_PREFLIGHT; + data.shortname = "user"; + data.domain = "test.com"; + key_handle = talloc_strdup(tmp_ctx, TEST_KEY_HANDLE); + data.key_handle_list = &key_handle; + data.key_handle_size = 1; + data.type = COSE_ES256; + data.user_verification = FIDO_OPT_TRUE; + data.user_id = NULL; + data.quiet = false; + will_return(__wrap_time, 0); + will_return(__wrap_time, 0); + will_return(__wrap_fido_dev_info_manifest, FIDO_OK); + will_return(__wrap_fido_dev_info_manifest, dev_number); + will_return(__wrap_fido_assert_set_rp, FIDO_OK); + will_return(__wrap_fido_assert_allow_cred, FIDO_OK); + will_return(__wrap_fido_assert_set_uv, FIDO_OK); + will_return(__wrap_fido_assert_set_clientdata_hash, FIDO_OK); + for (size_t i = 0; i < (dev_number - 1); i++) { + will_return(__wrap_fido_dev_info_path, TEST_PATH); + will_return(__wrap_fido_dev_open, FIDO_OK); + will_return(__wrap_fido_dev_is_fido2, true); + if (i == 0) { + will_return(__wrap_fido_dev_get_assert, FIDO_ERR_INVALID_SIG); + } else { + will_return(__wrap_fido_dev_get_assert, FIDO_OK); + } + } + will_return(__wrap_fido_dev_has_uv, false); + will_return(__wrap_fido_dev_has_pin, true); + will_return(__wrap_fido_dev_supports_uv, false); + will_return(__wrap_fido_dev_get_retry_count, FIDO_OK); + will_return(__wrap_fido_dev_get_retry_count, 8); + + ret = preflight(&data, 1); + assert_int_equal(ret, EOK); + + talloc_free(tmp_ctx); +} + int main(int argc, const char *argv[]) { poptContext pc; @@ -1418,6 +1511,9 @@ int main(int argc, const char *argv[]) cmocka_unit_test(test_authenticate_integration), cmocka_unit_test(test_get_assert_data_integration), cmocka_unit_test(test_verify_assert_data_integration), + cmocka_unit_test(test_get_device_pin_retries_success), + cmocka_unit_test(test_get_device_pin_retries_failure), + cmocka_unit_test(test_preflight_integration), }; /* Set debug level to invalid value so we can decide if -d 0 was used. */ From 1fa9640aaaca4db68efdfc490c04a6d273303e32 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 2 Jun 2025 12:27:01 +0200 Subject: [PATCH 5/6] passkey: propagate error code for power cycle When performing a passkey authentication and the PIN is entered 3 times incorrectly the FIDO2 device requires a power cycle (disconnect and reconnect the device to the USB port). libfido2 recognizes this with a special error code and the passkey child should return it so that the SSSD responder is aware of it. Signed-off-by: Iker Pedrosa --- src/passkey_child/passkey_child.c | 4 +++- src/util/util.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c index 0e9f7220875..1fe0076faa3 100644 --- a/src/passkey_child/passkey_child.c +++ b/src/passkey_child/passkey_child.c @@ -104,7 +104,9 @@ int main(int argc, const char *argv[]) done: talloc_free(main_ctx); - if (ret != EOK) { + if (ret == FIDO_ERR_PIN_AUTH_BLOCKED) { + return PIN_AUTH_BLOCKED_EXIT_CODE; + } else if (ret != EOK) { return EXIT_FAILURE; } else { return EXIT_SUCCESS; diff --git a/src/util/util.h b/src/util/util.h index 2346c1e18ea..4d50b4d915c 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -122,6 +122,7 @@ extern int socket_activated; enum sssd_exit_status { CHILD_TIMEOUT_EXIT_CODE = 7, CA_DB_NOT_FOUND_EXIT_CODE = 50, + PIN_AUTH_BLOCKED_EXIT_CODE = 52, /* to match FIDO_ERR_PIN_AUTH_BLOCKED in fido2 error codes */ SSS_WATCHDOG_EXIT_CODE = 70 /* to match EX_SOFTWARE in sysexits.h */ }; From 2b2bd2ad4356bc27363913cba897ea3cae1b572c Mon Sep 17 00:00:00 2001 From: Justin Stephenson Date: Tue, 20 May 2025 09:15:40 -0400 Subject: [PATCH 6/6] passkey: Add preflight operation Also refactor PAM passkey child related operations --- Makefile.am | 10 +- src/krb5_plugin/passkey/passkey.h | 6 + src/krb5_plugin/passkey/passkey_utils.c | 37 + src/responder/pam/pamsrv.h | 1 + src/responder/pam/pamsrv_cmd.c | 44 +- src/responder/pam/pamsrv_passkey.c | 911 +++++++++++++++++------- src/responder/pam/pamsrv_passkey.h | 41 +- src/sss_client/pam_message.h | 1 + src/sss_client/pam_sss.c | 19 +- src/sss_client/sss_cli.h | 6 +- src/tests/cmocka/test_pam_srv.c | 10 +- 11 files changed, 788 insertions(+), 298 deletions(-) diff --git a/Makefile.am b/Makefile.am index cc4429d161b..6704009bd27 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1523,9 +1523,11 @@ sssd_pam_SOURCES = \ src/responder/pam/pam_prompting_config.c \ src/sss_client/pam_sss_prompt_config.c \ src/responder/pam/pam_helpers.c \ + src/krb5_plugin/common/utils.c \ $(SSSD_RESPONDER_OBJ) if BUILD_PASSKEY - sssd_pam_SOURCES += src/responder/pam/pamsrv_passkey.c + sssd_pam_SOURCES += src/responder/pam/pamsrv_passkey.c \ + src/krb5_plugin/passkey/passkey_utils.c endif sssd_pam_CFLAGS = \ $(AM_CFLAGS) \ @@ -1535,6 +1537,7 @@ sssd_pam_LDADD = \ $(LIBADD_DL) \ $(TDB_LIBS) \ $(SSSD_LIBS) \ + $(JANSSON_LIBS) \ $(SELINUX_LIBS) \ $(PAM_LIBS) \ $(GSSAPI_KRB5_LIBS) \ @@ -2570,6 +2573,7 @@ pam_srv_tests_SOURCES = \ src/responder/pam/pamsrv_dp.c \ src/responder/pam/pam_prompting_config.c \ src/sss_client/pam_sss_prompt_config.c \ + src/krb5_plugin/common/utils.c \ $(NULL) pam_srv_tests_CFLAGS = \ -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \ @@ -2591,6 +2595,7 @@ pam_srv_tests_LDADD = \ $(PAM_LIBS) \ $(SSSD_LIBS) \ $(SSSD_INTERNAL_LTLIBS) \ + $(JANSSON_LIBS) \ $(GSSAPI_KRB5_LIBS) \ libsss_test_common.la \ libsss_idmap.la \ @@ -2599,7 +2604,8 @@ pam_srv_tests_LDADD = \ libsss_sbus.la \ $(NULL) if BUILD_PASSKEY - pam_srv_tests_SOURCES += src/responder/pam/pamsrv_passkey.c + pam_srv_tests_SOURCES += src/responder/pam/pamsrv_passkey.c \ + src/krb5_plugin/passkey/passkey_utils.c endif # BUILD_PASSKEY EXTRA_ssh_srv_tests_DEPENDENCIES = \ diff --git a/src/krb5_plugin/passkey/passkey.h b/src/krb5_plugin/passkey/passkey.h index 0f36d5da1af..6ea8cb9a598 100644 --- a/src/krb5_plugin/passkey/passkey.h +++ b/src/krb5_plugin/passkey/passkey.h @@ -22,6 +22,7 @@ #define _PASSKEY_H_ #include +#include #include #ifndef discard_const @@ -100,6 +101,11 @@ sss_passkey_message_encode_padata(const struct sss_passkey_message *data); struct sss_passkey_message * sss_passkey_message_decode_padata(krb5_pa_data *padata); +int +sss_passkey_preflight_from_json(const char *json_str, + bool *_pin_required, + int *_attempts); + krb5_pa_data ** sss_passkey_message_encode_padata_array(const struct sss_passkey_message *data); diff --git a/src/krb5_plugin/passkey/passkey_utils.c b/src/krb5_plugin/passkey/passkey_utils.c index e73da97c53b..89835c0280c 100644 --- a/src/krb5_plugin/passkey/passkey_utils.c +++ b/src/krb5_plugin/passkey/passkey_utils.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -561,6 +562,42 @@ sss_passkey_message_from_reply_json(enum sss_passkey_phase phase, return message; } +int +sss_passkey_preflight_from_json(const char *json_str, + bool *_pin_required, + int *_attempts) +{ + json_t *jroot; + json_error_t jret; + int ret; + bool pin_required; + int attempts; + + jroot = json_loads(json_str, 0, &jret); + if (jroot == NULL) { + return ENOMEM; + } + + ret = json_unpack(jroot, "{s:b, s:i}", + "pin_required", &pin_required, + "attempts", &attempts); + if (ret != 0) { + ret = EINVAL; + goto done; + } + + *_pin_required = pin_required; + *_attempts = attempts; + + ret = 0; +done: + if (jroot != NULL) { + json_decref(jroot); + } + + return ret; +} + char * sss_passkey_message_encode(const struct sss_passkey_message *data) { diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 2aa14ae02ff..4db7c5c3fad 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -106,6 +106,7 @@ struct pam_auth_req { bool initial_cert_auth_successful; bool passkey_data_exists; + struct pam_preflight_data *pam_pf_data; uint32_t client_id_num; }; diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index d6afb8304ab..6be4124588b 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -1270,6 +1270,16 @@ void pam_reply(struct pam_auth_req *preq) local_sc_auth_allow ? "True" : "False", local_passkey_auth_allow ? "True" : "False"); + /* Passkey preflight data */ + if (preq->pam_pf_data == NULL) { + preq->pam_pf_data = talloc_zero(preq, struct pam_preflight_data); + if (preq->pam_pf_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_pf_data == NULL\n"); + ret = ENOMEM; + goto done; + } + } + if (pd->cmd == SSS_PAM_AUTHENTICATE && !preq->cert_auth_local && (pd->pam_status == PAM_AUTHINFO_UNAVAIL @@ -1513,8 +1523,14 @@ void pam_reply(struct pam_auth_req *preq) && !pk_preauth_done && preq->passkey_data_exists && local_passkey_auth_allow) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); - pam_check_user_done(preq, ret); + /* First execute passkey child preflight operation, once completed another call to pam_reply() * + * is made with preq->pam_pf_data->obtained set to true */ + ret = passkey_child_execute(cctx, cctx, cctx->ev, preq, pctx, pd, PAM_PASSKEY_OP_PREFLIGHT); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey child execute failed %s [%d].\n", sss_strerror(ret), ret); + goto done; + } return; } #endif /* BUILD_PASSKEY */ @@ -1898,6 +1914,9 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) struct pam_auth_req *preq; struct pam_data *pd; int ret; +#ifdef BUILD_PASSKEY + enum passkey_child_op passkey_op = PAM_PASSKEY_OP_INVALID; +#endif struct pam_ctx *pctx = talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); struct tevent_req *req; @@ -1979,11 +1998,14 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { if (may_do_passkey_auth(pctx, pd)) { if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; + passkey_op = PAM_PASSKEY_OP_KERBEROS_AUTH; } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + passkey_op = PAM_PASSKEY_OP_LOCAL_AUTH; + } + + if (passkey_op == PAM_PASSKEY_OP_KERBEROS_AUTH || passkey_op == PAM_PASSKEY_OP_LOCAL_AUTH) { + ret = passkey_child_execute(cctx, cctx, cctx->ev, preq, pctx, pd, passkey_op); goto done; } } @@ -2350,6 +2372,9 @@ static void pam_forwarder_cb(struct tevent_req *req) struct cli_ctx *cctx = preq->cctx; struct pam_data *pd; errno_t ret = EOK; +#ifdef BUILD_PASSKEY + enum passkey_child_op passkey_op = PAM_PASSKEY_OP_INVALID; +#endif struct pam_ctx *pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); @@ -2399,11 +2424,14 @@ static void pam_forwarder_cb(struct tevent_req *req) if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { if (may_do_passkey_auth(pctx, pd)) { if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { - ret = passkey_kerberos(pctx, preq->pd, preq); - goto done; + passkey_op = PAM_PASSKEY_OP_KERBEROS_AUTH; } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { - ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + passkey_op = PAM_PASSKEY_OP_LOCAL_AUTH; + } + + if (passkey_op == PAM_PASSKEY_OP_KERBEROS_AUTH || passkey_op == PAM_PASSKEY_OP_LOCAL_AUTH) { + ret = passkey_child_execute(cctx, cctx, cctx->ev, preq, pctx, pd, passkey_op); goto done; } } diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c index f9f4195fc83..d0bb132c3ce 100644 --- a/src/responder/pam/pamsrv_passkey.c +++ b/src/responder/pam/pamsrv_passkey.c @@ -24,13 +24,12 @@ #include "db/sysdb.h" #include "db/sysdb_passkey_user_verification.h" #include "responder/pam/pamsrv.h" - #include "responder/pam/pamsrv_passkey.h" +#include "krb5_plugin/passkey/passkey.h" -struct pam_passkey_verification_enum_str { - enum passkey_user_verification verification; - const char *option; -}; +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) struct pam_passkey_table_data { hash_table_t *table; @@ -38,6 +37,11 @@ struct pam_passkey_table_data { struct pk_child_user_data *data; }; +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { { PAM_PASSKEY_VERIFICATION_ON, "on" }, { PAM_PASSKEY_VERIFICATION_OFF, "off" }, @@ -45,10 +49,6 @@ struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { { PAM_PASSKEY_VERIFICATION_INVALID, NULL } }; -#define PASSKEY_PREFIX "passkey:" -#define USER_VERIFICATION "user_verification=" -#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) - const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) { size_t c; @@ -62,15 +62,61 @@ const char *pam_passkey_verification_enum_to_string(enum passkey_user_verificati return "(NULL)"; } -struct passkey_ctx { - struct pam_ctx *pam_ctx; - struct tevent_context *ev; - struct pam_data *pd; - struct pam_auth_req *preq; +struct passkey_child_op_enum_str { + enum passkey_child_op op; + const char *option; +}; + +struct passkey_child_op_enum_str passkey_child_op_enum_str[] = { + { PAM_PASSKEY_OP_PREFLIGHT, "preflight" }, + { PAM_PASSKEY_OP_KERBEROS_AUTH, "kerberos auth" }, + { PAM_PASSKEY_OP_LOCAL_AUTH, "local auth" }, + { PAM_PASSKEY_OP_INVALID, NULL } }; +const char *passkey_child_op_enum_to_string(enum passkey_child_op op) +{ + size_t c; + + for (c = 0 ; passkey_child_op_enum_str[c].option != NULL; c++) { + if (passkey_child_op_enum_str[c].op == op) { + return passkey_child_op_enum_str[c].option; + } + } + + return "(NULL)"; +} + void pam_forwarder_passkey_cb(struct tevent_req *req); +/* Provide appropriate passkey child op to passkey_child_execute() + * to execute these functions from PAM responder, don't call them + * directly */ +static errno_t passkey_preflight(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_auth_req *pam_req, + struct pam_ctx *pam_ctx, + struct pam_data *pd, + enum passkey_child_op op, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pk_child_user_data *data); +static errno_t passkey_local_auth(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_auth_req *pam_req, + struct pam_ctx *pam_ctx, + struct pam_data *pd, + enum passkey_child_op op, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pk_child_user_data *data); +static errno_t passkey_kerberos_auth(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + enum passkey_child_op op); + errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, struct pk_child_user_data *pk_data, bool kerberos_pa, @@ -79,7 +125,8 @@ errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, - struct passkey_ctx *pctx); + struct pam_ctx *pam_ctx, + struct pam_data *pd); void pam_passkey_get_user_done(struct tevent_req *req); void pam_passkey_get_mapping_done(struct tevent_req *req); errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, @@ -91,32 +138,13 @@ struct passkey_get_mapping_state { struct cache_req_result *result; }; -void passkey_kerberos_cb(struct tevent_req *req) -{ - struct pam_auth_req *preq = tevent_req_callback_data(req, - struct pam_auth_req); - errno_t ret = EOK; - int child_status; - ret = pam_passkey_auth_recv(req, &child_status); - talloc_free(req); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } +void passkey_kerberos_auth_cb(struct tevent_req *req); - DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - - pam_check_user_search(preq); - -done: - pam_check_user_done(preq, ret); -} - -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq) +static errno_t passkey_kerberos_auth(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + enum passkey_child_op op) { errno_t ret; const char *prompt; @@ -190,14 +218,14 @@ errno_t passkey_kerberos(struct pam_ctx *pctx, } req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, - verification, pd, data, true); + verification, pd, data, op); if (req == NULL) { DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", ret, sss_strerror(ret)); goto done; } - tevent_req_set_callback(req, passkey_kerberos_cb, preq); + tevent_req_set_callback(req, passkey_kerberos_auth_cb, preq); ret = EAGAIN; @@ -207,52 +235,35 @@ errno_t passkey_kerberos(struct pam_ctx *pctx, } - -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd) +void passkey_kerberos_auth_cb(struct tevent_req *req) { - struct tevent_req *req; - struct passkey_ctx *pctx; - errno_t ret; - - pctx = talloc_zero(mem_ctx, struct passkey_ctx); - if (pctx == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); - return ENOMEM; - } - - pctx->pd = pd; - pctx->pam_ctx = pam_ctx; - pctx->ev = ev; - pctx->preq = preq; - - DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + uint8_t *buf; + ssize_t buf_len; - req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); - ret = ENOMEM; + ret = pam_passkey_auth_recv(req, preq->cctx, &child_status, &buf, &buf_len); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); goto done; } - tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); - ret = EAGAIN; + pam_check_user_search(preq); done: - if (ret != EAGAIN) { - talloc_free(pctx); - } - - return ret; + pam_check_user_done(preq, ret); } struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, - struct passkey_ctx *pk_ctx) + struct pam_ctx *pam_ctx, + struct pam_data *pd) { struct passkey_get_mapping_state *state; @@ -267,11 +278,11 @@ struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, goto done; } - subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, - pk_ctx->pam_ctx->rctx, - pk_ctx->pam_ctx->rctx->ncache, 0, - pk_ctx->pd->domain, - pk_ctx->pd->user, attrs); + subreq = cache_req_user_by_name_attrs_send(state, ev, + pam_ctx->rctx, + pam_ctx->rctx->ncache, 0, + pd->domain, + pd->user, attrs); if (subreq == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); ret = ENOMEM; @@ -327,6 +338,475 @@ errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, return EOK; } +struct passkey_get_user_data_state { + struct pam_ctx *pam_ctx; + struct pam_data *pd; + bool debug_libfido2; + int timeout; + enum passkey_user_verification verification; + struct pk_child_user_data *data; +}; + +static void passkey_get_user_data_done(struct tevent_req *subreq); + +struct tevent_req *passkey_get_user_data_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_data *pd) +{ + struct passkey_get_user_data_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_user_data_state); + if (req == NULL) { + printf("tevent_req_create() failed\n"); + return NULL; + } + + state->pam_ctx = pam_ctx; + state->pd = pd; + + subreq = pam_passkey_get_mapping_send(state, ev, pam_ctx, pd); + if (subreq == NULL) { + ret = 1; + goto done; + } + + tevent_req_set_callback(subreq, passkey_get_user_data_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +static void passkey_get_user_data_done(struct tevent_req *subreq) +{ + struct passkey_get_user_data_state *state; + struct tevent_req *req; + char *domain_name = NULL; + struct cache_req_result *result = NULL; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_user_data_state); + + ret = pam_passkey_get_mapping_recv(state, subreq, &result); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "pam_passkey_get_mapping_recv failed\n"); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + state->data = talloc_zero(state->pam_ctx, struct pk_child_user_data); + if (!state->data) { + DEBUG(SSSDBG_CRIT_FAILURE, "state->data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* dns_name is used for AD/IPA, for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(state->data, result->msgs[0], domain_name, state->data); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_int(state->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &state->timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(state->pam_ctx->rctx->cdb, + result->domain->sysdb, + result->domain->dns_name, + &state->verification, + &state->debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +int passkey_get_user_data_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + int *_timeout, + bool *_debug_libfido2, + enum passkey_user_verification *_verification, + struct pk_child_user_data **_data) +{ + struct passkey_get_user_data_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_user_data_state); + + *_timeout = state->timeout; + *_debug_libfido2 = state->debug_libfido2; + *_verification = state->verification; + *_data = state->data; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct passkey_check_data_ctx { + struct cli_ctx *cli_ctx; + struct pam_ctx *pam_ctx; + struct pam_data *pd; + struct pam_auth_req *pam_req; + enum passkey_child_op op; +}; + +void passkey_child_get_user_data_done(struct tevent_req *req); + +errno_t passkey_child_execute(TALLOC_CTX *mem_ctx, + struct cli_ctx *cli_ctx, + struct tevent_context *ev, + struct pam_auth_req *pam_req, + struct pam_ctx *pam_ctx, + struct pam_data *pd, + enum passkey_child_op op) +{ + errno_t ret = EOK; + struct tevent_req *req; + struct passkey_check_data_ctx *pk_check_ctx; + + pk_check_ctx = talloc_zero(mem_ctx, struct passkey_check_data_ctx); + if (!pk_check_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_check_ctx == NULL\n"); + return ENOMEM; + } + + pk_check_ctx->cli_ctx = cli_ctx; + pk_check_ctx->pam_ctx = pam_ctx; + pk_check_ctx->pd = pd; + pk_check_ctx->pam_req = pam_req; + pk_check_ctx->op = op; + + req = passkey_get_user_data_send(mem_ctx, ev, pam_ctx, pd); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, passkey_child_get_user_data_done, pk_check_ctx); +done: + + if (ret != EOK) { + talloc_free(pk_check_ctx); + } + return ret; +} + +void passkey_child_get_user_data_done(struct tevent_req *req) +{ + int ret; + struct passkey_check_data_ctx *pk_check_ctx; + int timeout; + bool debug_libfido2; + enum passkey_user_verification verification; + struct pk_child_user_data *data; + + pk_check_ctx = tevent_req_callback_data(req, struct passkey_check_data_ctx); + + ret = passkey_get_user_data_recv(pk_check_ctx, req, &timeout, + &debug_libfido2, &verification, + &data); + talloc_free(req); + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pk_check_ctx->pam_req->passkey_data_exists = false; + pam_check_user_search(pk_check_ctx->pam_req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", + ret, sss_strerror(ret)); + pk_check_ctx->pam_req->passkey_data_exists = false; + pk_check_ctx->pam_req->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pk_check_ctx->pam_req); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Executing passkey operation [%s]\n", + passkey_child_op_enum_to_string(pk_check_ctx->op)); + switch (pk_check_ctx->op) { + case PAM_PASSKEY_OP_PREFLIGHT: + ret = passkey_preflight(pk_check_ctx->cli_ctx, + pk_check_ctx->cli_ctx->ev, + pk_check_ctx->pam_req, + pk_check_ctx->pam_ctx, + pk_check_ctx->pd, + pk_check_ctx->op, + timeout, + debug_libfido2, + verification, + data); + break; + case PAM_PASSKEY_OP_KERBEROS_AUTH: + ret = passkey_kerberos_auth(pk_check_ctx->pam_ctx, + pk_check_ctx->pd, + pk_check_ctx->pam_req, + pk_check_ctx->op); + break; + case PAM_PASSKEY_OP_LOCAL_AUTH: + ret = passkey_local_auth(pk_check_ctx->cli_ctx, + pk_check_ctx->cli_ctx->ev, + pk_check_ctx->pam_req, + pk_check_ctx->pam_ctx, + pk_check_ctx->pd, + pk_check_ctx->op, + timeout, + debug_libfido2, + verification, + data); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey operation\n"); + pk_check_ctx->pam_req->passkey_data_exists = false; + pk_check_ctx->pam_req->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(pk_check_ctx->pam_req); + } +} + +struct passkey_preflight_ctx { + struct pam_ctx *pam_ctx; + struct pam_data *pd; + struct pam_auth_req *pam_req; +}; + +void passkey_preflight_done(struct tevent_req *req); + +static errno_t passkey_preflight(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_auth_req *pam_req, + struct pam_ctx *pam_ctx, + struct pam_data *pd, + enum passkey_child_op op, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pk_child_user_data *data) +{ + struct tevent_req *req; + errno_t ret = EOK; + + struct passkey_preflight_ctx *preflight_ctx; + + preflight_ctx = talloc_zero(mem_ctx, struct passkey_preflight_ctx); + if (!preflight_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "preflight_ctx == NULL\n"); + return ENOMEM; + } + + preflight_ctx->pam_ctx = pam_ctx; + preflight_ctx->pd = pd; + preflight_ctx->pam_req = pam_req; + + req = pam_passkey_auth_send(mem_ctx, ev, timeout, + debug_libfido2, verification, + pd, data, op); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_preflight_done, preflight_ctx); +done: + + if (ret != EOK) { + talloc_free(preflight_ctx); + } + return ret; +} + +void passkey_preflight_done(struct tevent_req *req) +{ + struct passkey_preflight_ctx *preflight_ctx = tevent_req_callback_data(req, + struct passkey_preflight_ctx); + errno_t ret = EOK; + int child_status; + const char *prompt_pin; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + bool debug_libfido2 = false; + bool pin_required; + int attempts = 8; + uint8_t *buf; + char *preflight_str; + ssize_t buf_len; + + ret = pam_passkey_auth_recv(req, preflight_ctx, &child_status, &buf, &buf_len); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey preflight failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + buf[buf_len - 1] = '\0'; + preflight_str = talloc_asprintf(preflight_ctx, "%s", buf); + if (preflight_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey preflight failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey preflight child finished with status [%d]\n", child_status); + DEBUG(SSSDBG_TRACE_FUNC, "read [%zu] bytes from passkey preflight child.\n", buf_len); + + ret = sss_passkey_preflight_from_json(preflight_str, &pin_required, &attempts); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "failed to parse JSON preflight output [%d]: %s\n", + ret, sss_strerror(ret)); + /* Preflight operation is not critical, even if preflight operation fails, + * return the default 8 attempts value. Only 3 or less attempts is acted on + * by pam_sss, so the '8' value is ignored essentially. 8 is also returned + * by passkey_child --preflight when an invalid key handle is provided */ + attempts = 8; + } + + /* Make data available inside pam_auth_req, GDM will use this data */ + preflight_ctx->pam_req->pam_pf_data->attempts = attempts; + preflight_ctx->pam_req->pam_pf_data->pin_required = pin_required; + + ret = pam_add_response(preflight_ctx->pd, SSS_PAM_PASSKEY_INFO, sizeof(uint32_t), + (const uint8_t *) &attempts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(preflight_ctx->pam_ctx->rctx->cdb, + preflight_ctx->pam_req->domain->sysdb, + preflight_ctx->pam_req->domain->dns_name, + &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(preflight_ctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preflight_ctx->pam_req->pd->pam_status = PAM_SUCCESS; + preflight_ctx->pam_req->pam_pf_data->obtained = true; + +done: + pam_check_user_done(preflight_ctx->pam_req, ret); + + if (ret != EOK) { + preflight_ctx->pam_req->passkey_data_exists = false; + } + + pam_reply(preflight_ctx->pam_req); + + return; +} + +struct passkey_local_auth_ctx { + struct pam_ctx *pam_ctx; + struct pam_data *pd; + struct pam_auth_req *pam_req; +}; + +static errno_t passkey_local_auth(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_auth_req *pam_req, + struct pam_ctx *pam_ctx, + struct pam_data *pd, + enum passkey_child_op op, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pk_child_user_data *data) +{ + struct tevent_req *req; + errno_t ret = EOK; + + struct passkey_local_auth_ctx *pk_local_ctx; + + pk_local_ctx = talloc_zero(mem_ctx, struct passkey_local_auth_ctx); + if (!pk_local_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_local_ctx == NULL\n"); + return ENOMEM; + } + + pk_local_ctx->pam_ctx = pam_ctx; + pk_local_ctx->pd = pd; + pk_local_ctx->pam_req = pam_req; + + req = pam_passkey_auth_send(mem_ctx, ev, timeout, + debug_libfido2, verification, + pd, data, op); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pam_req); +done: + + if (ret != EOK) { + talloc_free(pk_local_ctx); + } + return ret; +} + errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, const char *verify_opts, enum passkey_user_verification *_user_verification) @@ -379,14 +859,11 @@ errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, return ret; } -static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, - struct passkey_ctx *pctx, - struct confdb_ctx *cdb, - struct sysdb_ctx *sysdb, - const char *domain_name, - struct pk_child_user_data *pk_data, - enum passkey_user_verification *_user_verification, - bool *_debug_libfido2) + errno_t passkey_local_verification(struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) { TALLOC_CTX *tmp_ctx; errno_t ret; @@ -411,7 +888,7 @@ static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, goto done; } - ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + ret = confdb_get_bool(cdb, CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, &debug_libfido2); if (ret != EOK) { @@ -431,7 +908,7 @@ static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); } else { /* No verification set in LDAP, fallback to local sssd.conf setting */ - ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + ret = confdb_get_string(cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, &verify_opts); if (ret != EOK) { @@ -592,8 +1069,10 @@ void pam_forwarder_passkey_cb(struct tevent_req *req) struct pam_auth_req); errno_t ret = EOK; int child_status; + uint8_t *buf; + ssize_t buf_len; - ret = pam_passkey_auth_recv(req, &child_status); + ret = pam_passkey_auth_recv(req, preq->cctx, &child_status, &buf, &buf_len); talloc_free(req); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", @@ -613,135 +1092,6 @@ void pam_forwarder_passkey_cb(struct tevent_req *req) pam_check_user_done(preq, ret); } -void pam_passkey_get_user_done(struct tevent_req *req) -{ - int ret; - struct passkey_ctx *pctx; - bool debug_libfido2 = false; - char *domain_name = NULL; - int timeout; - struct cache_req_result *result = NULL; - struct pk_child_user_data *pk_data = NULL; - enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; - - pctx = tevent_req_callback_data(req, struct passkey_ctx); - - ret = pam_passkey_get_mapping_recv(pctx, req, &result); - talloc_zfree(req); - if (ret != EOK && ret != ENOENT) { - DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", - ret, sss_strerror(ret)); - goto done; - } - - if (result == NULL) { - DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); - ret = ENOMEM; - goto done; - } - - pk_data = talloc_zero(pctx, struct pk_child_user_data); - if (!pk_data) { - DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); - ret = ENOMEM; - goto done; - } - - /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ - if (result->domain != NULL) { - domain_name = result->domain->dns_name; - if (domain_name == NULL) { - domain_name = result->domain->name; - } - } - - if (domain_name == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); - ret = EIO; - goto done; - } - - /* Get passkey data */ - DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); - ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); - if (ret != EOK) { - DEBUG(SSSDBG_TRACE_FUNC, - "process_passkey_data failed: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* timeout */ - ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, - CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, - &timeout); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, - result->domain->sysdb, result->domain->dns_name, - pk_data, &verification, &debug_libfido2); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "Failed to check passkey verification [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - /* Preauth respond with prompt_pin true or false based on user verification */ - if (pctx->pd->cmd == SSS_PAM_PREAUTH) { - const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; - - ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, - (const uint8_t *) prompt_pin); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - pctx->pd->pam_status = PAM_SUCCESS; - pam_reply(pctx->preq); - talloc_free(pk_data); - return; - } - - req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, - verification, pctx->pd, pk_data, false); - if (req == NULL) { - DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", - ret, sss_strerror(ret)); - goto done; - } - - tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); - -done: - if (pk_data != NULL) { - talloc_free(pk_data); - } - - if (ret == ENOENT) { - /* No passkey data, continue through to typical auth flow */ - DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); - pctx->preq->passkey_data_exists = false; - pam_check_user_search(pctx->preq); - } else if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s.\n", - ret, sss_strerror(ret)); - pctx->preq->passkey_data_exists = false; - pctx->preq->pd->pam_status = PAM_SYSTEM_ERR; - pam_reply(pctx->preq); - } - - return; -} - - struct pam_passkey_auth_send_state { struct pam_data *pd; struct tevent_context *ev; @@ -751,7 +1101,9 @@ struct pam_passkey_auth_send_state { char *verify_opts; int timeout; int child_status; - bool kerberos_pa; + enum passkey_child_op op; + uint8_t *read_buf; + ssize_t read_buf_len; }; static errno_t passkey_child_exec(struct tevent_req *req); @@ -777,12 +1129,15 @@ errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, uint8_t *buf; size_t len; const char *pin = NULL; + enum sss_authtok_type type; if (pd == NULL || pd->authtok == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); return EINVAL; } + type = sss_authtok_get_type(pd->authtok); + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); @@ -807,8 +1162,8 @@ errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, safealign_memcpy(buf, pin, len, NULL); } else { - DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", - sss_authtok_get_type(pd->authtok)); + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%s].\n", + sss_authtok_type_to_str(type)); return EINVAL; } @@ -820,31 +1175,31 @@ errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, static void pam_passkey_child_read_data(struct tevent_req *subreq) { - uint8_t *buf; - ssize_t buf_len; char *str; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); int ret; - ret = read_pipe_recv(subreq, state, &buf, &buf_len); + ret = read_pipe_recv(subreq, state, &state->read_buf, &state->read_buf_len); talloc_zfree(subreq); if (ret != EOK) { tevent_req_error(req, ret); return; } - str = malloc(sizeof(char) * buf_len); - if (str == NULL) { - return; - } + if (state->op != PAM_PASSKEY_OP_PREFLIGHT) { + str = malloc(sizeof(char) * state->read_buf_len); + if (str == NULL) { + return; + } - snprintf(str, buf_len, "%s", buf); + snprintf(str, state->read_buf_len, "%s", state->read_buf); - sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); - free(str); + free(str); + } tevent_req_done(req); return; @@ -869,8 +1224,9 @@ static void passkey_child_write_done(struct tevent_req *subreq) FD_CLOSE(state->io->write_to_child_fd); - if (state->kerberos_pa) { - /* Read data back from passkey child */ + /* Read data back from passkey child. Passkey child in Kerberos auth provides JSON reply. + * preflight provides PIN attempts output */ + if (state->op == PAM_PASSKEY_OP_KERBEROS_AUTH || state->op == PAM_PASSKEY_OP_PREFLIGHT) { subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); if (subreq == NULL) { DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); @@ -940,7 +1296,7 @@ pam_passkey_auth_send(TALLOC_CTX *mem_ctx, enum passkey_user_verification verification, struct pam_data *pd, struct pk_child_user_data *pk_data, - bool kerberos_pa) + enum passkey_child_op op) { struct tevent_req *req; struct pam_passkey_auth_send_state *state; @@ -957,9 +1313,8 @@ pam_passkey_auth_send(TALLOC_CTX *mem_ctx, state->pd = pd; state->ev = ev; - state->timeout = timeout; - state->kerberos_pa = kerberos_pa; + state->op = op; state->logfile = PASSKEY_CHILD_LOG_FILE; num_args = 11; @@ -970,21 +1325,23 @@ pam_passkey_auth_send(TALLOC_CTX *mem_ctx, goto done; } - switch (verification) { - case PAM_PASSKEY_VERIFICATION_ON: - state->extra_args[arg_c++] = "--user-verification=true"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); - break; - case PAM_PASSKEY_VERIFICATION_OFF: - state->extra_args[arg_c++] = "--user-verification=false"; - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); - break; - default: - DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); - break; + if (op != PAM_PASSKEY_OP_PREFLIGHT) { + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } } - ret = pam_passkey_concatenate_keys(state, pk_data, state->kerberos_pa, + ret = pam_passkey_concatenate_keys(state, pk_data, op == PAM_PASSKEY_OP_KERBEROS_AUTH, &result_kh, &result_pk); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", @@ -993,7 +1350,7 @@ pam_passkey_auth_send(TALLOC_CTX *mem_ctx, } - if (state->kerberos_pa) { + if (op == PAM_PASSKEY_OP_KERBEROS_AUTH) { state->extra_args[arg_c++] = pk_data->crypto_challenge; state->extra_args[arg_c++] = "--cryptographic-challenge"; state->extra_args[arg_c++] = result_kh; @@ -1001,7 +1358,13 @@ pam_passkey_auth_send(TALLOC_CTX *mem_ctx, state->extra_args[arg_c++] = pk_data->domain; state->extra_args[arg_c++] = "--domain"; state->extra_args[arg_c++] = "--get-assert"; - } else { + } else if (op == PAM_PASSKEY_OP_PREFLIGHT) { + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--preflight"; + } else if (op == PAM_PASSKEY_OP_LOCAL_AUTH) { state->extra_args[arg_c++] = result_pk; state->extra_args[arg_c++] = "--public-key"; state->extra_args[arg_c++] = result_kh; @@ -1011,6 +1374,10 @@ pam_passkey_auth_send(TALLOC_CTX *mem_ctx, state->extra_args[arg_c++] = state->pd->user; state->extra_args[arg_c++] = "--username"; state->extra_args[arg_c++] = "--authenticate"; + } else { + ret = EINVAL; + DEBUG(SSSDBG_OP_FAILURE, "Invalid passkey op provided\n"); + goto done; } ret = passkey_child_exec(req); @@ -1050,32 +1417,37 @@ static errno_t passkey_child_exec(struct tevent_req *req) uint8_t *write_buf = NULL; size_t write_buf_len = 0; int ret; + bool local_auth; state = tevent_req_data(req, struct pam_passkey_auth_send_state); + local_auth = (state->op == PAM_PASSKEY_OP_LOCAL_AUTH); + ret = sss_child_start(state, state->ev, PASSKEY_CHILD_PATH, state->extra_args, false, state->logfile, STDOUT_FILENO, - (state->kerberos_pa) ? NULL : pam_passkey_auth_done, req, + (local_auth) ? pam_passkey_auth_done : NULL, req, state->timeout, passkey_child_timeout, req, true, &(state->io)); if (ret != EOK) { return ERR_PASSKEY_CHILD; } - /* PIN is needed */ - if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { - ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, - &write_buf_len); - if (ret != EOK) { - DEBUG(SSSDBG_OP_FAILURE, - "get_passkey_child_write_buffer failed [%d]: %s.\n", - ret, sss_strerror(ret)); - return ret; + /* PIN is needed except for Preflight operation */ + if (state->op != PAM_PASSKEY_OP_PREFLIGHT) { + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return ret; + } } } - if (write_buf_len != 0) { + if (write_buf_len != 0 || state->op == PAM_PASSKEY_OP_PREFLIGHT) { subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, state->io->write_to_child_fd); if (subreq == NULL) { @@ -1089,15 +1461,20 @@ static errno_t passkey_child_exec(struct tevent_req *req) } errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status) + TALLOC_CTX *mem_ctx, + int *child_status, + uint8_t **buf, + ssize_t *buf_len) { struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); - *child_status = state->child_status; - TEVENT_REQ_RETURN_ON_ERROR(req); + *child_status = state->child_status; + *buf = talloc_steal(mem_ctx, state->read_buf); + *buf_len = state->read_buf_len; + return EOK; } diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h index 48074d04263..3b95ee515e9 100644 --- a/src/responder/pam/pamsrv_passkey.h +++ b/src/responder/pam/pamsrv_passkey.h @@ -36,14 +36,19 @@ enum passkey_user_verification { PAM_PASSKEY_VERIFICATION_INVALID }; -errno_t passkey_local(TALLOC_CTX *mem_ctx, - struct tevent_context *ev, - struct pam_ctx *pam_ctx, - struct pam_auth_req *preq, - struct pam_data *pd); -errno_t passkey_kerberos(struct pam_ctx *pctx, - struct pam_data *pd, - struct pam_auth_req *preq); +/* Operations when calling passkey child */ +enum passkey_child_op { + PAM_PASSKEY_OP_PREFLIGHT, + PAM_PASSKEY_OP_KERBEROS_AUTH, + PAM_PASSKEY_OP_LOCAL_AUTH, + PAM_PASSKEY_OP_INVALID +}; + +struct pam_preflight_data { + int attempts; + bool pin_required; + bool obtained; +}; struct pk_child_user_data { /* Both Kerberos and non-kerberos */ @@ -58,6 +63,14 @@ struct pk_child_user_data { const char **public_keys; }; +errno_t passkey_child_execute(TALLOC_CTX *mem_ctx, + struct cli_ctx *cli_ctx, + struct tevent_context *ev, + struct pam_auth_req *pam_req, + struct pam_ctx *pam_ctx, + struct pam_data *pd, + enum passkey_child_op op); + errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, const char *verify_opts, enum passkey_user_verification *_user_verification); @@ -70,9 +83,12 @@ struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, enum passkey_user_verification verification, struct pam_data *pd, struct pk_child_user_data *pk_data, - bool kerberos_pa); + enum passkey_child_op op); errno_t pam_passkey_auth_recv(struct tevent_req *req, - int *child_status); + TALLOC_CTX *mem_ctx, + int *child_status, + uint8_t **read_buf, + ssize_t *read_buf_len); errno_t pam_eval_passkey_response(struct pam_ctx *pctx, struct pam_data *pd, struct pam_auth_req *preq, @@ -81,6 +97,11 @@ errno_t process_passkey_data(TALLOC_CTX *mem_ctx, struct ldb_message *user_mesg, const char *domain, struct pk_child_user_data *_data); +errno_t passkey_local_verification(struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2); bool may_do_passkey_auth(struct pam_ctx *pctx, struct pam_data *pd); diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h index d6fb254f208..784c9da1422 100644 --- a/src/sss_client/pam_message.h +++ b/src/sss_client/pam_message.h @@ -66,6 +66,7 @@ struct pam_items { char *first_factor; char *passkey_key; char *passkey_prompt_pin; + uint32_t passkey_attempts; bool password_prompting; bool user_name_hint; diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c index 3f2317f3eb7..aac9e8e0fa3 100644 --- a/src/sss_client/pam_sss.c +++ b/src/sss_client/pam_sss.c @@ -1338,17 +1338,20 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf, } break; case SSS_PAM_PASSKEY_INFO: + safealign_memcpy(&pi->passkey_attempts, &buf[p], sizeof(uint32_t), NULL); + if (buf[p + (len - 1)] != '\0') { D(("passkey info does not end with \\0.")); break; } free(pi->passkey_prompt_pin); - pi->passkey_prompt_pin = strdup((char *) &buf[p]); + pi->passkey_prompt_pin = strdup((char *) &buf[p + sizeof(uint32_t)]); if (pi->passkey_prompt_pin == NULL) { D(("strdup failed")); break; } + break; default: D(("Unknown response type [%d]", type)); @@ -1868,11 +1871,12 @@ static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi, { int ret; const struct pam_conv *conv; - const struct pam_message *mesg[4] = { NULL, NULL, NULL, NULL }; - struct pam_message m[4] = { {0}, {0}, {0}, {0} }; + const struct pam_message *mesg[5] = { NULL, NULL, NULL, NULL, NULL }; + struct pam_message m[5] = { {0}, {0}, {0}, {0}, {0} }; struct pam_response *resp = NULL; bool kerberos_preauth; bool prompt_pin; + char *prompt_attempts; int pin_idx = 0; int msg_idx = 0; size_t needed_size; @@ -1906,6 +1910,15 @@ static int prompt_passkey(pam_handle_t *pamh, struct pam_items *pi, msg_idx++; } + if (prompt_pin && pi->passkey_attempts <= 3) { + ret = asprintf(&prompt_attempts, "You have %u PIN attempts remaining\n", + pi->passkey_attempts); + + m[msg_idx].msg_style = PAM_TEXT_INFO; + m[msg_idx].msg = prompt_attempts; + msg_idx++; + } + /* Prompt for PIN * * If prompt_pin is false but a PIN is set on the device diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index d3570f505a1..ecc0669f591 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -552,11 +552,13 @@ enum response_type { * - verification_uri_complete * - user_code */ - SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available. + SSS_PAM_PASSKEY_INFO, /**< Indicates that passkey authentication is available * including a parameter string which dictates whether - * prompting for PIN is needed. + * prompting for PIN is needed. Also includes preflight + * data including number of PIN attempts remaining. * @param * - prompt_pin + * - attempts */ SSS_PAM_PASSKEY_KRB_INFO, /**< A message containing the passkey parameters * for the user. The key is the cryptographic challenge diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c index a08cda570f5..a7c984fedb8 100644 --- a/src/tests/cmocka/test_pam_srv.c +++ b/src/tests/cmocka/test_pam_srv.c @@ -587,8 +587,10 @@ static void passkey_test_done(struct tevent_req *req) { struct pam_test_ctx *ctx = tevent_req_callback_data(req, struct pam_test_ctx); + uint8_t *buf; + ssize_t buf_len; - pam_passkey_auth_recv(req, &pam_test_ctx->child_status); + pam_passkey_auth_recv(req, pam_test_ctx, &pam_test_ctx->child_status, &buf, &buf_len); talloc_zfree(req); /* No actual fido2 device available, overwrite the child status to successful. @@ -877,7 +879,7 @@ static int test_pam_passkey_found_preauth_check(uint32_t status, uint8_t *body, assert_int_equal(val, pam_test_ctx->exp_pam_status); SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); - assert_int_equal(val, 3); + assert_int_equal(val, 4); SAFEALIGN_COPY_UINT32(&val, body + rp, &rp); assert_int_equal(val, SSS_PAM_DOMAIN_NAME); @@ -4651,9 +4653,6 @@ void test_pam_passkey_preauth_no_passkey(void **state) mock_input_pam_passkey(pam_test_ctx, "pamuser", "1234", NULL, NULL, NULL); - - /* sss_parse_inp_recv() is called twice - * multiple cache req calls */ mock_parse_inp("pamuser", NULL, EOK); mock_parse_inp("pamuser", NULL, EOK); @@ -4841,7 +4840,6 @@ void test_pam_passkey_preauth_mapping_multi(void **state) mock_input_pam_passkey(pam_test_ctx, "pamuser", "1234", NULL, NULL, SSSD_TEST_PASSKEY); - mock_parse_inp("pamuser", NULL, EOK); /* Add passkey data first, then pubkey mapping data */