mirror of
https://git.libssh.org/projects/libssh.git
synced 2025-12-06 13:20:57 +03:00
tests(fido2): add tests for the usb-hid security key callbacks
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com> Reviewed-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
This commit is contained in:
@@ -112,6 +112,13 @@ if (UNIX AND NOT WIN32)
|
|||||||
endif (WITH_SERVER)
|
endif (WITH_SERVER)
|
||||||
endif (UNIX AND NOT WIN32)
|
endif (UNIX AND NOT WIN32)
|
||||||
|
|
||||||
|
if (HAVE_LIBFIDO2)
|
||||||
|
set(LIBSSH_UNIT_TESTS
|
||||||
|
${LIBSSH_UNIT_TESTS}
|
||||||
|
torture_sk_usbhid
|
||||||
|
)
|
||||||
|
endif (HAVE_LIBFIDO2)
|
||||||
|
|
||||||
foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS})
|
foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS})
|
||||||
add_cmocka_test(${_UNIT_TEST}
|
add_cmocka_test(${_UNIT_TEST}
|
||||||
SOURCES ${_UNIT_TEST}.c
|
SOURCES ${_UNIT_TEST}.c
|
||||||
|
|||||||
383
tests/unittests/torture_sk_usbhid.c
Normal file
383
tests/unittests/torture_sk_usbhid.c
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
/*
|
||||||
|
* torture_sk_usbhid.c - Torture tests for security key USB-HID
|
||||||
|
* callbacks.
|
||||||
|
*
|
||||||
|
* This file is part of the SSH Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Praneeth Sarode <praneethsarode@gmail.com>
|
||||||
|
*
|
||||||
|
* The SSH Library is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2.1 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* The SSH Library is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with the SSH Library; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||||
|
* MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#define LIBSSH_STATIC
|
||||||
|
|
||||||
|
#include "libssh/sk_common.h"
|
||||||
|
#include "torture.h"
|
||||||
|
#include "torture_sk.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These tests require at least one FIDO2 device to be connected
|
||||||
|
* and the environment variables TORTURE_SK_USBHID and TORTURE_SK_PIN to be set.
|
||||||
|
*
|
||||||
|
* If TORTURE_SK_USBHID is not set, these tests will be skipped.
|
||||||
|
* To enable these tests, set both environment variables before running:
|
||||||
|
*
|
||||||
|
* export TORTURE_SK_USBHID=1
|
||||||
|
* export TORTURE_SK_PIN=your_device_pin
|
||||||
|
*
|
||||||
|
* The TORTURE_SK_PIN environment variable should contain the PIN used to
|
||||||
|
* unlock the FIDO2 device for operations.
|
||||||
|
*
|
||||||
|
* Note that these tests must be run in the order that they are defined in, as
|
||||||
|
* the signing tests rely on the output of the enrollment tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const char *test_pin = NULL;
|
||||||
|
static const char *test_application = "ssh:test@example.com";
|
||||||
|
|
||||||
|
static const uint8_t dummy_data[] = {
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||||
|
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||||
|
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
|
||||||
|
|
||||||
|
/* Global variables to store key handles for signing tests */
|
||||||
|
static uint8_t *ecdsa_key_handle = NULL;
|
||||||
|
static size_t ecdsa_key_handle_len = 0;
|
||||||
|
static uint8_t *ed25519_key_handle = NULL;
|
||||||
|
static size_t ed25519_key_handle_len = 0;
|
||||||
|
|
||||||
|
/* Check if tests should run */
|
||||||
|
static bool should_run_tests(void)
|
||||||
|
{
|
||||||
|
char *env = getenv("TORTURE_SK_USBHID");
|
||||||
|
return (env != NULL && env[0] != '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sk_option **create_user_id_option(const char *user_id)
|
||||||
|
{
|
||||||
|
struct sk_option **array = NULL, *option = NULL;
|
||||||
|
|
||||||
|
array = calloc(2, sizeof(struct sk_option *));
|
||||||
|
assert_non_null(array);
|
||||||
|
|
||||||
|
option = calloc(1, sizeof(struct sk_option));
|
||||||
|
assert_non_null(option);
|
||||||
|
|
||||||
|
option->name = strdup(SSH_SK_OPTION_NAME_USER_ID);
|
||||||
|
assert_non_null(option->name);
|
||||||
|
option->value = strdup(user_id);
|
||||||
|
assert_non_null(option->value);
|
||||||
|
option->required = 0;
|
||||||
|
|
||||||
|
array[0] = option;
|
||||||
|
array[1] = NULL;
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_enroll_generic_key(enum ssh_keytypes_e key_type)
|
||||||
|
{
|
||||||
|
const struct ssh_sk_callbacks_struct *callbacks = NULL;
|
||||||
|
struct sk_enroll_response *response = NULL;
|
||||||
|
struct sk_option **options = NULL;
|
||||||
|
const char *user_id = NULL;
|
||||||
|
uint8_t **key_handle_out = NULL;
|
||||||
|
size_t *key_handle_len_out = NULL;
|
||||||
|
int rc, flags;
|
||||||
|
|
||||||
|
callbacks = ssh_sk_get_default_callbacks();
|
||||||
|
assert_non_null(callbacks);
|
||||||
|
assert_true(ssh_callbacks_exists(callbacks, enroll));
|
||||||
|
|
||||||
|
/* Setup based on key type */
|
||||||
|
switch (key_type) {
|
||||||
|
case SSH_SK_ECDSA:
|
||||||
|
user_id = "libssh_test_ecdsa_sk";
|
||||||
|
key_handle_out = &ecdsa_key_handle;
|
||||||
|
key_handle_len_out = &ecdsa_key_handle_len;
|
||||||
|
break;
|
||||||
|
case SSH_SK_ED25519:
|
||||||
|
user_id = "libssh_test_ed25519_sk";
|
||||||
|
key_handle_out = &ed25519_key_handle;
|
||||||
|
key_handle_len_out = &ed25519_key_handle_len;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Should never reach here */
|
||||||
|
assert_true(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = create_user_id_option(user_id);
|
||||||
|
|
||||||
|
/* Enroll non-resident key */
|
||||||
|
flags = SSH_SK_USER_PRESENCE_REQD;
|
||||||
|
rc = callbacks->enroll(key_type,
|
||||||
|
dummy_data,
|
||||||
|
sizeof(dummy_data),
|
||||||
|
test_application,
|
||||||
|
flags,
|
||||||
|
test_pin,
|
||||||
|
options,
|
||||||
|
&response);
|
||||||
|
assert_int_equal(rc, SSH_OK);
|
||||||
|
assert_sk_enroll_response(response, flags);
|
||||||
|
|
||||||
|
/* Store the non-resident key handle for signing tests */
|
||||||
|
*key_handle_out = calloc(response->key_handle_len, 1);
|
||||||
|
assert_non_null(*key_handle_out);
|
||||||
|
memcpy(*key_handle_out, response->key_handle, response->key_handle_len);
|
||||||
|
*key_handle_len_out = response->key_handle_len;
|
||||||
|
|
||||||
|
SK_ENROLL_RESPONSE_FREE(response);
|
||||||
|
SK_OPTIONS_FREE(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
torture_sk_usbhid_enroll_generic_resident_key(enum ssh_keytypes_e key_type)
|
||||||
|
{
|
||||||
|
const struct ssh_sk_callbacks_struct *callbacks = NULL;
|
||||||
|
struct sk_enroll_response *response = NULL;
|
||||||
|
struct sk_option **options = NULL;
|
||||||
|
const char *user_id = NULL;
|
||||||
|
int rc, flags;
|
||||||
|
|
||||||
|
callbacks = ssh_sk_get_default_callbacks();
|
||||||
|
assert_non_null(callbacks);
|
||||||
|
assert_true(ssh_callbacks_exists(callbacks, enroll));
|
||||||
|
|
||||||
|
/* Setup based on key type */
|
||||||
|
switch (key_type) {
|
||||||
|
case SSH_SK_ECDSA:
|
||||||
|
user_id = "libssh_test_ecdsa_sk";
|
||||||
|
break;
|
||||||
|
case SSH_SK_ED25519:
|
||||||
|
user_id = "libssh_test_ed25519_sk";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Should never reach here */
|
||||||
|
assert_true(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options = create_user_id_option(user_id);
|
||||||
|
|
||||||
|
/* Enroll first resident key */
|
||||||
|
flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY |
|
||||||
|
SSH_SK_FORCE_OPERATION;
|
||||||
|
rc = callbacks->enroll(key_type,
|
||||||
|
dummy_data,
|
||||||
|
sizeof(dummy_data),
|
||||||
|
test_application,
|
||||||
|
flags,
|
||||||
|
test_pin,
|
||||||
|
options,
|
||||||
|
&response);
|
||||||
|
assert_int_equal(rc, SSH_OK);
|
||||||
|
assert_sk_enroll_response(response, flags);
|
||||||
|
SK_ENROLL_RESPONSE_FREE(response);
|
||||||
|
|
||||||
|
/* Try to enroll same resident key again - should fail with
|
||||||
|
* SSH_SK_ERR_CREDENTIAL_EXISTS */
|
||||||
|
flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY;
|
||||||
|
rc = callbacks->enroll(key_type,
|
||||||
|
dummy_data,
|
||||||
|
sizeof(dummy_data),
|
||||||
|
test_application,
|
||||||
|
flags,
|
||||||
|
test_pin,
|
||||||
|
options,
|
||||||
|
&response);
|
||||||
|
assert_int_equal(rc, SSH_SK_ERR_CREDENTIAL_EXISTS);
|
||||||
|
SK_ENROLL_RESPONSE_FREE(response);
|
||||||
|
|
||||||
|
/* The force operation flag should overwrite the existing resident key with
|
||||||
|
* new one */
|
||||||
|
flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY |
|
||||||
|
SSH_SK_FORCE_OPERATION;
|
||||||
|
rc = callbacks->enroll(key_type,
|
||||||
|
dummy_data,
|
||||||
|
sizeof(dummy_data),
|
||||||
|
test_application,
|
||||||
|
flags,
|
||||||
|
test_pin,
|
||||||
|
options,
|
||||||
|
&response);
|
||||||
|
assert_int_equal(rc, SSH_OK);
|
||||||
|
assert_sk_enroll_response(response, flags);
|
||||||
|
SK_ENROLL_RESPONSE_FREE(response);
|
||||||
|
SK_OPTIONS_FREE(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_enroll_ecdsa_key(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
torture_sk_usbhid_enroll_generic_key(SSH_SK_ECDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_enroll_ed25519_key(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
torture_sk_usbhid_enroll_generic_key(SSH_SK_ED25519);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
torture_sk_usbhid_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ECDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
torture_sk_usbhid_enroll_ed25519_resident_key(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ED25519);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_sign_generic(enum ssh_keytypes_e key_type)
|
||||||
|
{
|
||||||
|
const struct ssh_sk_callbacks_struct *callbacks;
|
||||||
|
struct sk_sign_response *response = NULL;
|
||||||
|
uint8_t *key_handle = NULL;
|
||||||
|
size_t key_handle_len = 0;
|
||||||
|
int rc, flags;
|
||||||
|
|
||||||
|
/* Setup based on key type */
|
||||||
|
switch (key_type) {
|
||||||
|
case SSH_SK_ECDSA:
|
||||||
|
key_handle = ecdsa_key_handle;
|
||||||
|
key_handle_len = ecdsa_key_handle_len;
|
||||||
|
break;
|
||||||
|
case SSH_SK_ED25519:
|
||||||
|
key_handle = ed25519_key_handle;
|
||||||
|
key_handle_len = ed25519_key_handle_len;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Should never reach here */
|
||||||
|
assert_true(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_non_null(key_handle);
|
||||||
|
assert_true(key_handle_len > 0);
|
||||||
|
|
||||||
|
callbacks = ssh_sk_get_default_callbacks();
|
||||||
|
assert_non_null(callbacks);
|
||||||
|
assert_true(ssh_callbacks_exists(callbacks, sign));
|
||||||
|
|
||||||
|
flags = SSH_SK_USER_PRESENCE_REQD;
|
||||||
|
rc = callbacks->sign(key_type,
|
||||||
|
dummy_data,
|
||||||
|
sizeof(dummy_data),
|
||||||
|
test_application,
|
||||||
|
key_handle,
|
||||||
|
key_handle_len,
|
||||||
|
flags,
|
||||||
|
test_pin,
|
||||||
|
NULL,
|
||||||
|
&response);
|
||||||
|
assert_int_equal(rc, SSH_OK);
|
||||||
|
assert_sk_sign_response(response, key_type);
|
||||||
|
|
||||||
|
SK_SIGN_RESPONSE_FREE(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_sign_ecdsa(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
torture_sk_usbhid_sign_generic(SSH_SK_ECDSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_sign_ed25519(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
torture_sk_usbhid_sign_generic(SSH_SK_ED25519);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void torture_sk_usbhid_load_resident_keys(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
const struct ssh_sk_callbacks_struct *callbacks;
|
||||||
|
struct sk_resident_key **resident_keys = NULL;
|
||||||
|
size_t num_keys = 0;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
callbacks = ssh_sk_get_default_callbacks();
|
||||||
|
assert_non_null(callbacks);
|
||||||
|
assert_true(ssh_callbacks_exists(callbacks, load_resident_keys));
|
||||||
|
|
||||||
|
rc = callbacks->load_resident_keys(test_pin,
|
||||||
|
NULL,
|
||||||
|
&resident_keys,
|
||||||
|
&num_keys);
|
||||||
|
assert_int_equal(rc, SSH_OK);
|
||||||
|
assert_non_null(resident_keys);
|
||||||
|
assert_true(num_keys > 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_keys; i++) {
|
||||||
|
assert_sk_resident_key(resident_keys[i]);
|
||||||
|
SK_RESIDENT_KEY_FREE(resident_keys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(resident_keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int setup(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
const char *test_pin_env = NULL;
|
||||||
|
|
||||||
|
test_pin_env = torture_get_sk_pin();
|
||||||
|
if (test_pin_env != NULL) {
|
||||||
|
test_pin = test_pin_env;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cleanup(UNUSED_PARAM(void **state))
|
||||||
|
{
|
||||||
|
SAFE_FREE(ecdsa_key_handle);
|
||||||
|
SAFE_FREE(ed25519_key_handle);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int torture_run_tests(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
bool should_run;
|
||||||
|
|
||||||
|
struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_key),
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_key),
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_resident_key),
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_resident_key),
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_sign_ecdsa),
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_sign_ed25519),
|
||||||
|
cmocka_unit_test(torture_sk_usbhid_load_resident_keys),
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only run tests if TORTURE_SK_USBHID environment variable is set
|
||||||
|
* and we expect a FIDO2 device to be available.
|
||||||
|
*/
|
||||||
|
should_run = should_run_tests();
|
||||||
|
if (!should_run) {
|
||||||
|
printf("Skipping sk_usbhid tests: TORTURE_SK_USBHID not set\n");
|
||||||
|
return 0; /* Success, but no tests run */
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_init();
|
||||||
|
rc = cmocka_run_group_tests(tests, setup, cleanup);
|
||||||
|
ssh_finalize();
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user