Skip to content

Commit 404d5ce

Browse files
ikerexxejustin-stephenson
authored andcommitted
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 <ipedrosa@redhat.com>
1 parent b25d006 commit 404d5ce

File tree

8 files changed

+275
-4
lines changed

8 files changed

+275
-4
lines changed

Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3807,7 +3807,8 @@ test_passkey_LDFLAGS = \
38073807
-Wl,-wrap,fido_assert_sig_len \
38083808
-Wl,-wrap,fido_assert_set_count \
38093809
-Wl,-wrap,fido_assert_set_authdata \
3810-
-Wl,-wrap,fido_assert_set_sig
3810+
-Wl,-wrap,fido_assert_set_sig \
3811+
-Wl,-wrap,fido_dev_get_retry_count
38113812
test_passkey_LDADD = \
38123813
$(CMOCKA_LIBS) \
38133814
$(SSSD_LIBS) \

src/passkey_child/passkey_child.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ int main(int argc, const char *argv[])
9393
ERROR("Verification error.\n");
9494
goto done;
9595
}
96+
} else if (data.action == ACTION_PREFLIGHT) {
97+
ret = preflight(&data, 1);
98+
/* Errors are ignored, as in most cases they are due to the device not
99+
* being connected to the system. If an error occurs, the default
100+
* values are returned, and that is sufficient for the time being.
101+
*/
96102
}
97103

98104
done:

src/passkey_child/passkey_child.h

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@
3434
#define USER_ID_SIZE 32
3535
#define TIMEOUT 15
3636
#define FREQUENCY 1
37+
#define MAX_PIN_RETRIES 8
3738

3839
enum action_opt {
3940
ACTION_NONE,
4041
ACTION_REGISTER,
4142
ACTION_AUTHENTICATE,
4243
ACTION_GET_ASSERT,
43-
ACTION_VERIFY_ASSERT
44+
ACTION_VERIFY_ASSERT,
45+
ACTION_PREFLIGHT
4446
};
4547

