1
0
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:
Praneeth Sarode
2025-10-23 22:22:50 +05:30
parent e56af9fa79
commit acc080ac03
2 changed files with 390 additions and 0 deletions

View File

@@ -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

View 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;
}