mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-02-07 02:39:48 +09:00
feat(pki): add security key support with enrollment, signing, and resident key loading functions
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:
@@ -727,8 +727,13 @@ LIBSSH_API uint32_t ssh_key_get_sk_flags(const ssh_key key);
|
||||
LIBSSH_API ssh_string ssh_key_get_sk_application(const ssh_key key);
|
||||
LIBSSH_API ssh_string ssh_key_get_sk_user_id(const ssh_key key);
|
||||
|
||||
LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter,
|
||||
ssh_key *pkey);
|
||||
SSH_DEPRECATED LIBSSH_API int
|
||||
ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey);
|
||||
|
||||
LIBSSH_API int ssh_pki_generate_key(enum ssh_keytypes_e type,
|
||||
ssh_pki_ctx pki_context,
|
||||
ssh_key *pkey);
|
||||
|
||||
LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key,
|
||||
const char *passphrase,
|
||||
ssh_auth_callback auth_fn,
|
||||
@@ -929,6 +934,29 @@ enum ssh_pki_options_e {
|
||||
SSH_PKI_OPTION_SK_CALLBACKS,
|
||||
};
|
||||
|
||||
/* FIDO2/U2F Operation Flags */
|
||||
|
||||
/** Requires user presence confirmation (tap/touch) */
|
||||
#ifndef SSH_SK_USER_PRESENCE_REQD
|
||||
#define SSH_SK_USER_PRESENCE_REQD 0x01
|
||||
#endif
|
||||
|
||||
/** Requires user verification (PIN/biometric) - FIDO2 only */
|
||||
#ifndef SSH_SK_USER_VERIFICATION_REQD
|
||||
#define SSH_SK_USER_VERIFICATION_REQD 0x04
|
||||
#endif
|
||||
|
||||
/** Force resident key enrollment even if a resident key with given user ID
|
||||
* already exists - FIDO2 only */
|
||||
#ifndef SSH_SK_FORCE_OPERATION
|
||||
#define SSH_SK_FORCE_OPERATION 0x10
|
||||
#endif
|
||||
|
||||
/** Create/use resident key stored on authenticator - FIDO2 only */
|
||||
#ifndef SSH_SK_RESIDENT_KEY
|
||||
#define SSH_SK_RESIDENT_KEY 0x20
|
||||
#endif
|
||||
|
||||
LIBSSH_API ssh_pki_ctx ssh_pki_ctx_new(void);
|
||||
|
||||
LIBSSH_API int ssh_pki_ctx_options_set(ssh_pki_ctx context,
|
||||
@@ -963,6 +991,13 @@ LIBSSH_API void ssh_pki_ctx_free(ssh_pki_ctx context);
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Security key resident keys API */
|
||||
|
||||
LIBSSH_API int
|
||||
ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context,
|
||||
ssh_key **resident_keys_result,
|
||||
size_t *num_keys_found_result);
|
||||
|
||||
#ifndef LIBSSH_LEGACY_0_4
|
||||
#include "libssh/legacy.h"
|
||||
#endif
|
||||
|
||||
90
include/libssh/pki_sk.h
Normal file
90
include/libssh/pki_sk.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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, version 2.1 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef PKI_SK_H
|
||||
#define PKI_SK_H
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/pki.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SSH_SK_MAX_USER_ID_LEN 64
|
||||
|
||||
/**
|
||||
* @brief Enroll a new security key using a U2F/FIDO2 authenticator
|
||||
*
|
||||
* Creates a new security key credential configured according to the parameters
|
||||
* in the PKI context. This function handles key enrollment for both ECDSA and
|
||||
* Ed25519 algorithms, generates appropriate challenges, and returns the
|
||||
* enrolled key with optional attestation data.
|
||||
*
|
||||
* The PKI context must be configured with appropriate security key parameters
|
||||
* using ssh_pki_ctx_options_set() before calling this function. Required
|
||||
* options include SSH_PKI_OPTION_SK_APPLICATION, SSH_PKI_OPTION_SK_USER_ID, and
|
||||
* SSH_PKI_OPTION_SK_CALLBACKS.
|
||||
*
|
||||
* @param[in] context The PKI context containing security key configuration and
|
||||
* parameters
|
||||
* @param[in] key_type The type of key to enroll (SSH_KEYTYPE_SK_ECDSA or
|
||||
* SSH_KEYTYPE_SK_ED25519)
|
||||
* @param[out] enrolled_key_result Pointer to store the enrolled ssh_key
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on failure
|
||||
*
|
||||
* @see ssh_pki_ctx_new()
|
||||
* @see ssh_pki_ctx_options_set()
|
||||
* @see ssh_pki_ctx_get_sk_attestation_buffer()
|
||||
*/
|
||||
int pki_sk_enroll_key(ssh_pki_ctx context,
|
||||
enum ssh_keytypes_e key_type,
|
||||
ssh_key *enrolled_key_result);
|
||||
|
||||
/**
|
||||
* @brief Sign arbitrary data using a security key and a PKI context
|
||||
*
|
||||
* This function performs signing operations configured according to the
|
||||
* parameters in the PKI context and returns a properly formatted
|
||||
* ssh_signature. The caller must free the signature when it is no longer
|
||||
* needed.
|
||||
*
|
||||
* The PKI context should be configured with appropriate security key parameters
|
||||
* using ssh_pki_ctx_options_set() before calling this function. The security
|
||||
* key must have been previously enrolled or loaded.
|
||||
*
|
||||
* @param[in] context The PKI context containing security key configuration and
|
||||
* parameters
|
||||
* @param[in] key The security key to use for signing
|
||||
* @param[in] data The data to sign
|
||||
* @param[in] data_len Length of data to sign
|
||||
*
|
||||
* @return A valid ssh_signature on success, NULL on failure
|
||||
*
|
||||
* @see ssh_pki_ctx_new()
|
||||
* @see ssh_pki_ctx_options_set()
|
||||
* @see pki_sk_enroll_key()
|
||||
* @see ssh_signature_free()
|
||||
*/
|
||||
ssh_signature pki_sk_do_sign(ssh_pki_ctx context,
|
||||
const ssh_key key,
|
||||
const uint8_t *data,
|
||||
size_t data_len);
|
||||
|
||||
#endif /* PKI_SK_H */
|
||||
@@ -310,6 +310,7 @@ if (WITH_FIDO2)
|
||||
set(libssh_SRCS
|
||||
${libssh_SRCS}
|
||||
sk_common.c
|
||||
pki_sk.c
|
||||
)
|
||||
|
||||
if (HAVE_LIBFIDO2)
|
||||
|
||||
@@ -503,4 +503,6 @@ LIBSSH_AFTER_4_10_0
|
||||
ssh_key_get_sk_flags;
|
||||
ssh_key_get_sk_application;
|
||||
ssh_key_get_sk_user_id;
|
||||
ssh_pki_generate_key;
|
||||
ssh_sk_resident_keys_load;
|
||||
} LIBSSH_4_10_0;
|
||||
|
||||
183
src/pki.c
183
src/pki.c
@@ -43,15 +43,18 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/session.h"
|
||||
#include "libssh/priv.h"
|
||||
#include "libssh/pki.h"
|
||||
#include "libssh/pki_priv.h"
|
||||
#include "libssh/keys.h"
|
||||
#include "libssh/buffer.h"
|
||||
#include "libssh/misc.h"
|
||||
#include "libssh/agent.h"
|
||||
#include "libssh/buffer.h"
|
||||
#include "libssh/keys.h"
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/misc.h"
|
||||
#include "libssh/pki.h"
|
||||
#include "libssh/pki_context.h"
|
||||
#include "libssh/pki_priv.h"
|
||||
#include "libssh/pki_sk.h"
|
||||
#include "libssh/priv.h"
|
||||
#include "libssh/session.h"
|
||||
#include "libssh/sk_common.h" /* For SK_NOT_SUPPORTED_MSG */
|
||||
|
||||
#ifndef MAX_LINE_SIZE
|
||||
#define MAX_LINE_SIZE 4096
|
||||
@@ -2257,7 +2260,9 @@ int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates a key pair.
|
||||
* @internal
|
||||
*
|
||||
* @brief Internal function to generate a key pair.
|
||||
*
|
||||
* @param[in] type Type of key to create
|
||||
*
|
||||
@@ -2268,17 +2273,19 @@ int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey)
|
||||
* to free the memory using ssh_key_free().
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on error.
|
||||
*
|
||||
* @warning Generating a key pair may take some time.
|
||||
*
|
||||
* @see ssh_key_free()
|
||||
*/
|
||||
int ssh_pki_generate(enum ssh_keytypes_e type, int parameter,
|
||||
ssh_key *pkey)
|
||||
static int pki_generate_key_internal(enum ssh_keytypes_e type,
|
||||
int parameter,
|
||||
ssh_key *pkey)
|
||||
{
|
||||
int rc;
|
||||
ssh_key key = ssh_key_new();
|
||||
ssh_key key = NULL;
|
||||
|
||||
if (pkey == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
key = ssh_key_new();
|
||||
if (key == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
@@ -2289,6 +2296,15 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter,
|
||||
|
||||
switch(type){
|
||||
case SSH_KEYTYPE_RSA:
|
||||
if (parameter != 0 && parameter < RSA_MIN_KEY_SIZE) {
|
||||
SSH_LOG(
|
||||
SSH_LOG_WARN,
|
||||
"RSA key size parameter (%d) is below minimum allowed (%d)",
|
||||
parameter,
|
||||
RSA_MIN_KEY_SIZE);
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = pki_key_generate_rsa(key, parameter);
|
||||
if(rc == SSH_ERROR)
|
||||
goto error;
|
||||
@@ -2350,6 +2366,105 @@ error:
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates a key pair.
|
||||
*
|
||||
* @param[in] type Type of key to create
|
||||
*
|
||||
* @param[in] parameter Parameter to the creation of key:
|
||||
* rsa : length of the key in bits (e.g. 1024, 2048, 4096)
|
||||
* If parameter is 0, then the default size will be used.
|
||||
* @param[out] pkey A pointer to store the allocated private key. You need
|
||||
* to free the memory using ssh_key_free().
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on error.
|
||||
*
|
||||
* @warning Generating a key pair may take some time.
|
||||
*
|
||||
* @see ssh_key_free()
|
||||
*/
|
||||
int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey)
|
||||
{
|
||||
return pki_generate_key_internal(type, parameter, pkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates a key pair.
|
||||
*
|
||||
* @param[in] type Type of key to create
|
||||
*
|
||||
* @param[in] pki_context PKI context containing various configuration
|
||||
* parameters and sub-contexts. Can be NULL for
|
||||
* standard SSH key types (RSA, ECDSA, ED25519) where
|
||||
* defaults will be used. Can also be NULL for security
|
||||
* key types (SK_*), in which case default callbacks and
|
||||
* settings will be used automatically.
|
||||
*
|
||||
* @param[out] pkey A pointer to store the allocated private key. You need
|
||||
* to free the memory using ssh_key_free().
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on error.
|
||||
*
|
||||
* @see ssh_pki_ctx_new()
|
||||
* @see ssh_key_free()
|
||||
*/
|
||||
int ssh_pki_generate_key(enum ssh_keytypes_e type,
|
||||
ssh_pki_ctx pki_context,
|
||||
ssh_key *pkey)
|
||||
{
|
||||
|
||||
/* Handle Security Key types with the specialized function */
|
||||
if (is_sk_key_type(type)) {
|
||||
#ifdef WITH_FIDO2
|
||||
ssh_pki_ctx temp_ctx = NULL;
|
||||
ssh_pki_ctx ctx_to_use = pki_context;
|
||||
int rc;
|
||||
|
||||
/* If no context provided, create a temporary default one */
|
||||
if (pki_context == NULL) {
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
"No PKI context provided, using the default one");
|
||||
|
||||
temp_ctx = ssh_pki_ctx_new();
|
||||
if (temp_ctx == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
ctx_to_use = temp_ctx;
|
||||
}
|
||||
|
||||
/* Verify that we have valid SK callbacks */
|
||||
if (ctx_to_use->sk_callbacks == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Missing SK callbacks in PKI context");
|
||||
if (temp_ctx != NULL) {
|
||||
SSH_PKI_CTX_FREE(temp_ctx);
|
||||
}
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = pki_sk_enroll_key(ctx_to_use, type, pkey);
|
||||
|
||||
/* Clean up temporary context if we created one */
|
||||
if (temp_ctx != NULL) {
|
||||
SSH_PKI_CTX_FREE(temp_ctx);
|
||||
}
|
||||
|
||||
return rc;
|
||||
#else /* WITH_FIDO2 */
|
||||
SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG);
|
||||
return SSH_ERROR;
|
||||
#endif /* WITH_FIDO2 */
|
||||
} else {
|
||||
int parameter = 0;
|
||||
|
||||
if (type == SSH_KEYTYPE_RSA && pki_context != NULL) {
|
||||
parameter = pki_context->rsa_key_size;
|
||||
}
|
||||
|
||||
return pki_generate_key_internal(type, parameter, pkey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a public key from a private key.
|
||||
*
|
||||
@@ -3636,10 +3751,38 @@ ssh_string ssh_pki_do_sign(ssh_session session,
|
||||
}
|
||||
|
||||
/* Generate the signature */
|
||||
sig = pki_do_sign(privkey,
|
||||
ssh_buffer_get(sign_input),
|
||||
ssh_buffer_get_len(sign_input),
|
||||
hash_type);
|
||||
if (is_sk_key_type(privkey->type)) {
|
||||
#ifdef WITH_FIDO2
|
||||
if (session->pki_context == NULL ||
|
||||
session->pki_context->sk_callbacks == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Missing PKI context or SK callbacks");
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = pki_key_check_hash_compatible(privkey, hash_type);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Incompatible hash type %d for sk key type %d",
|
||||
hash_type,
|
||||
privkey->type);
|
||||
goto end;
|
||||
}
|
||||
|
||||
sig = pki_sk_do_sign(session->pki_context,
|
||||
privkey,
|
||||
ssh_buffer_get(sign_input),
|
||||
ssh_buffer_get_len(sign_input));
|
||||
#else
|
||||
SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG);
|
||||
goto end;
|
||||
#endif /* WITH_FIDO2 */
|
||||
} else {
|
||||
sig = pki_do_sign(privkey,
|
||||
ssh_buffer_get(sign_input),
|
||||
ssh_buffer_get_len(sign_input),
|
||||
hash_type);
|
||||
}
|
||||
|
||||
if (sig == NULL) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
971
src/pki_sk.c
Normal file
971
src/pki_sk.c
Normal file
@@ -0,0 +1,971 @@
|
||||
/*
|
||||
* 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, version 2.1 of the License.
|
||||
*
|
||||
* 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"
|
||||
|
||||
#include "libssh/buffer.h"
|
||||
#include "libssh/pki_context.h"
|
||||
#include "libssh/pki_priv.h"
|
||||
#include "libssh/pki_sk.h"
|
||||
#include "libssh/sk_common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DEFAULT_PIN_PROMPT "Enter SK PIN: "
|
||||
#define PIN_BUF_SIZE 64
|
||||
|
||||
/**
|
||||
* @addtogroup libssh_pki
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Serialize FIDO2 attestation data into an SSH buffer
|
||||
*
|
||||
* Serializes the attestation certificate, signature, and authenticator data
|
||||
* from a FIDO2 enrollment response into an SSH buffer in the
|
||||
* "ssh-sk-attest-v01" format.
|
||||
*
|
||||
* @param[in] enroll_response The sk_enroll_response struct containing
|
||||
* attestation data from FIDO2 enrollment
|
||||
* @param[in,out] attestation_buffer SSH buffer to store the serialized
|
||||
* attestation data
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on failure
|
||||
*/
|
||||
static int pki_sk_serialise_attestation_cert(
|
||||
const struct sk_enroll_response *enroll_response,
|
||||
ssh_buffer attestation_buffer)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (attestation_buffer == NULL || enroll_response == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Parameters cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Check if attestation data is available */
|
||||
if (enroll_response->attestation_cert == NULL ||
|
||||
enroll_response->attestation_cert_len == 0) {
|
||||
SSH_LOG(SSH_LOG_INFO, "No attestation certificate available");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (enroll_response->signature == NULL ||
|
||||
enroll_response->signature_len == 0) {
|
||||
SSH_LOG(SSH_LOG_INFO, "No attestation signature available");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (enroll_response->authdata == NULL ||
|
||||
enroll_response->authdata_len == 0) {
|
||||
SSH_LOG(SSH_LOG_INFO, "No authenticator data available");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = ssh_buffer_pack(attestation_buffer,
|
||||
"sdPdPdPds",
|
||||
"ssh-sk-attest-v01",
|
||||
(uint32_t)enroll_response->attestation_cert_len,
|
||||
enroll_response->attestation_cert_len,
|
||||
enroll_response->attestation_cert,
|
||||
(uint32_t)enroll_response->signature_len,
|
||||
enroll_response->signature_len,
|
||||
enroll_response->signature,
|
||||
(uint32_t)enroll_response->authdata_len,
|
||||
enroll_response->authdata_len,
|
||||
enroll_response->authdata,
|
||||
(uint32_t)0, /* reserved flags */
|
||||
""); /* reserved */
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to pack attestation data into buffer");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an ssh_key from an sk_enroll_response struct
|
||||
*
|
||||
* Constructs an ssh_key structure from an sk_enroll_response
|
||||
* struct for both ECDSA and Ed25519 algorithms.
|
||||
*
|
||||
* @param[in] algorithm The algorithm type (SSH_SK_ECDSA or
|
||||
* SSH_SK_ED25519)
|
||||
* @param[in] application The application string (relying party ID)
|
||||
* @param[in] enroll_response The sk_enroll_response struct containing key data
|
||||
* @param[out] ssh_key_result Pointer to store the newly created ssh_key
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on failure
|
||||
*/
|
||||
static int pki_sk_enroll_response_to_ssh_key(
|
||||
int algorithm,
|
||||
const char *application,
|
||||
const struct sk_enroll_response *enroll_response,
|
||||
ssh_key *ssh_key_result)
|
||||
{
|
||||
ssh_key key_to_build = NULL;
|
||||
ssh_string public_key_string = NULL;
|
||||
int rc, ret = SSH_ERROR;
|
||||
|
||||
/* Validate input parameters */
|
||||
if (ssh_key_result == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "ssh_key pointer cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
*ssh_key_result = NULL;
|
||||
|
||||
if (enroll_response == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Enrollment response cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Validate response data */
|
||||
if (enroll_response->public_key == NULL ||
|
||||
enroll_response->key_handle == NULL) {
|
||||
SSH_LOG(
|
||||
SSH_LOG_WARN,
|
||||
"Invalid enrollment response: missing public key or key handle");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
key_to_build = ssh_key_new();
|
||||
if (key_to_build == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_key");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Set key type based on algorithm */
|
||||
switch (algorithm) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_SK_ECDSA:
|
||||
key_to_build->type = SSH_KEYTYPE_SK_ECDSA;
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_SK_ED25519:
|
||||
key_to_build->type = SSH_KEYTYPE_SK_ED25519;
|
||||
break;
|
||||
default:
|
||||
SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm);
|
||||
goto out;
|
||||
}
|
||||
key_to_build->type_c = ssh_key_type_to_char(key_to_build->type);
|
||||
|
||||
public_key_string = ssh_string_from_data(enroll_response->public_key,
|
||||
enroll_response->public_key_len);
|
||||
if (public_key_string == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create public key string");
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (algorithm) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_SK_ECDSA:
|
||||
rc = pki_pubkey_build_ecdsa(key_to_build,
|
||||
pki_key_ecdsa_nid_from_name("nistp256"),
|
||||
public_key_string);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key");
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_SK_ED25519:
|
||||
rc = pki_pubkey_build_ed25519(key_to_build, public_key_string);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to build ED25519 public key");
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set security key specific fields */
|
||||
key_to_build->sk_application = ssh_string_from_char(application);
|
||||
if (key_to_build->sk_application == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create sk_application string");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Set key handle */
|
||||
key_to_build->sk_key_handle =
|
||||
ssh_string_from_data(enroll_response->key_handle,
|
||||
enroll_response->key_handle_len);
|
||||
if (key_to_build->sk_key_handle == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create sk_key_handle string");
|
||||
goto out;
|
||||
}
|
||||
|
||||
key_to_build->sk_reserved = ssh_string_from_data(NULL, 0);
|
||||
if (key_to_build->sk_reserved == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create sk_reserved string");
|
||||
goto out;
|
||||
}
|
||||
|
||||
key_to_build->sk_flags = enroll_response->flags;
|
||||
key_to_build->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC;
|
||||
|
||||
*ssh_key_result = key_to_build;
|
||||
key_to_build = NULL;
|
||||
ret = SSH_OK;
|
||||
|
||||
out:
|
||||
ssh_string_burn(public_key_string);
|
||||
SSH_STRING_FREE(public_key_string);
|
||||
SSH_KEY_FREE(key_to_build);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int pki_sk_enroll_key(ssh_pki_ctx context,
|
||||
enum ssh_keytypes_e key_type,
|
||||
ssh_key *enrolled_key_result)
|
||||
{
|
||||
const struct ssh_sk_callbacks_struct *sk_callbacks = NULL;
|
||||
|
||||
struct sk_enroll_response *enroll_response = NULL;
|
||||
ssh_key enrolled_key = NULL;
|
||||
|
||||
char pin_buf[PIN_BUF_SIZE] = {0};
|
||||
const char *pin_to_use = NULL;
|
||||
|
||||
unsigned char random_challenge[32];
|
||||
const unsigned char *challenge = NULL;
|
||||
size_t challenge_length = 0;
|
||||
|
||||
ssh_buffer challenge_buffer = NULL;
|
||||
ssh_buffer attestation = NULL;
|
||||
|
||||
int rc, ret = SSH_ERROR;
|
||||
int algorithm;
|
||||
|
||||
/* Validate input parameters */
|
||||
if (context == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "SK context cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (enrolled_key_result == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Enrolled key result pointer cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Initialize output parameter */
|
||||
*enrolled_key_result = NULL;
|
||||
|
||||
/* Clear any existing attestation data */
|
||||
SSH_BUFFER_FREE(context->sk_attestation_buffer);
|
||||
|
||||
/* Get security key callbacks from context */
|
||||
sk_callbacks = context->sk_callbacks;
|
||||
if (sk_callbacks == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (!ssh_callbacks_exists(sk_callbacks, enroll)) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Security key enroll callback is not implemented");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Validate required fields */
|
||||
if (context->sk_application == NULL || *context->sk_application == '\0') {
|
||||
SSH_LOG(SSH_LOG_WARN, "Application identifier cannot be NULL or empty");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Extract parameters from context */
|
||||
challenge_buffer = context->sk_challenge_buffer;
|
||||
|
||||
/* Determine algorithm based on key type */
|
||||
switch (key_type) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_KEYTYPE_SK_ECDSA:
|
||||
algorithm = SSH_SK_ECDSA;
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_KEYTYPE_SK_ED25519:
|
||||
algorithm = SSH_SK_ED25519;
|
||||
break;
|
||||
default:
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Unsupported key type for security key enrollment");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Determine challenge to use */
|
||||
if (challenge_buffer == NULL) {
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Using randomly generated challenge");
|
||||
|
||||
rc = ssh_get_random(random_challenge, sizeof(random_challenge), 0);
|
||||
if (rc != 1) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to generate random challenge");
|
||||
goto out;
|
||||
}
|
||||
|
||||
challenge = random_challenge;
|
||||
challenge_length = sizeof(random_challenge);
|
||||
|
||||
} else {
|
||||
challenge_length = ssh_buffer_get_len(challenge_buffer);
|
||||
if (challenge_length == 0) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Challenge buffer cannot be empty");
|
||||
goto out;
|
||||
}
|
||||
|
||||
challenge = ssh_buffer_get(challenge_buffer);
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Using provided challenge of length %zu",
|
||||
challenge_length);
|
||||
}
|
||||
|
||||
if (context->sk_pin_callback != NULL) {
|
||||
rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT,
|
||||
pin_buf,
|
||||
sizeof(pin_buf),
|
||||
0,
|
||||
0,
|
||||
context->sk_userdata);
|
||||
if (rc == SSH_OK) {
|
||||
pin_to_use = pin_buf;
|
||||
} else {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback");
|
||||
explicit_bzero(pin_buf, sizeof(pin_buf));
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN");
|
||||
}
|
||||
|
||||
rc = sk_callbacks->enroll(algorithm,
|
||||
challenge,
|
||||
challenge_length,
|
||||
context->sk_application,
|
||||
context->sk_flags,
|
||||
pin_to_use,
|
||||
context->sk_callbacks_options,
|
||||
&enroll_response);
|
||||
explicit_bzero(pin_buf, sizeof(pin_buf));
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Security key enroll callback failed: %s (%d)",
|
||||
ssh_sk_err_to_string(rc),
|
||||
rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Convert SK enroll response to ssh_key */
|
||||
rc = pki_sk_enroll_response_to_ssh_key(algorithm,
|
||||
context->sk_application,
|
||||
enroll_response,
|
||||
&enrolled_key);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to convert enroll response to ssh_key");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Try to serialize attestation data and store in context */
|
||||
attestation = ssh_buffer_new();
|
||||
if (attestation == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to allocate attestation buffer");
|
||||
goto out;
|
||||
} else {
|
||||
rc = pki_sk_serialise_attestation_cert(enroll_response, attestation);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
"Failed to serialize attestation data, continuing without "
|
||||
"attestation");
|
||||
} else {
|
||||
context->sk_attestation_buffer = attestation;
|
||||
attestation = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
*enrolled_key_result = enrolled_key;
|
||||
enrolled_key = NULL;
|
||||
ret = SSH_OK;
|
||||
|
||||
out:
|
||||
if (challenge == random_challenge) {
|
||||
explicit_bzero(random_challenge, sizeof(random_challenge));
|
||||
}
|
||||
|
||||
SK_ENROLL_RESPONSE_FREE(enroll_response);
|
||||
SSH_KEY_FREE(enrolled_key);
|
||||
SSH_BUFFER_FREE(attestation);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
pki_sk_pack_ecdsa_signature(const struct sk_sign_response *sign_response,
|
||||
ssh_buffer sig_buffer)
|
||||
{
|
||||
|
||||
bignum r_bn = NULL, s_bn = NULL;
|
||||
ssh_buffer inner_buffer = NULL;
|
||||
int rc = SSH_ERROR;
|
||||
|
||||
/* Convert raw r and s bytes to bignums */
|
||||
bignum_bin2bn(sign_response->sig_r, (int)sign_response->sig_r_len, &r_bn);
|
||||
if (r_bn == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_r to bignum");
|
||||
goto out;
|
||||
}
|
||||
|
||||
bignum_bin2bn(sign_response->sig_s, (int)sign_response->sig_s_len, &s_bn);
|
||||
if (s_bn == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_s to bignum");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Create inner buffer with r and s as SSH strings */
|
||||
inner_buffer = ssh_buffer_new();
|
||||
if (inner_buffer == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create inner buffer");
|
||||
goto out;
|
||||
}
|
||||
ssh_buffer_set_secure(inner_buffer);
|
||||
|
||||
rc = ssh_buffer_pack(inner_buffer, "BB", r_bn, s_bn);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to pack r and s into inner buffer");
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = ssh_buffer_pack(sig_buffer,
|
||||
"P",
|
||||
(size_t)ssh_buffer_get_len(inner_buffer),
|
||||
ssh_buffer_get(inner_buffer));
|
||||
if (rc != SSH_OK) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = SSH_OK;
|
||||
|
||||
out:
|
||||
SSH_BUFFER_FREE(inner_buffer);
|
||||
bignum_safe_free(s_bn);
|
||||
bignum_safe_free(r_bn);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
pki_sk_pack_ed25519_signature(const struct sk_sign_response *sign_response,
|
||||
ssh_buffer sig_buffer)
|
||||
{
|
||||
int rc = SSH_ERROR;
|
||||
|
||||
rc = ssh_buffer_pack(sig_buffer,
|
||||
"P",
|
||||
sign_response->sig_r_len,
|
||||
sign_response->sig_r);
|
||||
if (rc != SSH_OK) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an ssh_signature from a sk_sign_response structure
|
||||
*
|
||||
* Serializes a security key sign response into an ssh_signature structure
|
||||
* for both ECDSA and Ed25519 algorithms.
|
||||
*
|
||||
* @param[in] algorithm The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519)
|
||||
* @param[in] key_type The SSH key type for setting signature type
|
||||
* @param[in] sign_response The sk_sign_response containing signature data
|
||||
* @param[out] ssh_signature_result Pointer to store the created ssh_signature
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on failure
|
||||
*/
|
||||
static int pki_sk_sign_response_to_ssh_signature(
|
||||
int algorithm,
|
||||
enum ssh_keytypes_e key_type,
|
||||
const struct sk_sign_response *sign_response,
|
||||
ssh_signature *ssh_signature_result)
|
||||
{
|
||||
ssh_signature signature_to_build = NULL;
|
||||
ssh_buffer sig_buffer = NULL;
|
||||
int rc;
|
||||
|
||||
/* Validate input parameters */
|
||||
if (ssh_signature_result == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "ssh_signature pointer cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
*ssh_signature_result = NULL;
|
||||
|
||||
if (sign_response == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Sign response cannot be NULL");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Validate response data based on algorithm */
|
||||
switch (algorithm) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_SK_ECDSA:
|
||||
if (sign_response->sig_r == NULL || sign_response->sig_s == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Invalid ECDSA sign response: missing sig_r or sig_s");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_SK_ED25519:
|
||||
if (sign_response->sig_r == NULL ||
|
||||
sign_response->sig_r_len != ED25519_SIG_LEN) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Invalid sig_r in Ed25519 sign response");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Create new ssh_signature */
|
||||
signature_to_build = ssh_signature_new();
|
||||
if (signature_to_build == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_signature");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Set signature type and metadata */
|
||||
signature_to_build->type = key_type;
|
||||
signature_to_build->type_c = ssh_key_type_to_char(key_type);
|
||||
|
||||
/* Set security key specific fields */
|
||||
signature_to_build->sk_flags = sign_response->flags;
|
||||
signature_to_build->sk_counter = sign_response->counter;
|
||||
|
||||
/* Create a buffer to hold the signature data */
|
||||
sig_buffer = ssh_buffer_new();
|
||||
if (sig_buffer == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create signature buffer");
|
||||
goto error;
|
||||
}
|
||||
ssh_buffer_set_secure(sig_buffer);
|
||||
|
||||
/* Build the signature based on algorithm */
|
||||
switch (algorithm) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_SK_ECDSA:
|
||||
signature_to_build->hash_type = SSH_DIGEST_SHA256;
|
||||
|
||||
rc = pki_sk_pack_ecdsa_signature(sign_response, sig_buffer);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to pack ECDSA signature");
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_SK_ED25519:
|
||||
signature_to_build->hash_type = SSH_DIGEST_AUTO;
|
||||
|
||||
rc = pki_sk_pack_ed25519_signature(sign_response, sig_buffer);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to pack Ed25519 signature");
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set the signature data */
|
||||
signature_to_build->raw_sig =
|
||||
ssh_string_from_data(ssh_buffer_get(sig_buffer),
|
||||
ssh_buffer_get_len(sig_buffer));
|
||||
if (signature_to_build->raw_sig == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create raw signature string");
|
||||
goto error;
|
||||
}
|
||||
|
||||
*ssh_signature_result = signature_to_build;
|
||||
SSH_BUFFER_FREE(sig_buffer);
|
||||
|
||||
return SSH_OK;
|
||||
|
||||
error:
|
||||
SSH_SIGNATURE_FREE(signature_to_build);
|
||||
SSH_BUFFER_FREE(sig_buffer);
|
||||
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
ssh_signature pki_sk_do_sign(ssh_pki_ctx context,
|
||||
const ssh_key key,
|
||||
const unsigned char *data,
|
||||
size_t data_len)
|
||||
{
|
||||
const struct ssh_sk_callbacks_struct *sk_callbacks = NULL;
|
||||
struct sk_sign_response *sign_response = NULL;
|
||||
ssh_signature signature = NULL;
|
||||
|
||||
char pin_buf[PIN_BUF_SIZE] = {0};
|
||||
const char *pin_to_use = NULL;
|
||||
|
||||
int algorithm;
|
||||
int rc = SSH_ERROR;
|
||||
|
||||
/* Validate input parameters */
|
||||
if (context == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Context cannot be NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get security key callbacks from context */
|
||||
sk_callbacks = context->sk_callbacks;
|
||||
if (sk_callbacks == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ssh_callbacks_exists(sk_callbacks, sign)) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Security key sign callback is not implemented");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (key == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Key cannot be NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (data == NULL || data_len == 0) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Data cannot be NULL or empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Validate key type and determine algorithm */
|
||||
switch (key->type) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_KEYTYPE_SK_ECDSA:
|
||||
algorithm = SSH_SK_ECDSA;
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_KEYTYPE_SK_ED25519:
|
||||
algorithm = SSH_SK_ED25519;
|
||||
break;
|
||||
default:
|
||||
SSH_LOG(SSH_LOG_WARN, "Unsupported key type for security key signing");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Validate security key specific fields */
|
||||
if (key->sk_key_handle == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Security key handle cannot be NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (key->sk_application == NULL ||
|
||||
ssh_string_len(key->sk_application) == 0) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Security key application cannot be NULL or empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (context->sk_pin_callback != NULL) {
|
||||
rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT,
|
||||
pin_buf,
|
||||
sizeof(pin_buf),
|
||||
0,
|
||||
0,
|
||||
context->sk_userdata);
|
||||
if (rc == SSH_OK) {
|
||||
pin_to_use = pin_buf;
|
||||
} else {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback");
|
||||
explicit_bzero(pin_buf, sizeof(pin_buf));
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN");
|
||||
}
|
||||
|
||||
rc = sk_callbacks->sign(algorithm,
|
||||
data,
|
||||
data_len,
|
||||
ssh_string_get_char(key->sk_application),
|
||||
ssh_string_data(key->sk_key_handle),
|
||||
ssh_string_len(key->sk_key_handle),
|
||||
key->sk_flags,
|
||||
pin_to_use,
|
||||
context->sk_callbacks_options,
|
||||
&sign_response);
|
||||
explicit_bzero(pin_buf, sizeof(pin_buf));
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Security key sign callback failed: %s (%d)",
|
||||
ssh_sk_err_to_string(rc),
|
||||
rc);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Convert SK sign response to ssh_signature */
|
||||
rc = pki_sk_sign_response_to_ssh_signature(algorithm,
|
||||
key->type,
|
||||
sign_response,
|
||||
&signature);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to convert sign response to signature");
|
||||
goto error;
|
||||
}
|
||||
|
||||
SK_SIGN_RESPONSE_FREE(sign_response);
|
||||
return signature;
|
||||
|
||||
error:
|
||||
SK_SIGN_RESPONSE_FREE(sign_response);
|
||||
SSH_SIGNATURE_FREE(signature);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load resident keys from FIDO2 security keys
|
||||
*
|
||||
* This function loads all resident keys (discoverable credentials) stored
|
||||
* on FIDO2 security keys using the context's security key callbacks.
|
||||
* Resident keys are credentials stored directly on the security key device
|
||||
* and can be discovered without prior knowledge of key handles.
|
||||
*
|
||||
* Only resident keys with SSH application identifiers (starting with
|
||||
* "ssh:") are returned.
|
||||
*
|
||||
* @param[in] pki_context The PKI context containing security key callbacks.
|
||||
* Can be NULL, in which case a default context with
|
||||
* default callbacks will be used. If provided, the context
|
||||
* must have valid sk_callbacks configured.
|
||||
* @param[out] resident_keys_result Array of ssh_key structs representing the
|
||||
* resident keys found and loaded
|
||||
* @param[out] num_keys_found_result Number of resident keys found and loaded
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR on error
|
||||
*
|
||||
* @note The resident_keys_result array and its contents must be freed by
|
||||
* the caller using ssh_sk_resident_key_free() for each key and then
|
||||
* freeing the array itself when no longer needed.
|
||||
*/
|
||||
int ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context,
|
||||
ssh_key **resident_keys_result,
|
||||
size_t *num_keys_found_result)
|
||||
{
|
||||
const struct ssh_sk_callbacks_struct *sk_callbacks = NULL;
|
||||
struct sk_resident_key **raw_resident_keys = NULL;
|
||||
|
||||
ssh_key cur_resident_key = NULL, *result_keys = NULL, *temp_keys = NULL;
|
||||
ssh_pki_ctx temp_ctx = NULL;
|
||||
const struct ssh_pki_ctx_struct *ctx_to_use = NULL;
|
||||
|
||||
size_t raw_keys_count = 0, result_keys_count = 0, i;
|
||||
uint8_t sk_flags;
|
||||
|
||||
char pin_buf[PIN_BUF_SIZE] = {0};
|
||||
const char *pin_to_use = NULL;
|
||||
|
||||
int rc = SSH_ERROR;
|
||||
|
||||
/* If no context provided, create a temporary default one */
|
||||
if (pki_context == NULL) {
|
||||
SSH_LOG(SSH_LOG_INFO, "No PKI context provided, using the default one");
|
||||
|
||||
temp_ctx = ssh_pki_ctx_new();
|
||||
if (temp_ctx == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
ctx_to_use = temp_ctx;
|
||||
} else {
|
||||
ctx_to_use = pki_context;
|
||||
}
|
||||
|
||||
/* Get security key callbacks from context */
|
||||
sk_callbacks = ctx_to_use->sk_callbacks;
|
||||
if (sk_callbacks == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ssh_callbacks_exists(sk_callbacks, load_resident_keys)) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Security key load resident keys callback is not implemented");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (resident_keys_result == NULL || num_keys_found_result == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Result pointers cannot be NULL");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Initialize output parameters */
|
||||
*resident_keys_result = NULL;
|
||||
*num_keys_found_result = 0;
|
||||
|
||||
if (ctx_to_use->sk_pin_callback != NULL) {
|
||||
rc = ctx_to_use->sk_pin_callback(DEFAULT_PIN_PROMPT,
|
||||
pin_buf,
|
||||
sizeof(pin_buf),
|
||||
0,
|
||||
0,
|
||||
ctx_to_use->sk_userdata);
|
||||
if (rc == SSH_OK) {
|
||||
pin_to_use = pin_buf;
|
||||
} else {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback");
|
||||
explicit_bzero(pin_buf, sizeof(pin_buf));
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN");
|
||||
}
|
||||
|
||||
rc = sk_callbacks->load_resident_keys(pin_to_use,
|
||||
ctx_to_use->sk_callbacks_options,
|
||||
&raw_resident_keys,
|
||||
&raw_keys_count);
|
||||
explicit_bzero(pin_buf, sizeof(pin_buf));
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Security key load_resident_keys callback failed: %s (%d)",
|
||||
ssh_sk_err_to_string(rc),
|
||||
rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Process each raw resident key */
|
||||
for (i = 0; i < raw_keys_count; i++) {
|
||||
SSH_LOG(
|
||||
SSH_LOG_DEBUG,
|
||||
"Processing resident key %zu: alg %d, app \"%s\", user_id_len %zu",
|
||||
i,
|
||||
raw_resident_keys[i]->alg,
|
||||
raw_resident_keys[i]->application,
|
||||
raw_resident_keys[i]->user_id_len);
|
||||
|
||||
/* Filter out non-SSH applications */
|
||||
if (strncmp(raw_resident_keys[i]->application, "ssh:", 4) != 0) {
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Skipping non-SSH application: %s",
|
||||
raw_resident_keys[i]->application);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check supported algorithms */
|
||||
switch (raw_resident_keys[i]->alg) {
|
||||
#ifdef HAVE_ECC
|
||||
case SSH_SK_ECDSA:
|
||||
break;
|
||||
#endif /* HAVE_ECC */
|
||||
case SSH_SK_ED25519:
|
||||
break;
|
||||
default:
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Unsupported algorithm %d, skipping",
|
||||
raw_resident_keys[i]->alg);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set up security key flags */
|
||||
sk_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY;
|
||||
if (raw_resident_keys[i]->flags & SSH_SK_USER_VERIFICATION_REQD) {
|
||||
sk_flags |= SSH_SK_USER_VERIFICATION_REQD;
|
||||
}
|
||||
|
||||
/* Convert raw resident key to libssh key structure */
|
||||
rc =
|
||||
pki_sk_enroll_response_to_ssh_key(raw_resident_keys[i]->alg,
|
||||
raw_resident_keys[i]->application,
|
||||
&raw_resident_keys[i]->key,
|
||||
&cur_resident_key);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Failed to convert resident key %zu to ssh_key",
|
||||
i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set the security key flags on the converted key */
|
||||
cur_resident_key->sk_flags = sk_flags;
|
||||
|
||||
/* Copy user ID if present */
|
||||
if (raw_resident_keys[i]->user_id != NULL &&
|
||||
raw_resident_keys[i]->user_id_len > 0) {
|
||||
|
||||
cur_resident_key->sk_user_id =
|
||||
ssh_string_from_data(raw_resident_keys[i]->user_id,
|
||||
raw_resident_keys[i]->user_id_len);
|
||||
if (cur_resident_key->sk_user_id == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Failed to allocate user_id string for key %zu",
|
||||
i);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow the result array */
|
||||
temp_keys =
|
||||
realloc(result_keys, sizeof(ssh_key) * (result_keys_count + 1));
|
||||
if (temp_keys == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Failed to reallocate result keys array");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Add the current resident key to the result array */
|
||||
result_keys = temp_keys;
|
||||
result_keys[result_keys_count] = cur_resident_key;
|
||||
result_keys_count++;
|
||||
cur_resident_key = NULL;
|
||||
}
|
||||
|
||||
/* Set output parameters */
|
||||
*resident_keys_result = result_keys;
|
||||
*num_keys_found_result = result_keys_count;
|
||||
result_keys = NULL;
|
||||
result_keys_count = 0;
|
||||
rc = SSH_OK;
|
||||
|
||||
out:
|
||||
|
||||
if (raw_resident_keys != NULL) {
|
||||
for (i = 0; i < raw_keys_count; i++) {
|
||||
SK_RESIDENT_KEY_FREE(raw_resident_keys[i]);
|
||||
}
|
||||
SAFE_FREE(raw_resident_keys);
|
||||
}
|
||||
|
||||
SSH_KEY_FREE(cur_resident_key);
|
||||
for (i = 0; i < result_keys_count; i++) {
|
||||
SSH_KEY_FREE(result_keys[i]);
|
||||
}
|
||||
SAFE_FREE(result_keys);
|
||||
|
||||
/* Clean up temporary context if we created one */
|
||||
if (temp_ctx != NULL) {
|
||||
SSH_PKI_CTX_FREE(temp_ctx);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
Reference in New Issue
Block a user