4648
enum credential_type {
@@ -559,4 +561,47 @@ get_assert_data(struct passkey_data *data, int timeout);
559561
errno_t
560562
verify_assert_data(struct passkey_data *data);
561563

564+
/**
565+
* @brief Obtain PIN retries in the device
566+
*
567+
* @param[in] dev Device information
568+
* @param[in] data passkey data
569+
* @param[in] _pin_retries Number of PIN retries
570+
*
571+
* @return 0 if the PIN retries were obtained properly,
572+
* error code otherwise.
573+
*/
574+
errno_t
575+
get_device_pin_retries(fido_dev_t *dev, struct passkey_data *data,
576+
int *_pin_retries);
577+
578+
/**
579+
* @brief Print preflight information
580+
*
581+
* Print user-verification and pin retries
582+
*
583+
* @param[in] data passkey data
584+
* @param[in] _pin_retries Number of PIN retries
585+
*
586+
* @return EOK
587+
*
588+
*/
589+
errno_t
590+
print_preflight(const struct passkey_data *data, int pin_retries);
591+
592+
/**
593+
* @brief Obtain authentication data prior to processing
594+
*
595+
* Prepare the assertion request data, select the device to use, get the device
596+
* options and compare them with the organization policy, get the PIN retries
597+
* and print the preflight data.
598+
*
599+
* @param[in] data passkey data
600+
* @param[in] timeout Timeout in seconds to stop looking for a device
601+
*
602+
* @return EOK
603+
*/
604+
errno_t
605+
preflight(struct passkey_data *data, int timeout);
606+
562607
#endif /* __PASSKEY_CHILD_H__ */

src/passkey_child/passkey_child_assert.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ set_assert_client_data_hash(const struct passkey_data *data,
5151
return ENOMEM;
5252
}
5353

54-
if (data->action == ACTION_AUTHENTICATE) {
54+
if (data->action == ACTION_AUTHENTICATE
55+
|| data->action == ACTION_PREFLIGHT) {
5556
ret = sss_generate_csprng_buffer(cdh, sizeof(cdh));
5657
if (ret != EOK) {
5758
DEBUG(SSSDBG_OP_FAILURE,

src/passkey_child/passkey_child_common.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[],
177177
_("Obtain assertion data"), NULL },
178178
{"verify-assert", 0, POPT_ARG_NONE, NULL, 'v',
179179
_("Verify assertion data"), NULL },
180+
{"preflight", 0, POPT_ARG_NONE, NULL, 'p',
181+
_("Obtain authentication data prior to processing"), NULL },
180182
{"username", 0, POPT_ARG_STRING, &data->shortname, 0,
181183
_("Shortname"), NULL },
182184
{"domain", 0, POPT_ARG_STRING, &data->domain, 0,
@@ -259,6 +261,16 @@ parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[],
259261
}
260262
data->action = ACTION_VERIFY_ASSERT;
261263
break;
264+
case 'p':
265+
if (data->action != ACTION_NONE) {
266+
fprintf(stderr, "\nActions are mutually exclusive and should" \
267+
" be used only once.\n\n");
268+
poptPrintUsage(pc, stderr, 0);
269+
ret = EINVAL;
270+
goto done;
271+
}
272+
data->action = ACTION_PREFLIGHT;
273+
break;
262274
case 'q':
263275
data->quiet = true;
264276
break;
@@ -419,6 +431,14 @@ check_arguments(const struct passkey_data *data)
419431
goto done;
420432
}
421433

434+
if (data->action == ACTION_PREFLIGHT
435+
&& (data->domain == NULL || data->key_handle_list == NULL)) {
436+
DEBUG(SSSDBG_OP_FAILURE,
437+
"Too few arguments for preflight action.\n");
438+
ret = ERR_INPUT_PARSE;
439+
goto done;
440+
}
441+
422442
done:
423443
return ret;
424444
}
@@ -887,3 +907,47 @@ verify_assert_data(struct passkey_data *data)
887907

888908
return ret;
889909
}
910+
911+
errno_t
912+
preflight(struct passkey_data *data, int timeout)
913+
{
914+
fido_assert_t *assert = NULL;
915+
fido_dev_t *dev = NULL;
916+
int index = 0;
917+
int pin_retries = 0;
918+
errno_t ret;
919+
920+
ret = select_authenticator(data, timeout, &dev, &assert, &index);
921+
if (ret != EOK) {
922+
goto done;
923+
}
924+
925+
DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n");
926+
ret = get_device_options(dev, data);
927+
if (ret != FIDO_OK) {
928+
goto done;
929+
}
930+
931+
DEBUG(SSSDBG_TRACE_FUNC, "Checking the number of remaining PIN retries.\n");
932+
ret = get_device_pin_retries(dev, data, &pin_retries);
933+
if (ret != FIDO_OK) {
934+
goto done;
935+
}
936+
937+
ret = EOK;
938+
939+
done:
940+
if (ret != EOK) {
941+
data->user_verification = FIDO_OPT_TRUE;
942+
pin_retries = MAX_PIN_RETRIES;
943+
}
944+
print_preflight(data, pin_retries);
945+
946+
if (dev != NULL) {
947+
fido_dev_close(dev);
948+
}
949+
fido_dev_free(&dev);
950+
fido_assert_free(&assert);
951+
952+
return EOK;
953+
}

src/passkey_child/passkey_child_credentials.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424

2525
#include <fcntl.h>
26+
#include <jansson.h>
2627
#include <termios.h>
2728
#include <stdio.h>
2829

@@ -679,3 +680,40 @@ evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data)
679680
done:
680681
return ret;
681682
}
683+
684+
errno_t
685+
print_preflight(const struct passkey_data *data, int pin_retries)
686+
{
687+
json_t *jroot = NULL;
688+
char* string = NULL;
689+
bool user_verification;
690+
691+
if (data->user_verification == FIDO_OPT_TRUE) {
692+
user_verification = true;
693+
} else {
694+
user_verification = false;
695+
}
696+
697+
jroot = json_pack("{s:b, s:i}",
698+
"pin_required", user_verification,
699+
"attempts", pin_retries);
700+
701+
if (jroot == NULL) {
702+
DEBUG(SSSDBG_OP_FAILURE, "Failed to create jroot object.\n");
703+
goto done;
704+
}
705+
706+
string = json_dumps(jroot, 0);
707+
if (string == NULL) {
708+
DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n");
709+
goto done;
710+
}
711+
712+
puts(string);
713+
free(string);
714+
715+
done:
716+
json_decref(jroot);
717+
718+
return EOK;
719+
}

