1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-12-06 13:20:57 +03:00
Files
libssh/tests/torture_sk.c
Praneeth Sarode 5937b5ba4e feat(torture_sk): add functions to validate security key signatures and to create PKI context
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30

396 lines
12 KiB
C

/*
* torture_sk.c - torture library for testing security keys
*
* 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 "torture_sk.h"
#include "libssh/pki.h"
#include "libssh/pki_priv.h"
#include "libssh/sk_api.h" /* For SSH_SK_* flag definitions */
void assert_sk_key_valid(ssh_key key,
enum ssh_keytypes_e expected_type,
bool private)
{
char *app_str = NULL;
const char *expected_type_str = NULL;
assert_non_null(key);
assert_true(is_sk_key_type(expected_type));
assert_int_equal(key->type, expected_type);
if (private) {
assert_int_equal(key->flags,
SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC);
} else {
assert_int_equal(key->flags, SSH_KEY_FLAG_PUBLIC);
}
expected_type_str = ssh_key_type_to_char(expected_type);
assert_non_null(expected_type_str);
assert_non_null(key->type_c);
assert_string_equal(key->type_c, expected_type_str);
/* Validate security key specific fields */
assert_non_null(key->sk_application);
/* Validate application string format and content */
app_str = ssh_string_to_char(key->sk_application);
assert_non_null(app_str);
assert_true(ssh_string_len(key->sk_application) >= 4);
assert_true(strncmp(app_str, "ssh:", 4) == 0);
ssh_string_free_char(app_str);
if (private) {
assert_non_null(key->sk_key_handle);
assert_true(ssh_string_len(key->sk_key_handle) > 0);
}
const uint8_t allowed_flags = SSH_SK_USER_PRESENCE_REQD |
SSH_SK_USER_VERIFICATION_REQD |
SSH_SK_RESIDENT_KEY | SSH_SK_FORCE_OPERATION;
/* Validate sk_flags contain only allowed bits */
uint8_t flags = key->sk_flags;
assert_int_equal(flags & ~allowed_flags, 0);
/* Validate underlying cryptographic key exists based on type */
switch (expected_type) {
case SSH_KEYTYPE_SK_ECDSA:
#if defined(HAVE_LIBGCRYPT)
assert_non_null(key->ecdsa);
#elif defined(HAVE_LIBMBEDCRYPTO)
assert_non_null(key->ecdsa);
#elif defined(HAVE_LIBCRYPTO)
assert_non_null(key->key);
#endif
break;
case SSH_KEYTYPE_SK_ED25519:
#if defined(HAVE_LIBCRYPTO)
assert_non_null(key->key);
#elif !defined(HAVE_LIBCRYPTO)
assert_non_null(key->ed25519_pubkey);
#endif
break;
default:
/* Should not reach here */
assert_true(0);
break;
}
}
void assert_sk_signature_valid(ssh_signature signature,
enum ssh_keytypes_e expected_type,
ssh_key signing_key,
const uint8_t *data,
size_t data_len)
{
uint8_t valid_flags;
const char *expected_type_str = NULL;
ssh_string sig_blob = NULL;
ssh_signature reconstructed = NULL;
ssh_buffer sk_sig_buffer = NULL;
int rc;
/* Basic null and type validation */
assert_non_null(signature);
assert_int_equal(signature->type, expected_type);
/* Validate hash type is appropriate for security keys */
switch (expected_type) {
case SSH_KEYTYPE_SK_ECDSA:
assert_int_equal(signature->hash_type, SSH_DIGEST_SHA256);
break;
case SSH_KEYTYPE_SK_ED25519:
assert_int_equal(signature->hash_type, SSH_DIGEST_AUTO);
break;
default:
/* Should not reach here */
assert_true(0);
break;
}
expected_type_str = ssh_key_type_to_char(expected_type);
assert_non_null(signature->type_c);
assert_string_equal(signature->type_c, expected_type_str);
/* Check that only valid SK flags are set */
valid_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD;
assert_int_equal(signature->sk_flags & ~valid_flags, 0);
assert_true(signature->sk_flags & SSH_SK_USER_PRESENCE_REQD);
assert_true(signature->sk_counter > 0);
assert_non_null(signature->raw_sig);
assert_true(ssh_string_len(signature->raw_sig) > 0);
rc = ssh_pki_export_signature_blob(signature, &sig_blob);
assert_int_equal(rc, SSH_OK);
assert_non_null(sig_blob);
assert_non_null(signing_key);
rc = ssh_pki_import_signature_blob(sig_blob, signing_key, &reconstructed);
assert_int_equal(rc, SSH_OK);
assert_non_null(reconstructed);
rc = pki_sk_signature_buffer_prepare(signing_key,
reconstructed,
data,
data_len,
&sk_sig_buffer);
assert_int_equal(rc, SSH_OK);
assert_non_null(sk_sig_buffer);
rc = pki_verify_data_signature(reconstructed,
signing_key,
ssh_buffer_get(sk_sig_buffer),
ssh_buffer_get_len(sk_sig_buffer));
assert_int_equal(rc, SSH_OK);
SSH_BUFFER_FREE(sk_sig_buffer);
ssh_signature_free(reconstructed);
ssh_string_free(sig_blob);
}
ssh_pki_ctx
torture_create_sk_pki_ctx(const char *application,
uint8_t flags,
const void *challenge_data,
size_t challenge_len,
ssh_auth_callback pin_callback,
const char *device_path,
const char *user_id,
const struct ssh_sk_callbacks_struct *sk_callbacks)
{
ssh_pki_ctx ctx = NULL;
ssh_buffer challenge_buffer = NULL;
int rc;
ctx = ssh_pki_ctx_new();
assert_non_null(ctx);
rc = ssh_pki_ctx_options_set(ctx,
SSH_PKI_OPTION_SK_APPLICATION,
application);
assert_int_equal(rc, SSH_OK);
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_SK_FLAGS, &flags);
assert_int_equal(rc, SSH_OK);
if (challenge_data != NULL && challenge_len > 0) {
challenge_buffer = ssh_buffer_new();
assert_non_null(challenge_buffer);
rc = ssh_buffer_add_data(challenge_buffer,
challenge_data,
challenge_len);
assert_int_equal(rc, SSH_OK);
}
rc = ssh_pki_ctx_options_set(ctx,
SSH_PKI_OPTION_SK_CHALLENGE,
challenge_buffer);
assert_int_equal(rc, SSH_OK);
SSH_BUFFER_FREE(challenge_buffer);
rc = ssh_pki_ctx_set_sk_pin_callback(ctx, pin_callback, NULL);
assert_int_equal(rc, SSH_OK);
if (device_path != NULL) {
rc = ssh_pki_ctx_sk_callbacks_option_set(ctx,
SSH_SK_OPTION_NAME_DEVICE_PATH,
device_path,
false);
assert_int_equal(rc, SSH_OK);
}
if (user_id != NULL) {
rc = ssh_pki_ctx_sk_callbacks_option_set(ctx,
SSH_SK_OPTION_NAME_USER_ID,
user_id,
false);
assert_int_equal(rc, SSH_OK);
}
if (sk_callbacks != NULL) {
rc = ssh_pki_ctx_options_set(ctx,
SSH_PKI_OPTION_SK_CALLBACKS,
sk_callbacks);
assert_int_equal(rc, SSH_OK);
}
return ctx;
}
void assert_sk_enroll_response(struct sk_enroll_response *response, int flags)
{
assert_non_null(response);
assert_non_null(response->public_key);
assert_true(response->public_key_len > 0);
assert_non_null(response->key_handle);
assert_true(response->key_handle_len > 0);
assert_non_null(response->signature);
assert_true(response->signature_len > 0);
/*
* This check might fail for some authenticators, as returning an
* attestation certificate as part of the attestation statement is not
* mandated by the FIDO2 standard.
*/
assert_non_null(response->attestation_cert);
assert_true(response->attestation_cert_len > 0);
assert_non_null(response->authdata);
assert_true(response->authdata_len > 0);
assert_int_equal(response->flags, flags);
}
void assert_sk_sign_response(struct sk_sign_response *response,
enum ssh_keytypes_e key_type)
{
assert_non_null(response);
assert_non_null(response->sig_r);
assert_true(response->sig_r_len > 0);
/* sig_s is NULL for Ed25519, present for ECDSA */
switch (key_type) {
case SSH_SK_ECDSA:
assert_non_null(response->sig_s);
assert_true(response->sig_s_len > 0);
break;
case SSH_SK_ED25519:
assert_null(response->sig_s);
assert_int_equal(response->sig_s_len, 0);
break;
default:
/* Should not reach here */
assert_true(0);
break;
}
}
void assert_sk_resident_key(struct sk_resident_key *resident_key)
{
assert_non_null(resident_key);
assert_non_null(resident_key->application);
assert_true(strlen(resident_key->application) > 0);
assert_non_null(resident_key->user_id);
assert_true(resident_key->user_id_len > 0);
assert_non_null(resident_key->key.public_key);
assert_true(resident_key->key.public_key_len > 0);
assert_non_null(resident_key->key.key_handle);
assert_true(resident_key->key.key_handle_len > 0);
}
const char *torture_get_sk_pin(void)
{
const char *pin = getenv("TORTURE_SK_PIN");
return (pin != NULL && pin[0] != '\0') ? pin : NULL;
}
#ifdef HAVE_SK_DUMMY
/* External declarations for sk-dummy library functions
* These match the signatures in openssh sk-api.h */
extern uint32_t sk_api_version(void);
extern int sk_enroll(uint32_t alg,
const uint8_t *challenge,
size_t challenge_len,
const char *application,
uint8_t flags,
const char *pin,
struct sk_option **options,
struct sk_enroll_response **enroll_response);
extern int sk_sign(uint32_t alg,
const uint8_t *data,
size_t data_len,
const char *application,
const uint8_t *key_handle,
size_t key_handle_len,
uint8_t flags,
const char *pin,
struct sk_option **options,
struct sk_sign_response **sign_response);
extern int sk_load_resident_keys(const char *pin,
struct sk_option **options,
struct sk_resident_key ***resident_keys,
size_t *num_keys_found);
static struct ssh_sk_callbacks_struct sk_dummy_callbacks = {
.api_version = sk_api_version,
.enroll = sk_enroll,
.sign = sk_sign,
.load_resident_keys = sk_load_resident_keys,
};
#endif /* HAVE_SK_DUMMY */
#ifdef WITH_FIDO2
const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void)
{
#ifdef HAVE_SK_DUMMY
ssh_callbacks_init(&sk_dummy_callbacks);
return &sk_dummy_callbacks;
#else
return NULL;
#endif /* HAVE_SK_DUMMY */
}
const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void)
{
const char *env = getenv("TORTURE_SK_USBHID");
bool torture_sk_usbhid = (env != NULL && env[0] != '\0');
if (torture_sk_usbhid) {
return ssh_sk_get_default_callbacks();
} else {
return torture_get_sk_dummy_callbacks();
}
}
#endif /* WITH_FIDO2 */
bool torture_sk_is_using_sk_dummy(void)
{
const char *env = getenv("TORTURE_SK_USBHID");
/* Return true if using sk-dummy callbacks (when TORTURE_SK_USBHID is NOT
* set) */
return (env == NULL || env[0] == '\0');
}