src/passkey_child/passkey_child_devices.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
errno_t
3131
list_devices(int timeout, fido_dev_info_t *dev_list, size_t *dev_number)
3232
{
33-
errno_t ret;
33+
errno_t ret = EOK;
3434

3535
for (int i = 0; i < timeout; i += FREQUENCY) {
3636
ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number);
@@ -244,3 +244,25 @@ get_device_options(fido_dev_t *dev, struct passkey_data *_data)
244244

245245
return ret;
246246
}
247+
248+
errno_t
249+
get_device_pin_retries(fido_dev_t *dev, struct passkey_data *data,
250+
int *_pin_retries)
251+
{
252+
int ret = EOK;
253+
254+
if (data->user_verification == FIDO_OPT_TRUE) {
255+
ret = fido_dev_get_retry_count(dev, _pin_retries);
256+
if (ret != FIDO_OK) {
257+
DEBUG(SSSDBG_OP_FAILURE,
258+
"fido_dev_get_retry_count failed [%d]: %s.\n",
259+
ret, fido_strerr(ret));
260+
goto done;
261+
}
262+
} else {
263+
*_pin_retries = MAX_PIN_RETRIES;
264+
}
265+
266+
done:
267+
return ret;
268+
}

src/tests/cmocka/test_passkey_child.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,17 @@ __wrap_fido_assert_set_sig(fido_assert_t *assert, size_t idx,
524524
return ret;
525525
}
526526

527+
int
528+
__wrap_fido_dev_get_retry_count(fido_dev_t *dev, int *pin_retries)
529+
{
530+
int ret;
531+
532+
ret = mock();
533+
(*pin_retries) = mock();
534+
535+
return ret;
536+
}
537+
527538
/***********************
528539
* TEST
529540
**********************/
@@ -1339,6 +1350,38 @@ void test_verify_assert_data_integration(void **state)
13391350
talloc_free(tmp_ctx);
13401351
}
13411352

1353+
void test_get_device_pin_retries_success(void **state)
1354+
{
1355+
struct passkey_data data;
1356+
fido_dev_t *dev = NULL;
1357+
int pin_retries = 0;
1358+
errno_t ret;
1359+
1360+
data.user_verification = FIDO_OPT_TRUE;
1361+
will_return(__wrap_fido_dev_get_retry_count, FIDO_OK);
1362+
will_return(__wrap_fido_dev_get_retry_count, 8);
1363+
1364+
ret = get_device_pin_retries(dev, &data, &pin_retries);
1365+
assert_int_equal(ret, FIDO_OK);
1366+
assert_int_equal(pin_retries, 8);
1367+
}
1368+
1369+
void test_get_device_pin_retries_failure(void **state)
1370+
{
1371+
struct passkey_data data;
1372+
fido_dev_t *dev = NULL;
1373+
int pin_retries = 0;
1374+
errno_t ret;
1375+
1376+
data.user_verification = FIDO_OPT_TRUE;
1377+
will_return(__wrap_fido_dev_get_retry_count, FIDO_ERR_INVALID_ARGUMENT);
1378+
will_return(__wrap_fido_dev_get_retry_count, 8);
1379+
1380+
ret = get_device_pin_retries(dev, &data, &pin_retries);
1381+
assert_int_equal(ret, FIDO_ERR_INVALID_ARGUMENT);
1382+
assert_int_equal(pin_retries, 8);
1383+
}
1384+
13421385
static void test_parse_supp_valgrind_args(void)
13431386
{
13441387
/*
@@ -1349,6 +1392,54 @@ static void test_parse_supp_valgrind_args(void)
13491392
DEBUG_CLI_INIT(debug_level);
13501393
}
13511394

1395+
void test_preflight_integration(void **state)
1396+
{
1397+
TALLOC_CTX *tmp_ctx;
1398+
struct passkey_data data;
1399+
size_t dev_number = 3;
1400+
char *key_handle;
1401+
errno_t ret;
1402+
1403+
tmp_ctx = talloc_new(NULL);
1404+
assert_non_null(tmp_ctx);
1405+
data.action = ACTION_PREFLIGHT;
1406+
data.shortname = "user";
1407+
data.domain = "test.com";
1408+
key_handle = talloc_strdup(tmp_ctx, TEST_KEY_HANDLE);
1409+
data.key_handle_list = &key_handle;
1410+
data.key_handle_size = 1;
1411+
data.type = COSE_ES256;
1412+
data.user_verification = FIDO_OPT_TRUE;
1413+
data.user_id = NULL;
1414+
data.quiet = false;
1415+
will_return(__wrap_fido_dev_info_manifest, FIDO_OK);
1416+
will_return(__wrap_fido_dev_info_manifest, dev_number);
1417+
will_return(__wrap_fido_assert_set_rp, FIDO_OK);
1418+
will_return(__wrap_fido_assert_allow_cred, FIDO_OK);
1419+
will_return(__wrap_fido_assert_set_uv, FIDO_OK);
1420+
will_return(__wrap_fido_assert_set_clientdata_hash, FIDO_OK);
1421+
for (size_t i = 0; i < (dev_number - 1); i++) {
1422+
will_return(__wrap_fido_dev_info_path, TEST_PATH);
1423+
will_return(__wrap_fido_dev_open, FIDO_OK);
1424+
will_return(__wrap_fido_dev_is_fido2, true);
1425+
if (i == 0) {
1426+
will_return(__wrap_fido_dev_get_assert, FIDO_ERR_INVALID_SIG);
1427+
} else {
1428+
will_return(__wrap_fido_dev_get_assert, FIDO_OK);
1429+
}
1430+
}
1431+
will_return(__wrap_fido_dev_has_uv, false);
1432+
will_return(__wrap_fido_dev_has_pin, true);
1433+
will_return(__wrap_fido_dev_supports_uv, false);
1434+
will_return(__wrap_fido_dev_get_retry_count, FIDO_OK);
1435+
will_return(__wrap_fido_dev_get_retry_count, 8);
1436+
1437+
ret = preflight(&data, 1);
1438+
assert_int_equal(ret, EOK);
1439+
1440+
talloc_free(tmp_ctx);
1441+
}
1442+
13521443
int main(int argc, const char *argv[])
13531444
{
13541445
poptContext pc;
@@ -1396,6 +1487,9 @@ int main(int argc, const char *argv[])
13961487
cmocka_unit_test(test_authenticate_integration),
13971488
cmocka_unit_test(test_get_assert_data_integration),
13981489
cmocka_unit_test(test_verify_assert_data_integration),
1490+
cmocka_unit_test(test_get_device_pin_retries_success),
1491+
cmocka_unit_test(test_get_device_pin_retries_failure),
1492+
cmocka_unit_test(test_preflight_integration),
13991493
};
14001494

14011495
/* Set debug level to invalid value so we can decide if -d 0 was used. */

0 commit comments

Comments
 (0)